aspnetcore
aspnetcore copied to clipboard
Keyed services fail to dependency inject into middleware
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
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?
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…?
I have the same problem.
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());
The workaround mentioned by @laliconfigcat (injecting via IServiceProvider) resolves this for now.
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>();