Search code examples
c#.netasp.net-coreminimal-apis

How to combine Minimal API's Pipeline Branching with MapGet syntax?


Say I want to have 2 endpoints, /a and /b, while also utilizing the pipeline branching for both of those endpoints, for adding custom middleware for them.

The following code does not work:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

var a = "/a";
var b = "/b";

app.Map(a, true, branch =>
{
    branch.Use((context, next) =>
    {
        context.Response.Headers.Add("X-Test", "a");
        return next(context);
    });
});

app.Map(b, true, branch =>
{
    branch.Use((context, next) =>
    {
        context.Response.Headers.Add("X-Test", "b");
        return next(context);
    });
});

app.MapGet(a, () => "hello from a!");
app.MapGet(b, () => "hello from b!");

app.Run();

Calling /a fails with:

System.InvalidOperationException: The request reached the end of the pipeline without executing the endpoint: 'HTTP: GET /a'. Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if using routing.
   at Microsoft.AspNetCore.Builder.ApplicationBuilder.<>c.<Build>b__25_0(HttpContext context)
   at Program.<>c.<<Main>$>b__0_4(HttpContext context, RequestDelegate next) in C:\Users\...\RiderProjects\MinimalApiSample\MinimalApiSample\Program.cs:line 15
   at Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

But I can't figure out, where to put UseEndpoints and why do I need it after adding custom app.Map(... calls? I'm also not putting UseRouting anywhere because I tried it right before the first app.Map(... but it didn't change the outcome and apparently it's not required in Minimal API at all.


Solution

  • Use UseWhen:

    Conditionally creates a branch in the request pipeline that is rejoined to the main pipeline.

    app.UseWhen(context => context.Request.Path == a, ab => ab.Use((context, next) =>
    {
        context.Response.Headers.Add("X-Test", "a");
        return next(context);
    }));
    
    //... the same for the b
    
    app.MapGet(a, () => "hello from a!");
    

    Map on the other hand creates just a separate branch:

    Branches the request pipeline based on matches of the given request path. If the request path starts with the given path, the branch is executed.

    Also see the Branch the middleware pipeline section of the docs.

    UPD

    if you need the separate branch then you can just build it fully. For example for /b:

    app.Map(b, true, branch =>
    {
        branch.Use((context, next) =>
        {
            context.Response.Headers.Add("X-Test", "b");
            return next(context);
        });
        branch.UseRouting();
        branch.UseEndpoints(rb => rb.MapGet(b, () => "hello from b!"));
    });