Ever been wondering what happens behind-the-scenes in MVC to render a page in an ASP.NET application? Wonder no more! I will take you through the processes that MVC does to solve that very action.
Routes
The first stage is that it will look through your list of routes.
Declaring the Pattern
Now, each route has a pattern which is basically the URL it must match to use that route. Each route also has a default controller and action so MVC knows which controller and action to use if that route matches. And you can also include custom placeholders.
Here is an example of declaring a route in an ASP.NET Core Application:
routes.MapRoute( name: "About", template: "about/{slug}", defaults: new { controller = "Page", action = "AboutUs" } );
Patterns
The template parameter is the one that contains the URL. As you can see from the above example, it contains some text wrapped around with curly brackets. This is a way of declaring a custom placeholder and there are two ways of declaring this:
- about/{slug} - The {slug} placeholder will store the value for the sub segment after the about parent folder. For example, if you requested the URL /about/me, the slug placeholder will store me as the value. However, if you were to request the URL /about/me/and-you, the pattern would not match and it will go on to the next route pattern.
- about/{*slug} - The {*slug} placeholder will store the value after the about segment. Similar to {slug} with one noticeable difference. It will store the full URL after the about segment of the URL. So if you were to request the URL /about/me/and-you, the slug placeholder would store me/and-you as the value.
Placeholders will be stored as values. This is part of MVC's RouteValueDictionary class and can be used further down the MVC process chain.
Typically, the order that MVC will use to match the pattern is the order that you declare the pattern. If there are two patterns that match the same URL, it will use the one that is declared first. On some versions of MVC, you can override an order of a pattern by declaring the Order parameter.
Constraints
When declaring a pattern in MVC, you can add an optional constraint. Now a constraint can be declared as something like a regular expression match, or you can create additional logic in a seperate class.
Examples of where you would use a constraint is if you wanted to render a custom product page on your ecommerce website. You could declare a route pattern of product/{slug} and want to perform a database check to see if a product with a slug exists. This is where you can do such thing. Here is an example:
// ProductConstraint.cs
namespace MyShoppingPage
{
public partial class ProductConstraint : IRouteConstraint
{
public virtual bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.UrlGeneration)
{
return true;
}
var slug = values["slug"] != null ? values["slug"].ToString() : null;
if (slug == null)
{
return false;
}
// Do my check against the product here
return true;
}
}
}
The constraint has to include the IRouteConstraint interface. This contains a Match method which returns a boolean. With this constraint, you need to declare it when registering a route pattern like below:
routes.MapRoute( name: "Product", template: "product/{slug}", defaults: new { controller = "Page", action = "Product" }, constraints: new { slug = new ProductConstraint() } );
If the constraint returns false, it will no longer use that route pattern and will continue to perform the URL against the next route pattern declared. However, if it returns true, it will go on and perform the code in the Controller and Action.
Controller and Action
In MVC, you will declare a controller class. Typically, the way a route matches a controller class is by the controller name stored in the route pattern followed by the word Controller.
For example, if you store Page as the controller name in the route pattern, MVC will look for the PageController class.
Inside a controller, you will have a number of methods which is known as actions. Inside an action, you can perform more logic, but what you return in this method will determine what is shown to the end user.
Here is an example:
// PageController.cs
namespace MyShoppingPage
{
public class PageController : Controller
{
public PageController()
{
}
public IActionResult Product()
{
return View();
}
}
}
Views
The typical examples that you will find on MVC will get you to return a view when declaring an Action method. From the above example, you can see that we are returning a View method inside the Product method.
Now, you can declare a custom model that gets passed through to the model. The model is just a class where you can store properties that you wish to use for the view.
I've typically used Razor for returning the views. A razor view is a enhanced HTML template that allows you to pass in C# code to perform functions.
Here is an example:
// MyProduct.cs
namespace MyShoppingPage.Models {
public partial class MyProduct {
public virtual string Title { get; }
public MyProduct(string title) {
Title = title;
}
}
}
<!-- Product.cshtml -->
@using MyShoppingPage.Models
@model MyProduct
<div class="myProduct">
<h1>@Model.Title</h1>
</div>
What if a Pattern is Not Found?
By default, if MVC can't find a matching pattern, either by the pattern of the URL, or the matching pattern returning false at the constraint stage, it will return a 404 not found error. This means that it's returning the correct status code to the browser so it doesn't store it in it's cache.