In February 2017, the Manning Early Access Program (MEAP) started for the ASP.NET Core book I am currently writing - ASP.NET Core in Action. This post is a sample of what you can find in the book. If you like what you see, please take a look - for now you can even get a 37% discount with the code lockaspdotnet!
The Manning Early Access Program provides you full access to books as they are written, You get the chapters as they are produced, plus the finished eBook as soon as it’s ready, and the paper book long before it's in bookstores. You can also interact with the author (me!) on the forums to provide feedback as the book is being written.
The book is now finished and completely available in the MEAP, so now is the time to act if you're interested! Thanks 🙂
MVC in ASP.NET Core
As you may be aware, ASP.NET Core implements MVC using a single piece of middleware, which is normally placed at the end of the middleware pipeline, as shown in figure 1. Once a request has been processed by each middleware (and assuming none of them handle the request and short-circuit the pipeline), it is received by the MVC middleware.
Figure 1. The middleware pipeline. The MVC Middleware is typically configured as the last middleware in the pipeline.
Middleware often handles cross-cutting concerns or narrowly defined requests such as requests for files. For requirements that fall outside of these functions, or which have many external dependencies, a more robust framework is required. The MvcMiddleware
in ASP.NET Core can provide this framework, allowing interaction with your application’s core business logic, and generation of a user interface. It handles everything from mapping the request to an appropriate controller, to generating the HTML or API response.
In the traditional description of the MVC design pattern, there is only a single type of model, which holds all the non-UI data and behavior. The controller updates this model as appropriate and then passes it to the view, which uses it to generate a UI. This simple, three-component pattern may be sufficient for some basic applications, but for more complex applications, it often doesn’t scale.
One of the problems when discussing MVC is the vague and overloaded terms that it uses, such as “controller” and “model.” Model, in particular, is such an overloaded term that it’s often difficult to be sure exactly what it refers to – is it an object, a collection of objects, an abstract concept? Even ASP.NET Core uses the word “model” to describe several related, but different, components, as you’ll see shortly.
Directing a request to a controller and building a binding model
The first step when the MvcMiddleware
receives a request is the routing of the request to an appropriate controller. Let’s think about another page in our ToDo application. On this page, you’re displaying a list of items marked with a given category, assigned to a particular user. If you’re looking at the list of items assigned to the user “Andrew” with a category of “Simple,” you’d make a request to the URL /todo/list/Simple/Andrew
.
Routing takes the path of the request, /todo/list/Simple/Andrew
, and maps it against a preregistered list of patterns. These patterns match a path to a single controller class and action method.
DEFINITION An action (or action method) is a method that runs in response to a request. A controller is a class that contains a number of logically grouped action methods.
Once an action method is selected, the binding model (if applicable) is generated, based on the incoming request and the method parameters required by the action method, as shown in figure 2. A binding model is normally a standard class, with properties that map to the request data.
DEFINITION A binding model is an object that acts a “container” for the data provided in a request which is required by an action method.
Figure 2. Routing a request to a controller, and building a binding model. A request to the URL /todo/list/Simple/Andrew
results in the ListCategory
action being executed, passing in a populated binding model
In this case, the binding model contains two properties: Category
, which is “bound” to the value "Simple"
; and the property User
which is bound to the value "Andrew"
. These values are provided in the request URL’s path and are used to populate a binding model of type TodoModel
.
This binding model corresponds to the method parameter of the ListCategory
action method. This binding model is passed to the action method when it executes, and it can be used to decide how to respond. For this example, the action method uses it to decide which ToDo items to display on the page.
Executing an action using the application model
The role of an action method in the controller is to coordinate the generation of a response to the request it’s handling. That means it should only perform a limited number of actions. In particular, it should:
- Validate that the data contained in the binding model provided is valid for the request
- Invoke the appropriate actions on the application model
- Select an appropriate response to generate, based on the response from the application model
Figure 3. When executed, an action invokes the appropriate methods in the application model.
Figure 3 shows the action model invoking an appropriate method on the application model. Here you can see that the “application model” is a somewhat abstract concept, which encapsulates the remaining non-UI part of your application. It contains the domain model, a number of services, database interaction, and a few other things.
DEFINITION The domain model encapsulates complex business logic in a series of classes that don’t depend on any infrastructure and can be easily tested
The action method typically calls into a single point in the application model. In our example of viewing a product page, the application model might use a variety of different services to check whether the user is allowed to view the product, to calculate the display price for the product, to load the details from the database, or to load a picture of the product from a file.
Assuming the request is valid, the application model returns the required details back to the action method. It’s then up to the action method to choose a response to generate.
Generating a response using a view model
Once the action method is called out to the application model that contains the application business logic, it’s time to generate a response. A view model captures the details necessary for the view to generate a response.
DEFINITION A view model is a simple object that contains data required by the view to render a UI. It’s typically some transformation of the data contained in the application model, plus extra information required to render the page, for example the page’s title.
The action method selects an appropriate view template and passes the view model to it. Each view is designed to work with a particular view model, which it uses to generate the final HTML response. Finally, this is sent back through the middleware pipeline and out to the user’s browser, as shown in figure 4.
Figure 4 The action method builds a view model, selects which view to use to generate the response, and passes it the view model. It is the view which generates the response itself.
It is important to note that although the action method selects which view to display, it doesn’t select what’s generated. It is the view itself that decides the content of the response.
Putting it all together: a complete mvc request
Now you’ve seen each of the steps that go into handling a request in ASP.NET Core using MVC, let’s put it all together from request to response. Figure 5 shows how each of the steps combine to handle the request to display the list of ToDos for user “Andrew” and category “Simple.” The traditional MVC pattern is still visible in ASP.NET Core, made up of the action/controller, the view, and the application model.
Figure 5 A complete MVC request for the list of ToDos in the “Simple” category for user “Andrew”
By now, you might be thinking this whole process seems rather convoluted – numerous steps to display some HTML! Why not allow the application model to create the view directly, rather than having to go on a dance back and forth with the controller/action method?
The key benefit throughout this process is the separation of concerns.
- The view is responsible for taking some data and generating HTML.
- The application model is responsible for executing the required business logic.
- The controller is responsible for validating the incoming request and selecting the appropriate view to display, based on the output of the application model.
By having clearly-defined boundaries it’s easier to update and test each of the components without depending on any of the others. If your UI logic changes, you won’t necessarily need to modify any of your business logic classes, and you’re less likely to introduce errors in unexpected places.
That’s all for this article. For more information, read the free first chapter of ASP.NET Core in Action and see this Slideshare presentation.