Factory-based middleware activation in ASP.NET Core

By Luke Latham

IMiddlewareFactory/IMiddleware is an extensibility point for middleware activation.

UseMiddleware extension methods check if a middleware's registered type implements IMiddleware. If it does, the IMiddlewareFactory instance registered in the container is used to resolve the IMiddleware implementation instead of using the convention-based middleware activation logic. The middleware is registered as a scoped or transient service in the app's service container.

Benefits:

  • Activation per request (injection of scoped services)
  • Strong typing of middleware

IMiddleware is activated per request, so scoped services can be injected into the middleware's constructor.

View or download sample code (how to download)

The sample app demonstrates middleware activated by:

The middleware implementations function identically and record the value provided by a query string parameter (key). The middlewares use an injected database context (a scoped service) to record the query string value in an in-memory database.

IMiddleware

IMiddleware defines middleware for the app's request pipeline. The InvokeAsync(HttpContext, RequestDelegate) method handles requests and returns a Task that represents the execution of the middleware.

Middleware activated by convention:

public class ConventionalMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context, AppDbContext db)
    {
        var keyValue = context.Request.Query["key"];

        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            db.Add(new Request()
                {
                    DT = DateTime.UtcNow, 
                    MiddlewareActivation = "ConventionalMiddleware", 
                    Value = keyValue
                });

            await db.SaveChangesAsync();
        }

        await _next(context);
    }
}

Middleware activated by MiddlewareFactory:

public class FactoryActivatedMiddleware : IMiddleware
{
    private readonly AppDbContext _db;

    public FactoryActivatedMiddleware(AppDbContext db)
    {
        _db = db;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var keyValue = context.Request.Query["key"];

        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            _db.Add(new Request()
                {
                    DT = DateTime.UtcNow, 
                    MiddlewareActivation = "FactoryActivatedMiddleware", 
                    Value = keyValue
                });

            await _db.SaveChangesAsync();
        }

        await next(context);
    }
}

Extensions are created for the middlewares:

public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseConventionalMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ConventionalMiddleware>();
    }

    public static IApplicationBuilder UseFactoryActivatedMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<FactoryActivatedMiddleware>();
    }
}

It isn't possible to pass objects to the factory-activated middleware with UseMiddleware:

public static IApplicationBuilder UseFactoryActivatedMiddleware(
    this IApplicationBuilder builder, bool option)
{
    // Passing 'option' as an argument throws a NotSupportedException at runtime.
    return builder.UseMiddleware<FactoryActivatedMiddleware>(option);
}

The factory-activated middleware is added to the built-in container in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<AppDbContext>(options =>
        options.UseInMemoryDatabase("InMemoryDb"));

    services.AddTransient<FactoryActivatedMiddleware>();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Both middlewares are registered in the request processing pipeline in Configure:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseConventionalMiddleware();
    app.UseFactoryActivatedMiddleware();

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseMvc();
}

IMiddlewareFactory

IMiddlewareFactory provides methods to create middleware. The middleware factory implementation is registered in the container as a scoped service.

The default IMiddlewareFactory implementation, MiddlewareFactory, is found in the Microsoft.AspNetCore.Http package.

Additional resources