DI020

Middleware Captures Scoped Service In Constructor

Scoped services captured by the constructor of a conventional middleware class — both directly (a scoped parameter) and transitively (a parameter whose activation graph reaches a scoped service). Middleware registrations are recognized in reduced extension form (`app.UseMiddleware<T>()`) and in direct framework static form (`UseMiddlewareExtensions.UseMiddleware<T>(app)` / `UseMiddlewareExtensions.UseMiddleware(app, typeof(T))`), with explicit activation arguments matched to constructor parameters.

Default severity: Warning · Code fix: No

Why it matters

Conventional middleware (used via `app.UseMiddleware<T>()`) is instantiated once per application lifetime. Injecting a scoped service into the constructor will cause that specific scoped instance to be captured for the entire application lifetime, which often leads to "captive dependency" bugs or runtime errors (e.g., if the service is a DbContext).

README problem example

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMyScopedService _scoped;

    public MyMiddleware(RequestDelegate next, IMyScopedService scoped)
    {
        _next = next;
        _scoped = scoped; // Scoped service captured in singleton middleware!
    }

    public Task InvokeAsync(HttpContext context) => _next(context);
}

README better pattern

public class MyMiddleware
{
    private readonly RequestDelegate _next;

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

    // Resolve scoped services from InvokeAsync parameters
    public Task InvokeAsync(HttpContext context, IMyScopedService scoped)
    {
        return _next(context);
    }
}

No. Moving dependencies to the `InvokeAsync` method may require significant architectural changes.

Repo sample extraction

Examples pulled from the sample app

Open full sample file

Sample app middleware capture warning

    public class Bad_MiddlewareConstructorCapture
    {
        private readonly RequestDelegate _next;
        private readonly IScopedService _scoped;

        // [DI020] Middleware 'Bad_MiddlewareConstructorCapture' captures scoped dependency 'IScopedService' in its constructor
        public Bad_MiddlewareConstructorCapture(RequestDelegate next, IScopedService scoped)
        {
            _next = next;
            _scoped = scoped;
        }

        public Task InvokeAsync(HttpContext context)
        {
            return _next(context);
        }
    }

Sample app safe invoke pattern

    public class Good_MiddlewareInvokeResolution
    {
        private readonly RequestDelegate _next;

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

        // Resolve scoped services from Invoke/InvokeAsync parameters instead
        public Task InvokeAsync(HttpContext context, IScopedService scoped)
        {
            return _next(context);
        }
    }

Related guides

  • No problem-guide pages point here yet.