In the end, I looked at the source code of ASP.NET (because it is now open source!) And found that I can copy the UseMvc extension method from this class and replace the default handler for my own.
public static class TokenAuthenticationExtensions { public static IApplicationBuilder UseTokenAuthentication(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes) { var routes = new RouteBuilder { DefaultHandler = new TokenRouteHandler(), ServiceProvider = app.ApplicationServices }; configureRoutes(routes); routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute( routes.DefaultHandler, app.ApplicationServices)); return app.UseRouter(routes.Build()); } }
Then you create your own version of this class . In my case, I really don't want to reference actions. I will let the typical Mvc middleware do this. Since this is the case, I selected all the associated code and saved only what I need to get the route data that is in the actionDescriptor variable. I can probably delete the code associated with the backups of the route data, since I do not think that what I will do will affect the data, but I saved it in this example. This is the skeleton I'll start with, based on the mvc route handler.
public class TokenRouteHandler : IRouter { private IActionSelector _actionSelector; public VirtualPathData GetVirtualPath(VirtualPathContext context) { EnsureServices(context.Context); context.IsBound = _actionSelector.HasValidAction(context); return null; } public async Task RouteAsync(RouteContext context) { var services = context.HttpContext.RequestServices; EnsureServices(context.HttpContext); var actionDescriptor = await _actionSelector.SelectAsync(context); if (actionDescriptor == null) { return; } var oldRouteData = context.RouteData; var newRouteData = new RouteData(oldRouteData); if (actionDescriptor.RouteValueDefaults != null) { foreach (var kvp in actionDescriptor.RouteValueDefaults) { if (!newRouteData.Values.ContainsKey(kvp.Key)) { newRouteData.Values.Add(kvp.Key, kvp.Value); } } } try { context.RouteData = newRouteData;
And finally, in the Startup.cs Configure file at the end of the pipeline, I have a setting, so I use the same routing setting (I use attribute routing) to authenticate the token and router mvc.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //Other middleware delcartions here <---------------- Action<IRouteBuilder> routeBuilder = routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }; app.UseTokenAuthentication(routeBuilder); //Middleware after this point will be blocked if authentication fails by having the TokenRouteHandler setting context.IsHandled to true app.UseMvc(routeBuilder); }
Edit 1: I should also note that at the moment I am not worried about the extra time required to select a route twice, which I think will happen here since both my middleware and Mvc middleware will do this. If this becomes a performance issue, I will create mvc and authentication for one handler. That would be the best idea in terms of performance, but what I showed here is the most modular approach that I think.
Edit 2: In the end, in order to get the information I needed, I had to include an ActionDescriptor in ControllerActionDescriptor. I'm not sure what other types of actions you can have in ASP.NET, but I'm sure all my action descriptors should be ControllerActionDescriptors. Perhaps the old legacy of Web Api requires a different type of ActionDescriptor.