Override or Force Routing in ASP.NET Core

This blog post describes a technique that you can use to force the application of routes in ASP.NET Core, overriding routes that would apply otherwise, including catchall routes.

Recently, I implemented an ASP.NET Razor solution that uses a catchall wildcard in the @Page directive. I also implemented a Web API that can be hosted as part of the Razor Pages solution or separately. These each use the ASP.NET Core routing system with minor differences in configuration.

Probably after I put the catchall in the @Page directive but possibly after started hosting the Web API in the same solution, I noticed that the site was not serving static files. This is my understanding.

  • In a default ASP.NET razor pages solution, if no razor page matches the requested URL, then subsequent middleware pipeline logic applies, which could include the static file handler, the 404 handler, and otherwise. I implemented a catchall that matches all requests.
  • I added the Web API before the razor pages configuration, so if the URL matches a Web API route, the system applies that before reaching the logic for razor pages.
  • There is no route for static files. The system would need to scan the entire file system and create routes, which would not be desirable. I could probably add logic in the request process such that if the requested URL path corresponds to a file or directory that exists, then serve it before razor pages logic. I chose an alternative described below.

I imagine that I will find more in the future (sitemap.xml and robots.txt come to mind, but I intend to use logic rather than flat files to serve those anyway), but for now, all the static files in this solution are either well-known or in a few well-known directories: /favicon.ico, /css, /js, /lib, and /img, and I intend to keep it that way (no new files or directories at the document root).

In my case, I can map requests for anything matching any of these paths to the static file logic. It would be easier if I moved these directories within something like a /static directory, but I have few enough of these that I find this configuration acceptable. I added this method to configure an IRouter to Startup.cs.

private IRouter BuildRouter(IApplicationBuilder applicationBuilder)
{
    RouteBuilder routeBuilder = new RouteBuilder(applicationBuilder);
    routeBuilder.MapMiddlewareGet("/lib/{*path}", appBuilder => { appBuilder.UseStaticFiles(); });
    routeBuilder.MapMiddlewareGet("/img/{*path}", appBuilder => { appBuilder.UseStaticFiles(); });
    routeBuilder.MapMiddlewareGet("/css/{*path}", appBuilder => { appBuilder.UseStaticFiles(); });
    routeBuilder.MapMiddlewareGet("/js/{*path}", appBuilder => { appBuilder.UseStaticFiles(); });
    routeBuilder.MapMiddlewareGet("/favicon.ico", appBuilder => { appBuilder.UseStaticFiles(); });
    return routeBuilder.Build();
}

I think this will use default error control, which may generate results inconsistent with other error pages, but visitors should not request these resources directly anyway. The relevant section of the Configure method in my Startup.cs looks like this. Note the call to the UseRouter() method, passing the IRouter created by the method listed previously.

app.UseRouter(BuildRouter(app));

if (HostWebApi)
{
    app.UseMvcWithDefaultRoute();
}
else
{
    app.UseMvc();
}

app.UseRouting();

if (!IsDevelopment)
{
    app.UseAuthorization();
}

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
});

As always, there may be a better way.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: