Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how does dependency injection work with Middleware?

I saw some code lilke this:

public class CustomMiddleware {
    private RequestDelegate next;

    public CustomMiddleware (RequestDelegate nextDelegate) {
       next = nextDelegate;
    }
    
    public async Task Invoke(HttpContext context, IResponseFormatter formatter) {
       ...
       await next(context);
    }
}

and IResponseFormatter service is registered as:

public void ConfigureServices(IServiceCollection services) {
    services.AddTransient<IResponseFormatter, GuidService>();
}

I know how DI works, but my understanding how middleware works is, next(RequestDelegate) represents the next middleware's Invoke method, so in CustomMiddleware, even the second argument is resolved by DI, but the definition of RequestDelegate is

public delegate Task RequestDelegate(HttpContext context);

how does the previous middleware before CustomMiddleware knows that CustomMiddleware's Invoke method has changed by having an extra argument? It cannot know in advance, therefore the previous middleware's next RequestDelegate does't match the signature of CustomMiddleware's Invoke method?


1 Answers

Internally, framework code uses reflection to determine arguments for both constructor and Invoke member of the middleware by the following convention

The middleware class must include:

  • A public constructor with a parameter of type RequestDelegate.
  • A public method named Invoke or InvokeAsync. This method must:
    • Return a Task.
    • Accept a first parameter of type HttpContext.

Additional parameters for the constructor and Invoke/InvokeAsync are populated by dependency injection (DI).

Reference Write custom ASP.NET Core middleware

As demonstrated from the Source code

/// <summary>
/// Adds a middleware type to the application's request pipeline.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
/// <param name="middleware">The middleware type.</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)
{
    if (typeof(IMiddleware).IsAssignableFrom(middleware))
    {
        // IMiddleware doesn't support passing args directly since it's
        // activated from the container
        if (args.Length > 0)
        {
            throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
        }

        return UseMiddlewareInterface(app, middleware);
    }

    var applicationServices = app.ApplicationServices;
    return app.Use(next =>
    {
        var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
        var invokeMethods = methods.Where(m =>
            string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
            || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
            ).ToArray();

        if (invokeMethods.Length > 1)
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
        }

        if (invokeMethods.Length == 0)
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
        }

        var methodInfo = invokeMethods[0];
        if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
        }

        var parameters = methodInfo.GetParameters();
        if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
        }

        var ctorArgs = new object[args.Length + 1];
        ctorArgs[0] = next;
        Array.Copy(args, 0, ctorArgs, 1, args.Length);
        var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
        if (parameters.Length == 1)
        {
            return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
        }

        var factory = Compile<object>(methodInfo, parameters);

        return context =>
        {
            var serviceProvider = context.RequestServices ?? applicationServices;
            if (serviceProvider == null)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
            }

            return factory(instance, context, serviceProvider);
        };
    });
}
like image 129
Nkosi Avatar answered Sep 13 '25 15:09

Nkosi