Getting route data in your ASP.NET Core middleware
Requirement
Why can’t I do this in earlier versions?
Read ASP.NET Core 2.2 First Look – Endpoint Routing and ASP.NET Core updates in .NET Core 3.0 Preview 2 to get a more-detailed picture on how routing is now exposed in relation to ASP.NET Core.
TL;DR
Routing has traditionally been a construct confined within the ASP.NET ecosystem. As long as your code executes within that construct, you can read route data. However, with the proliferation of middleware that run before and after ASP.NET, the desire/need for an app.-wide, exposed routing system increased.
The 🗝️ to making this work
ASP.NET Core 2.2
using Microsoft.AspNetCore.Internal;
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app
.UseEndpointRouting() // Registering this prior to your middleware unlocks the ✨.
//.YourMiddleware()
.UseMvc();
}
}
ASP.NET Core 3.0 (preview-2)
using Microsoft.AspNetCore.Builder;
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app
.UseRouting() // Registering this prior to your middleware unlocks the ✨.
//.YourMiddleware()
.UseMvc();
}
}
Example
Imagine you’re building a new blog engine… 🤔
💡 Maybe you want to pre-load a post via middleware so it’s available anywhere down the chain. This way, none of other middleware is concerned with how to retrieve the post and can instead focus on whatever it does to augment the response.
Model
public class Post
{
public string Slug { get; set; }
public string Title { get; set; }
}
/// <summary>
/// We need a derivative class to support scenarios where we can't find a `Post`-record based on {slug}.
/// DI will throw an exception if we try to give it `null`.
/// </summary>
public class GenericPost : Post
{ }
Razor view
@page "{slug}"
@model PostModel
<h1>@Model.Title</h1>
...
Razor view code
public class PostModel : BaseModel
{
public PostModel(Post post)
{
this.post = post;
}
public Post post { get; private set; }
public void OnGet()
{ }
}
Middleware
public class PostMiddleware
{
public PostMiddleware(
RequestDelegate next,
IDatabase database)
)
{
this.next = next;
this.database = database;
}
private readonly IDatabase datbase;
private readonly RequestDelegate next;
public async Task InvokeAsync(HttpContext context)
{
var route = context.GetRouteData();
if (route.Values.TryGetValue("slug", out var routeSlug))
{
var slug = routeSlug.ToString();
var post = (await database.Fetch<Post>()).FirstOrDefault(x => x.Slug == slug);
if (post != null)
{
context.Items.Add(nameof(Post), post);
}
}
await next(context);
}
}
Startup
ℹ️ This could also be refactored into a separate extension-method.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<Post>(serviceProvider =>
{
var accessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
if (accessor.HttpContext.Items.TryGetValue(nameof(Post), out var post))
{
return post as Post;
}
else
{
return new GenericPost(); // we need to return something that is either a `Post` or derivative of `Post`.
}
});
}
References
Here are a couple source-links for both the 2.2 and 3.0-versions if you’re interested in what happens behind the scenes: