Implement Middlewares using Endpoint Routing in ASP.NET Core 3.0
Jürgen Gutsch - 10 April, 2019
If you have a Middleware that needs to work on a specific path, you should implement it by mapping it to a route in ASP.NET Core 3.0, instead of just checking the path names. This post doesn't handle regular Middlewares, which need to work all request, or all requests inside a Map
or MapWhen
branch.
At the Global MVP Summit 2019 in Redmond I attended the hackathon where I worked on my GraphQL Middlewares for ASP.NET Core. I asked Glen Condron for a review of the API and the way the Middleware gets configured. He told me that we did it all right. We followed the proposed way to provide and configure an ASP.NET Core Middleware. But he also told me that there is a new way in ASP.NET Core 3.0 to use this kind of Middlewares.
Glen asked James Newton King who works on the new Endpoint Routing to show me how this needs to be done in ASP.NET Core 3.0. James pointed me to the ASP.NET Core Health Checks and explained me the new way to go.
BTW: That's kinda closing the loop: Four summits ago Damien Bowden and I where working on the initial drafts of the ASP.NET Core Health Checks together with Glen Condron. Awesome that this is now in production ;-)
The new ASP.NET Core 3.0 implementation of the GraphQL Middlewares is in the aspnetcore30 branch of the repository: https://github.com/JuergenGutsch/graphql-aspnetcore
About Endpoint Routing
The MVP fellow Steve Gordon had an early look into Endpoint Routing. His great post may help you to understand Entpoint Routing.
How it worked before:
Until now you used MapWhen()
to map the Middleware to a specific condition defined in a predicate:
Func<HttpContext, bool> predicate = context =>
{
return context.Request.Path.StartsWithSegments(path, out var remaining) &&
string.IsNullOrEmpty(remaining);
};
return builder.MapWhen(predicate, b => b.UseMiddleware<GraphQlMiddleware>(schemaProvider, options));
(ApplicationBuilderExtensions.cs)
In this case the path is checked. This is pretty common to not only map based on paths. This allows you to also map on all other kind of criteria based on the HttpContext
.
Also the much simpler Map()
was a way to go:
builder.Map(path, branch => branch.UseMiddleware<GraphQlMiddleware>(schemaProvider, options));
How this should be done now
In ASP.NET Core 3.0 these kind of mappings, where you may listen on a specific endpoint, should be done using the EndpoiontRouteBuilder
. If you create a new ASP.NET Core 3.0 web application. MVC is now added a little different in the Startup.cs
than before:
app.UseRouting(routes =>
{
routes.MapControllerRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRazorPages();
});
The method MapControllerRoute()
adds the controller based MVC and Web API. The new ASP.NET Core Health Checks, which also provide an own endpoint will also be added like this. Means we now have Map()
methods as extension methods on the IEndpointRouteBuilder
instead of Use()
methods on the IApplicationBuilder
. It is still possible to use the Use
methods.
In case of the GraphQL Middleware it looks like this:
var pipeline = routes.CreateApplicationBuilder()
.UseMiddleware<GraphQlMiddleware>(schemaProvider, options)
.Build();
return routes.Map(pattern, pipeline)
.WithDisplayName(_defaultDisplayName);
(EndpointRouteBuilderExtensions.cs)
Based on the current IEndpointRouteBuilder
a new IApplicationBuilder
is created, where we Use
the GraphQL Middleware as before. We pass the ISchemaProvider
and the GraphQlMiddlewareOptions
as arguments to the Middleware. The result is a RequestDelegate
in the pipeline
variable.
The configured endpoint pattern
and the pipeline
than gets mapped to the IEndpointRouteBuilder
. The small extension Method WithDisplayName()
sets the configured display name to the endpoint.
I needed to copy this extension method to from the ASP.NET Core repository to my code base, because the current development build of ASP.NET Core didn't contain this method two weeks ago. I need to check the latest version ASAP.
In ASP.NET Core 3.0 the GraphQl and the GraphiQl Middleware can now added like this:
app.UseRouting(routes =>
{
if (env.IsDevelopment())
{
routes.MapGraphiQl("/graphiql");
}
routes.MapGraphQl("/graphql");
routes.MapControllerRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRazorPages();
});
Conclusion
The new ASP.NET Core 3.0 implementation of the GraphQL Middlewares is on the aspnetcore30 branch of the repository: https://github.com/JuergenGutsch/graphql-aspnetcore
This approach feels a bit different. In my opinion it messes the startup.cs
a little bit. Previously we added one middleware after another... line by line to the IApplicationBuilder
method. With this approach we have some Middlewares still registered on the IApplicationBuilder
and some others on the IEndpointRouteBuilder
inside a lambda expression on a new IApplicationBuilder
.
The other thing is, that the order isn't really clear anymore. When will the Middlewares inside the UseRouting()
be executed and in which direction? I will dig deeper into this the next months.