If you use centralized routing, it is occasionally needed to ignore a greedy Web API route so that the request can be processed by some other component or handler.
One of the tiny overlooked features of Web API 2.1 was that it finally shipped with a cross-host way to ignore routes. It’s not too exciting, as it’s something that’s been in MVC for ages, but it’s nice to finally have an easy way to do it in Web API.
Ignoring routes 🔗
Before 2.1, you could use System.Web.Routing.StopRoutingHandler to prevent Web API from catching certain routes, but obviously that was only available on web host (System.Web) and when running full ASP.NET runtime. On other hosts you were generally in trouble though.
Web API 2.1 introduced a Web API version of StopRoutingHandler (System.Web.Http.Routing) which can be used on all of the hosts - web host, self-host and OWIN host.
Consider a simple OWIN-based example. We compose an application from three pieces - Web API (Microsoft.AspNet.WebApi.OwinSelfHost), Nancy (Nancy.Owin) and Katana static file handler (Microsoft.Owin.StaticFiles).
Let’s imagine that in this OWIN app, the requests for /files/{anything} should be handled by static file handler, the requests for /{anything}.html should be handled by Nancy, and the rest by Web API.
You could achieve that by providing the relevant Web API ignore route definitions:
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("Html", "{whatever}.html/{*pathInfo}", null,
null, new StopRoutingHandler());
config.Routes.MapHttpRoute("FilesRoute", "files/{*pathInfo}", null,
null, new StopRoutingHandler());
config.Routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });
appBuilder.UseWebApi(config);
appBuilder.UseFileServer(new FileServerOptions()
{
RequestPath = new PathString("/files"),
EnableDirectoryBrowsing = true
});
appBuilder.UseNancy();
}
In fact there is even an extension method IgnoreRoute which makes the setup even a little simpler:
config.Routes.IgnoreRoute("Html", "{whatever}.html/{*pathInfo}");
config.Routes.IgnoreRoute("FilesRoute", "files/{*pathInfo}");
With this in place, Web API is still the default middleware and runs first, but the following rules will be obeyed:
-
- myapp.com/files/* -> static file handler
-
- myapp.com/*.html -> Nancy
-
- myapp.com/{everything else} -> Web API
You could achieve similar result without any ignore rules, by just reordering the OWIN middleware used - that is putting Web API last, static file handler first, and setting up pass-through rules for Nancy in case HTML is not found. However, in that case, every single request would go through those other two middlewares first, which might not be optimal from the performance standpoint if your primary service is Web API based, and you would want it to short circuit responses as soon as possible.
Interestingly, internally, the new System.Web.Http.Routing.StopRoutingHandler is a complete no-op - it doesn’t do anything besides being used for a type-check. Web API will find the ignored route as it would find any other route, but HttpRoutingDispatcher will then check if route handler attached to it is of StopRoutingHandler type, and if that’s true, treat the request as if there was no route matched. As a result the implementation of StopRoutingHandler is as follows:
public sealed class StopRoutingHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
}