Routing system

Essentially the URL Routing system is used to map incoming requests to controller methods.

The early routing of Asp.Net matched pretty exactly to the server folder structure. This works well for cgi style applications or aspx, where each file is its own response. But for MVC style applications it makes no sense because http requests are handled by action methods of controllers. The solution to this mismatch is the MVC routing system.

The routing system is used for two tasks in particular:

  1. evaluate incoming URLs to select the appropriate controller and action to handle the request
  2. Generate outgoing URLs. These are the ones that appear in output HTML.

This approach to routing URLs is especially useful, because it does not couple your C# classes to a specific URL. With Asp.Net Core the routing system is concise and lets you express any URL your application might need. It is pretty easy to setup and for most applications the default behavior might suffice. To set it up you simply use the UseMvcWithDefaultRoute() method in the Configure method of the Startup.cs:

namespace MyRoutingExample
{
    public class Startup
    {
        // omitted configureservices

        public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
        {
            app.UseDeveloperExceptionPage();
            app.UseStatusCodePages();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }
     }
}

This enables the routing middleware component that is set up by MVC to handle incoming http requests and outgoing http responses from your application. (more on this in another post about middleware).

What this post will cover

  • How the default routing works
  • How to define your own routes
  • Generally only the first task

What this post will not cover

  • Generating outgoing URLs for views and areas
  • General topics of MVC like controllers and such

For this post we assume our web application is reachable at the URL http://myapp.com/ .

What do URL patterns look like in MVC

To enable MVCs routing system you define a set of routes that define your URL schema for your application. This determines which URLs it responds to. Each route you will define contains an URL pattern , a so called template. This template is then used by MVC to compare against the incoming URL.

To see how those patterns work we will look at a common URL for an application:
http://myapp.com/Home/Index

This URL can be separated into segments, relative to the “/”. So this URL has then two segments. The first segment “Home” and the second segment “Index“. What MVC now does is using the first segment as the controller to invoke the action method with, which then is the Index method.

To express this as a URL routing pattern (template) for MVC you define it like so:
“{controller}/{action}”

The routing system now uses this pattern (or any other defined) to match incoming URLs, query strings and so on and so forth. The variables for segments are defined inside the curly braces “{” and “}”.

In a normal Asp.Net Core web application you will have several routes for the routing system to choose from. With only the above in place you will match only URLs with exactly two segments. In other cases a 404 Not Found response is send to the client. So one can say the URL routing is conservative about the amount of segments, but they are liberal with regards to the values of segments if they are at least of the right amount.

Registering Routes

To enable MVCs routing system you can either rely on the default routing system, which we will explore shortly, or register different self created routing patterns. This happens in the Startup classes Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //omitted other configuration expressions
    app.UseMvc(routes => {
        routes.MapRoute(name: "default", template: "{controller}/{action}");});
}

You create a route with a lambda expression passed as argument to the UseMvc method. This enables the routing middleware. The routing middleware uses routes that are registered in the same sequence that they are registered. The expression receives an object that implements IRouteBuilder interface from namespace Microsoft.AspNetCore.Routing.
When you register a route, by convention you would specify the arguments by name, as I did in the preceding example.

Naming routes, somewhat obstructs the clean separation that the routing system gives you especially when you use the names during generation of outgoing URLs. We will look further into this topic in the post about Generating outgoing URLs.

With the routing system, and the above route in place, you can create default values for your segments. What this does is, if a segment is not filled in the requesting URL, the parameter will be filled with the specified default value.

app.UseMvc(routes 
     => routes.MapRoute(name: "default_2",
                               template: "{controller}/{action}",
                               defaults: new {action = "Index"});

To specify defaults you use an anonymous type, that has property names that equal the segment names. Another way to use default values, is to write it directly into the routing segment parameters separated by an equal sign like so:

app.UseMvc(routes 
     => routes.MapRoute(name: "default_2",
                        template : "{controller = Home}/{action = Index}",);

Route Attribute and mixed routing

As well as default routing with the middleware you could use the Route Attribute to specify the route for a given controller and action. If one action of a controller is using a Route Attribute, these actions of the controller are only accessible by Route Attribute. So Mixed in the sense of Routing means, that you can have controller actions that use the middleware, and others employ the Route attribute routing but not both on the same method.

Typically you would employ the attribute routing on API controllers, where as the middleware routing would be used by “standard” ViewResult returning controllers. Another way to employ the Route Attribute is to override the default routing/middleware routing in special situations.

Static URL segments

Until now we created routes for the middleware that had parameters and parameters with default values. But this does not need to be the case, you can also use static segments. You could use this for example to fix a part of the URL so it always looks like this:

http://myapp.com/StaticSegment/Home/Index

Where the Home and Index are the Controller and Action from the default route. You would define this like so:

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{
     //omitted other configuration expressions 
     app.UseMvc(routes => { routes.MapRoute(name: "default",
                                            template: "StaticSegment/{controller=Home}/{action=Index}");}); 
}

Now all routes need to have at least three segments, where the first segment needs to match the term StaticSegment exactly. An interesting feature of this static segment is, that you can combine it with the defaults argument to create an alias for a URL. This helps to conserve a URL even though the controller name might change over the lifetime of your webapplication.

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{
     //omitted other configuration expressions 
     app.UseMvc(routes => { 
                 routes.MapRoute(
                   name: "default",
                   template: "StaticSegment/{action=Index}"
                   defaults: new { controller = "Home"}
                   );}
                ); 
}

This would now match to the URL http://myapp.com/StaticSegment/Index and would call the HomeControllers action method Index. This is extendable to the action segment, and would also be replaced by the a static segment and defined in the defaults anonymous type. This works, because the names controller and action have a specific meaning in the context of MVC applications.

Custom segments

Besides those two it is entirely possible to create custom segments. You simply create another segment after the controller and action parameter like the following example:

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{
     //omitted other configuration expressions 
     app.UseMvc(routes => { 
                 routes.MapRoute(
                   name: "default",
                   template: "{controller=Home}/{action=Index}/{id=DefaultId}"
                   );}
                ); 
}

Again like for the other segments it is conservative for the amount, but lliberal for the contents. If there is no third segment in the requesting URL the value DefaultId is used. Not allowed for the custom segments name are controller, action, area and page. This is because all of them have a specific meaning in the wake of MVC applications.

Next up we will look at how we can control the conservatism of the MVC routing system concerning the segments amount in an URL and also will look closer at how we can constrain the liberalism of the routing system concerning the segment values.


0 Comments

Leave a Reply

Avatar placeholder