Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyed services fail to dependency inject into middleware #54500

Closed
1 task done
hacst opened this issue Mar 12, 2024 · 6 comments · Fixed by #55722
Closed
1 task done

Keyed services fail to dependency inject into middleware #54500

hacst opened this issue Mar 12, 2024 · 6 comments · Fixed by #55722
Labels
area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlesware
Milestone

Comments

@hacst
Copy link

hacst commented Mar 12, 2024

Is there an existing issue for this?

  • 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

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Mar 12, 2024
@mkArtakMSFT mkArtakMSFT added area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlesware and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Mar 12, 2024
@laliconfigcat
Copy link

laliconfigcat commented Apr 10, 2024

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?

@KalleOlaviNiemitalo
Copy link

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…?

@Bituum
Copy link

Bituum commented Apr 11, 2024

I have the same problem.

@NicoBrabers
Copy link
Contributor

NicoBrabers commented May 8, 2024

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 pushed a commit to NicoBrabers/aspnetcore that referenced this issue May 14, 2024
…ction to Microsoft.AspNetCore.Http.Abstractions and utilized the ActivatorUtilities it provides to obtain a middleware instance. Also changed the ReflectionMiddlewareBinder to be able to handle keyed injection.
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue May 15, 2024
…new lines) based on pull-request feedback.
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue May 16, 2024
… parameter within the ReflectionFallback.
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue May 17, 2024
…ach parameter within the ReflectionFallback, removed the parameterType and declaringType from the precomputed cache.
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue May 17, 2024
…d added the NotNullWhen(true) attribute. Also changed the Inherit param from true to false when obtaining the custom attribute FromKeyedServicesAttribute.
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue May 21, 2024
…test. Added Benchmark(s) for the ReflectionFallback method.
@codymullins
Copy link

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

@gaufung
Copy link

gaufung commented Jun 9, 2024

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>();

NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue Jul 23, 2024
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue Jul 24, 2024
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue Jul 24, 2024
NicoBrabers pushed a commit to NicoBrabers/aspnetcore that referenced this issue Jul 25, 2024
@BrennanConroy BrennanConroy added this to the 9.0-rc1 milestone Jul 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlesware
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants