ASP.NET Core MVC – Doors Open, Come In!

Throughout the experiment, I have an empty project. Having an empty project is a good way to learn. It allows me to focus on other things first such as

  1. Build and Release with VSTS
  2. Play around with Docker and Part 2
  3. Fundamental – Configuration.

Ultimately, I want to use ASP.NET Core MVC to build a web application (and other services around it). Time to open the door and step into MVC world; but, as always, with the fundamental first.

ASP.NET Core application is enriched by the use of Middlewares at the developers’ will. MVC is one of the built-in middleware. Plug it in, you have MVC feature. Take it out, well, you are on your own.

MVC Middlewares High Level
MVC Middlewares High Level

Remember, order matters! Let’s take a quick tour with Middleware first.

Middleware

By default, even with an empty project, there are built-in middlewares auto generated. The detail is in the previous post.

Just plug the MVC Middleware in and we are good to go.

Model – View – Controller (MVC)

MVC Model High Level
MVC Model High Level

A request comes in, Controller does 2 things

  1. Build a proper Model.
  2. Decide what view to use.

Remember the keywords here: Build – Model, Decide – View.

The main purpose of a Model is to supply data for the View.

The View takes the data supplied from Model, and render the final HTML for the Response.

Why is that diagram important? Because It came from my understanding of MVC, in my own words (with some borrow from Courses I learned). The point is that I have my own way to express it instead of reading paragraphs of text. Diagrams, images increase my ability to explain.

When you learn something, try to draw diagrams. Try to explain it visually.

Show Me the Code

Let create a home page with some information from an empty project. From a design perspective, what do we need to build an MVC application? Well, I would naively say these

  1. Enable MVC middle
  2. Create a controller to handle the logic
  3. Create a view to display the result

With some fundamentals knowledge about MVC, I created these code and structure

AspNet MVC First Controller
AspNet MVC First Controller

Problems? When I hit F5, an empty page is displayed. I would have expected the Home/Index view displayed.

Old lesson: Nothing works at the first time. If it works, something must have been wrong.

I guess that I missed the routing. Basically, I have to tell the MVC framework how to map a request to a Controller.

Let dig a bit further into routing from MS docs site. There are so many things to learn from that site. However, I just want to get my first simple routing work. I pick what matters most to me.

       public void ConfigureServices(IServiceCollection services)
        {
            // Register the ability to read options in configuration

            services.AddRouting();
            services.AddMvc();
 
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    "default",
                    "{controller=Home}/{action=Index}/{id?}");
            });
        }

To make the magic work, we must

  1. Enable both Route (AddRouting) and MVC (AddMvc) services. This will register all the necessary services required for routing and MVC
  2. Use MVC with configured routing. Above I defined a default routing.

Visit the home page. It works as I expected. Note

Note: If you create an MVC project from an MVC template, they are all created for you, for free.

Add Logic

The next step is to work with the logic code. So far, I have had a Controller ready to play around, the mighty HomeController.

Display an ugly fact: How far are you to the creator?

Let’s assume a man lives for 80 years maximum. Given a birthday, display how many you have spent and how close you are to the creator.

It is a pretty simple logic. The questions I want to explore here are

  1. Where should I put the logic code?
  2. What kind of project should it be? .NET Standard? .NET Core? or .NET Framework?
  3. How to organize data and flow?

First, let make the functionality work

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// Serve the GET /home/uglyfact?birthday=dd-MM-yyyy
        /// </summary>
        /// <param name="birthday"></param>
        /// <returns></returns>
        public IActionResult UglyFact(string birthday)
        {
            // Ignore all data validation. Simply assume that the request is correct.
            var dob = DateTime.ParseExact(birthday, "dd-MM-yyyy", CultureInfo.CurrentCulture);
            var meetCreatorOn = dob.AddYears(80);
            var spent = (int)Math.Round(DateTime.Now.Subtract(dob).TotalDays / 7);
            var closed = (int)Math.Round(meetCreatorOn.Subtract(DateTime.Now).TotalDays / 7);
            return View( new UglyFactModel
            {
                SpentWeeks = spent,
                CloseToCreatorWeeks = closed
            });
        }
    }

There is an UglyFact Action which will do some simple calculation. And then it returns an UglyFactModel to the View. Pretty typical MVC stuff. When we take a closer look

When we take a closer look at the UglyFact action implementation, it is a typical piece of business logic. We know that it should not be there. The business logic should not be in the Controller-Action code.

The business logic should bot be in the Controller-Action code.

I decided to go with .NET Standard. Because the business logic code does not depend on the .NET Framework.

namespace Coconut.CoreLogic
{
    public class UglyFactTeller
    {
        public UglyFactModel TellMe(DateTime dob)
        {
            var meetCreatorOn = dob.AddYears(80);
            var spent = (int)Math.Round(DateTime.Now.Subtract(dob).TotalDays / 7);
            var closed = (int)Math.Round(meetCreatorOn.Subtract(DateTime.Now).TotalDays / 7);
            return new UglyFactModel
            {
                SpentWeeks = spent,
                CloseToCreatorWeeks = closed
            };
        }
    }
}

The logic in the Controller-Action has been moved to a separated assembly. And here the nice-clean controller

namespace Coconut.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly UglyFactTeller _uglyFactTeller;

        public HomeController(UglyFactTeller uglyFactTeller)
        {
            _uglyFactTeller = uglyFactTeller;
        }
        public IActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// Serve the GET /home/uglyfact?birthday=dd-MM-yyyy
        /// </summary>
        /// <param name="birthday"></param>
        /// <returns></returns>
        public IActionResult UglyFact(string birthday)
        {
            // Ignore all data validation. Simply assume that the request is correct.
            var dob = DateTime.ParseExact(birthday, "dd-MM-yyyy", CultureInfo.CurrentCulture);
            return View( _uglyFactTeller.TellMe(dob));
        }
    }
}

To make everything works as before I tell the HomeController how to resolve its dependency (the UglyFactTeller class)

services.AddTransient<UglyFactTeller>();

What’s Next?

At the beginning, I wanted to write more in the post. However, it seems too much for a single post. It is a better idea to stop here and enjoy the “get the job done” feeling.

Next, I will continue with how to make the code structure better (with MediatR (mediator pattern)), explore the unit test.

One of the fun thing when doing from the scratch (or at least with an empty project) is that you think you know them but you do not. You do not know unless you start coding/implementing.

Write a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.