Mark Vincze

Software Developer

Matching route templates manually in ASP.NET Core

asp.net mvc

We can use routing in ASP.NET to define paths on which we want to respond to HTTP requests. In ASP.NET Core we have two common ways to specify routing in our application.

We can use the Route attribute on the action methods:

[HttpGet("test/{myParam}"]
public IActionResult Get(int myParam)
{
    // ...
}

Or if we don’t want to use MVC, we can directly set up some responses in our Startup class by creating a RouteBuilder and adding it to the pipeline with the UseRouter method.

RouteBuilder builder = new RouteBuilder(app);

builder.MapVerb(
    HttpMethod.Get.Method,
    "test/{myParam}",
    async context =>
    {
        var arg = context.GetRouteData().Values["myParam"];

        // ...
    });

app.UseRouter(builder.Build());

Using route matching manually

In usual development the methods above are enough, since most often we’ll use MVC to implement our application. On the other hand sometimes it can be handy if we’re able to manually match a URL path to a template and extract the arguments.

Recently I needed to do this in the Stubbery library, to set up stubbed replies for various request paths.

I looked around in the ASP.NET Routing codebase quite a while in an attempt to figure out how the routing worked internally and whether the classes doing the template matching are public, so I can use them in my code.

TLDR version

You can manually do the template matching and extract the route arguments with this little helper class.

public class RouteMatcher
{
    public RouteValueDictionary Match(string routeTemplate, string requestPath)
    {
        var template = TemplateParser.Parse(routeTemplate);

        var matcher = new TemplateMatcher(template, GetDefaults(template));

        var values = matcher.Match(requestPath);

        return values;
    }

    // This method extracts the default argument values from the template.
    private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
    {
        var result = new RouteValueDictionary();

        foreach (var parameter in parsedTemplate.Parameters)
        {
            if (parameter.DefaultValue != null)
            {
                result.Add(parameter.Name, parameter.DefaultValue);
            }
        }

        return result;
    }
}

Fortunately, the necessary framework classes (TemplateParser and TemplateMatcher) are public.

Some more detail

There are quite a handful of classes participating in the routing process, I needed to debug the code and step over the different calls to wrap my head around them. On a high level this is what happening during setting up our routes.

Set up routing in ASP.NET

When we specify a route by calling RouteBuilder.MapVerb a new Route is created, and added to the builder’s collection. The Route object has a delegate to the handler processing the request and producing the response. If we use UseMvc, then the routes are automatically created based on the action methods we have (this is done by a method called CreateAttributeMegaRoute :)), and then UseRouter is called.

When we call the UseRouter extension method, a middleware is registered called RouterMiddleware, to which a RouteCollection is passed with the configured routes.

This middleware is added to the pipeline, and its Invoke method is called during request processing. This method checks if any of the route templates match, and then calls the appropriate handler.

At this point it wasn’t much more work to find the classes doing the actual route matching and extract them into my own codebase.