Routing was introduced to .NET with the release of ASP.NET MVC 1.0 back in 2009. Routing is the process of taking an input URL, and mapping it to a route handler. This integrated into the ASP.NET pipeline as an IHttpModule
- the UrlRoutingModule
.
Current ASP.Net Routing
Let's remind ourselves about how the ASP.NET 4.x pipeline works:
Routing integrated with the pipeline as an IHttpModule
, and when a route was resolved, it would bypass the rest of the pipeline and delegate to the final IHttpHandler
through a new factory-type interface, the IRouteHandler
:
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext context);
}
It was through this IRouteHandler
that MVC integrated with Routing, and this is important, because generally MVC-style URLs are extensionless, so the routing system enabled these types of URLs to be mapped to a specific IHttpHandler
, and in the case of MVC, this means mapping to the MvcHandler
which was the entry point for controller/action execution. This means we didn't need to express a whole host of <httpHandler>
rules for each unique route in our web.config
file.
Mapping Routes
The MVC integration provided the MapRoute
methods as extensions for a RouteCollection
. Each Route
instance provides a Handler
property - which by default it set to the MvcRouteHandler
(through the IRouteHandler
abstraction):
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", Id = UrlParameter.Optional });
This method call creates a Route
instance with the MvcRouteHandler
set. You could always override that if you wanted to do something slightly more bespoke:
var route = routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", Id = UrlParameter.Optional });
route.Handler = new MyCustomHandler();
The Routing table
In current Routing (System.Web.Routing
) routes are registered into a RouteCollection
which forms a linear collection of all possible routes. When routes are being processed against an incoming URL, they form a top-down queue, where the first Route that matches, wins. For ASP.NET 4.x there can only be one route collection for your application, so all of your routing requirements had to be fulfilled by this instance.
ASP.Net Core 1.0 Routing
How has routing changed for ASP.Net Core 1.0? Quite significantly really, but they had managed to maintain a very familiar shape of API, but it is now integrated into the ASP.NET Core middleware pipeline.
The new Routing framework is based around the concept of an IRouter
:
public interface IRouter
{
Task RouteAsync(RouteContext context);
VirtualPathData GetVirtualPath(VirtualPathContext context);
}
An instance of RouterMiddleware
can be created using any instance of IRouter
. You can think of this as the root of a routing tree. Routers can be implemented any which way, and can be plugged directly into the pipeline using this middleware. To handle the classical routing collection (or route table), we now have an IRouteCollection
abstraction, itself extending IRouter
. This means that any RouteCollection
instance acts as a router and can be used directly with the middleware:
This is how MVC hooks into the pipeline. When you call app.UseMvc(r => { })
and configure your routes, you're actually using a new IRouteBuilder
abstraction which is used to build a router instance:
public interface IRouteBuilder
{
IRouter DefaultHandler { get; set; }
IServiceProvider ServiceProvider { get; }
IList<IRouter> Routes { get; }
IRouter Build();
}
For MVC, the DefaultHandler
property is an instance of MvcRouteHandler
, and this does the work of selecting an action to execute.
The MapRoute
methods are now provided as extensions of IRouteBuilder
and they work by creating new routes and adding them to the builder's Routes
collection. When the final Build
call is executed, the standard RouteBuilder
creates an instance of RouteCollection
, which acts as our router for our middleware instance.
Remembering this is important if you are migrating from an ASP.NET 4.x application to ASP.NET Core and you've invested heavily on tweaking the Routing framework to suit your needs.
A quick note on Attribute Routing
Attribute Routing is a feature of MVC and not directly tied to the Routing framework. Because MVC creates its own router, it can control at what point it integrates Attribute routing. It does this during the call to UseMvc(r => { })
by injecting a single instance of AttributeRoute
at the start of the route collection after all other routes have been configured.
This single instance of AttributeRoute
acts as the router for handling Controller and Action-level attribute routes, using the application model as the source of truth.
Making decisions
Using the standard MapRoute
method you end up with an instance of a TemplateRoute
which works on a route template string, such as {controller}/{action}/{id?}
. When the RouteAsync
method is evaluating for this type, it does so by checking to see if the incoming request path matches the route template. If this is true, it then checks against any applied constraints to determine if the route values lifted from the incoming request path are valid. If either of these steps returns false, then the route does not match and control is returned back to the route collection to test the next route. This is very similar to how conventional working currently operates for ASP.NET 4.x.
Finishing up
Hopefully you can appreciate this run through of the under-the-hood changes made to the Routing framework. This newer Routing framework offers up greater flexibility in composing our applications because of the integration with the middelware pipeline.
It's worth having a look at the GitHub repo code for yourself.