Creating an endpoint from multiple middleware in ASP.NET Core 3.x
Tags: middleware, netcore30, aspnetcore, routing
Published: 2020-02-16 14:03:02

---
title: Creating an endpoint from multiple middleware in ASP.NET Core 3.x
published: true
date: 2020-02-11 10:00:00 UTC
tags: Middleware,.NET Core 3.0,ASP.NET Core,Routing
canonical_url: https://andrewlock.net/creating-an-endpoint-from-multiple-middleware-in-aspnetcore-3/
---

 ![Creating an endpoint from multiple middleware in ASP.NET Core 3.x](https://andrewlock.net/content/images/2020/endpoint_middleware_banner.png)

In [a recent post](https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/) I discussed the changes to routing that come in ASP.NET Core 3.0, and how you can convert a "terminal" middleware to the new "endpoint" design. One question I've received is whether that removes the need for "branching" the pipeline, and if not, how can you achieve the same thing with endpoints?

This short post assumes that you've already read [my post on converting a terminal middleware to endpoint routing](https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/), so if you haven't already, take a look at that one first! I give a quick recap below, but I won't go into details.

## Converting terminal middleware to endpoints

In my previous post on terminal middleware I used a `VersionMiddleware` class as an example. This middleware always returns a response, which is [the `FileVersion` of the app's assembly](https://andrewlock.net/version-vs-versionsuffix-vs-packageversion-what-do-they-all-mean/):

```csharp
public class VersionMiddleware
{
    readonly RequestDelegate _next;
    static readonly Assembly _entryAssembly = System.Reflection.Assembly.GetEntryAssembly();
    static readonly string _version = FileVersionInfo.GetVersionInfo(_entryAssembly.Location).FileVersion;

    public VersionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync(_version);
    }
}
```

In ASP.NET Core 2.x, you could include the middleware as a conditional branch in your middleware pipeline, so that it only executes if the app receives a request starting with `"/version"`:

```csharp
public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseCors();

    app.Map("/version", versionApp => 
        versionApp.UseMiddleware<VersionMiddleware>()); 

    app.UseMvcWithDefaultRoute();
}
```

This approach creates a middleware pipeline that looks something like the following, with the `Map()` call branching the pipeline based on the incoming request.

<picture>
    <source srcset="https://andrewlock.net/content/images/2020/branching_version_middleware.svg" type="image/svg+xml">
    <img src="https://andrewlock.net/content/images/2020/branching_version_middleware.png" alt="Image of the app pipeline branching at Map">
</source></picture>

In ASP.NET Core 3.x using the `Map()` function is generally not idiomatic. Instead, it's more natural to use Endpoint Routing. In [my previous post on terminal middleware](https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/), I showed how you could take middleware like the `VersionMiddleware` above, and turn it into an "endpoint" that can be used like this:

```csharp
public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseRouting();

    app.UseCors();

    // Execute the endpoint selected by the routing middleware
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapVersion("/version");
        endpoints.MapDefaultControllerRoute();
    });
}

```

This approach is very similar to the original 2.x version, but with a few benefits:

- You can declaratively add authorization or [CORS requirements](https://docs.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-3.0#enable-cors-with-endpoint-routing) to the version endpoint.
- You benefit from full route-template matching, rather than being a simple "starts-with" check
- Middleware placed between `UseRouting()` and `UseEndpoints()` knows which endpoint is going to be run _before it runs_.

The end result is a _bit_ like the middleware pipeline still branching, but only branching right at the end when it splits into the various endpoints:

<picture>
    <source srcset="https://andrewlock.net/content/images/2020/endpoint_routing_version.svg" type="image/svg+xml">
    <img src="https://andrewlock.net/content/images/2020/endpoint_routing_version.png" alt="Image of the middleware pipeline splitting at the end of the request">
</source></picture>
## Creating larger middleware branches

One of the nice things about using `Map` in 2.x (which is the origin of the question that spawned this post) wass that you are given a whole new `IApplicationBuilder` that you can add middleware to.

For example, lets say you had a middleware that resizes an image provided in the body of the request, the `ResizeImageMiddleware`. You don't have the source code for this middleware - maybe you got it from a NuGet package - but you want to add some logging/caching/metrics around the requests. That's easy to do in 2.x, as you can add those extra features as middleware in the `Map` branch:

```csharp
public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseCors();

    app.Map("/resizeImage", resizeAppBuilder => 
        resizeAppBuilder
            .UseMiddleware<LoggingMiddleware>() // <- Add extra middleware in the branch.
            .UseMiddleware<CachingMiddleware>() // <- Only runs when ResizeImageMiddleware will be hit
            .UseMiddleware<ResizeImageMiddleware>()); 

    // <- The LoggingMiddleware and CachingMiddleware don't run if a request makes it to the MVC branch
    app.UseMvcWithDefaultRoute();
}
```

Because these middleware are added to the branch, they'll only execute for requests that ultimately hit the `ResizeImageMiddleware`, not requests that hit the MVC branch

<picture>
    <source srcset="https://andrewlock.net/content/images/2020/branching_image_middleware.svg" type="image/svg+xml">
    <img src="https://andrewlock.net/content/images/2020/branching_image_middleware.png" alt="Image of the app pipeline with extra middleware">
</source></picture>

On the face of it, you might feel a bit stuck when you try to do the same thing in ASP.NET Core 3.0. The middleware pipeline is effectively linear if you don't use `Map()`. You can't do the following, as that would execute the `LoggingMiddleware` and `CachingMiddleware` for every request:

```csharp
public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseRouting();

    app.UseCors();

    // These will now run for every request - not what we want
    app.UseMiddleware<LoggingMiddleware>();
    app.UseMiddleware<CachingMiddleware>(); 

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapResizeImageEndpoint("/resizeImage"); // <- We only want to run them here 
        endpoints.MapDefaultControllerRoute();
    });
}
```

Technically you _could_ do this if you customize the `LoggingMiddleware` and `CachingMiddleware` to execute based on the endpoint selected inside `UseRouting`, but there's an easier way.

> I showed how you can retrieve the selected endpoint name by calling `HttpContext.GetEndpoint()` [in a recent post on Serilog](https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-logging-the-selected-endpoint-name-with-serilog/#setting-values-in-idiagnosticcontext).

I cheated a little bit in the code above, as I didn't show the implementation of the `MapResizeImageEndpoint()`. If we look at the implementation shown below, then the better option should be more apparent:

```csharp
public static class ResizeImageEndpointRouteBuilderExtensions
{
    public static IEndpointConventionBuilder MapResizeImageEndpoint(
        this IEndpointRouteBuilder endpoints, string pattern)
    {
        var pipeline = endpoints.CreateApplicationBuilder()
            .UseMiddleware<ResizeImageMiddleware>()
            .Build();

        return endpoints.Map(pattern, pipeline).WithDisplayName("Resize image");
    }
}
```

This is the same format of extension method as I showed in [my previous post for converting the `VersionMiddleware` to an endpoint](https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/#converting-the-middleware-to-endpoint-routing). This code shows that an "endpoint" has its own `IApplicationBuilder`, which means you're not limited to adding a single piece of middleware, you can add a whole pipeline!

```csharp
public static class ResizeImageEndpointRouteBuilderExtensions
{
    public static IEndpointConventionBuilder MapResizeImageEndpoint(
        this IEndpointRouteBuilder endpoints, string pattern)
    {
        var pipeline = endpoints.CreateApplicationBuilder()
            .UseMiddleware<LoggingMiddleware>() // <- Add the extra middleware 
            .UseMiddleware<CachingMiddleware>() // <- They will only be executed when the endpoint runs
            .UseMiddleware<ResizeImageMiddleware>()
            .Build();

        return endpoints.Map(pattern, pipeline).WithDisplayName("Resize image");
    }
}
```

With the updated extension method, our middleware pipeline is again apparently simple:

```csharp
public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseRouting();

    app.UseCors();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapResizeImageEndpoint("/resizeImage"); // <- runs all our middleware
        endpoints.MapDefaultControllerRoute();
    });
}
```

We get all the benefits of endpoint routing here - we could add Authorization or CORS policies to the image endpoint for example - but we've also kept all the middleware unchanged from ASP.NET Core 2.x. In effect we've cut the resize image "branch" off at the original `Map()`, and moved it to the end instead:

<picture>
    <source srcset="https://andrewlock.net/content/images/2020/endpoint_routing_image.svg" type="image/svg+xml">
    <img src="https://andrewlock.net/content/images/2020/endpoint_routing_image.png" alt="Image of the resize image middleware branch in ASP.NET Core 3.x">
</source></picture>

The solution shown above doesn't work in all situations - as far as I know there's no easy way to add extra middleware to the MVC endpoints added by `MapDefaultControllerRoute()` (or the other MVC endpoint methods). If that's something you need, you could look at using [MVC filters](https://andrewlock.net/asp-net-core-in-action-filters/) as a way to hook into the pipeline instead.

## Summary

In this post I showed how you can create "composite" endpoints in ASP.NET Core 3.x, which consist of multiple middleware. This was commonly achieved in ASP.NET Core 2.x by calling `Map()` to branch the middleware pipeline, and can be used in ASP.NET Core 3.x in a similar way