aspnetcore icon indicating copy to clipboard operation
aspnetcore copied to clipboard

Keyed services fail to dependency inject into middleware

Open hacst opened this issue 1 year ago • 4 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Describe the bug

It does not seem possible to get keyed services injected into middleware classes . Neither using constructor or Invoke/InvokeAsync argument injection. Attempting to do so results in a startup or invoke time exception respectively.

Expected Behavior

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#service-lifetimes documents constructor as well as InvokeAsync using DI as well as constructor injection using keyed services for classes in DI.

As such the expectation is for DI on middleware to also work with keyed services.

Steps To Reproduce

Execute the following code using dotnet run


class Program
{
    static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        builder.Services.AddKeyedSingleton<SomeClass>("test");
        builder.Services.AddSingleton<ObjectUsingKeyedSomeClass>();

        var app = builder.Build();
        app.UseMiddleware<MiddlewareUsingKeyedSomeClassConstructor>(); // Throws on startup
        app.UseMiddleware<MiddlewareUsingKeyedSomeClassInvoke>(); // Throws on invoke
        app.MapGet("/", ([FromKeyedServices("test")] SomeClass foo) => Results.Ok()); // Works
        app.MapGet("/2", (ObjectUsingKeyedSomeClass foo) => Results.Ok()); // Works
        app.Run();
    }
}

public class SomeClass { }
public class MiddlewareUsingKeyedSomeClassConstructor(RequestDelegate next, [FromKeyedServices("test")] SomeClass someClass)
{
    public async Task InvokeAsync(HttpContext context)
    {
        await next(context);
    }
}

public class MiddlewareUsingKeyedSomeClassInvoke(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context, [FromKeyedServices("test")] SomeClass someClass)
    {
        await next(context);
    }
}

class ObjectUsingKeyedSomeClass([FromKeyedServices("test")] SomeClass someClass) {}

Exceptions (if any)

app.UseMiddleware<MiddlewareUsingKeyedSomeClassConstructor>(); // Throws on startup

crit: Microsoft.AspNetCore.Hosting.Diagnostics[6]
      Application startup exception
      System.InvalidOperationException: Unable to resolve service for type 'SomeClass' while attempting to activate 'MiddlewareUsingKeyedSomeClassConstructor'.
         at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
         at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
         at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.ReflectionMiddlewareBinder.CreateMiddleware(RequestDelegate next)
         at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
         at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
         at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)

app.UseMiddleware<MiddlewareUsingKeyedSomeClassInvoke>(); // Throws on invoke

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: Unable to resolve service for type 'SomeClass' while attempting to Invoke middleware 'MiddlewareUsingKeyedSomeClassInvoke'.
         at lambda_method1(Closure, Object, HttpContext, IServiceProvider)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

8.0.200

Anything else?

No response

hacst avatar Mar 12 '24 16:03 hacst

I am also experiencing the issue with .NET 8.0.2. I did a workaround with injecting the IServiceProvider to the middleware and acquiring the keyed service implementation with serviceProvider.GetKeyedService<>. It works but it doesn't look so nice.

Do you have any updates regarding this issue?

laliconfigcat avatar Apr 10 '24 11:04 laliconfigcat

Microsoft.Extensions.DependencyInjection.ActivatorUtilities supports FromKeyedServicesAttribute but Microsoft.Extensions.Internal.ActivatorUtilities doesn't. Why does ASP.NET Core even have its own fork of this class…?

KalleOlaviNiemitalo avatar Apr 10 '24 13:04 KalleOlaviNiemitalo

I have the same problem.

Bituum avatar Apr 11 '24 11:04 Bituum

I'm experiencing the same issue. Specifically, it resolves to an unexpected instance when another instance is already registered using the AddSingleton method.

Consider the following example:

public class HelloWorldMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context, [FromKeyedServices("HelloWorld:1")] IHelloWorld helloWorld) 
    {         
        await next(context);
    }
}

Scenario 1 returns an incorrect instance: In Program.cs:

builder.Services.AddSingleton<IHelloWorld>(new HelloWorld()); // This instance is incorrectly resolved in the middleware, which shouldn't happen.
builder.Services.AddKeyedSingleton<IHelloWorld>("HelloWorld:1", new HelloWorld());

Scenario 2, which results in the error mentioned in the original post: In Program.cs:

builder.Services.AddKeyedSingleton<IHelloWorld>("HelloWorld:1", new HelloWorld());

NicoBrabers avatar May 08 '24 11:05 NicoBrabers

The workaround mentioned by @laliconfigcat (injecting via IServiceProvider) resolves this for now.

codymullins avatar May 24 '24 20:05 codymullins

Another workaround is to implement IMiddleware interface.

public class MiddlewareUsingKeyedSomeClassConstructor(R[FromKeyedServices("test")] SomeClass someClass) : IMiddleware
{
    public async Task InvokeAsync(HttpContext context. RequestDelegate next)
    {
        await next(context);
    }
}

builder.Services.AddTransient<MiddlewareUsingKeyedSomeClassConstructor>();

app.UseMiddleware<MiddlewareUsingKeyedSomeClassConstructor>();

gaufung avatar Jun 09 '24 13:06 gaufung