-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
Let's say we have the following services:
public enum WeatherProvider
{
AccuWeather = 1,
OpenWeatherMap = 2,
}
public interface IWeatherProvider
{
string GetWeatherForCity(string city);
}
public sealed class AccuWeatherWeatherProvider : IWeatherProvider
{
public string GetWeatherForCity(string city) => "AccuWeather";
}
public sealed class OpenWeatherMapWeatherProvider : IWeatherProvider
{
public string GetWeatherForCity(string city) => "OpenWeather";
}
And their registration in the dependency container using enum keys:
var builder = WebApplication.CreateBuilder();
builder
.Services
.AddKeyedScoped<IWeatherProvider, OpenWeatherMapWeatherProvider>(WeatherProvider.OpenWeatherMap)
.AddKeyedScoped<IWeatherProvider, AccuWeatherWeatherProvider>(WeatherProvider.AccuWeather);
Having this, we can resolve a weather provider by interface and key using method injection in a controller (which works fine):
[HttpGet]
public object GetWeather(
[FromQuery] string city,
[FromKeyedServices(WeatherProvider.OpenWeatherMap)] IWeatherProvider weatherProvider
)
{
return new
{
City = city,
Weather = weatherProvider.GetWeatherForCity(city),
};
}
However, if we wanted to inject a default weather provider via constructor, like:
[ApiController]
[Route("weather")]
public sealed class WeatherController : ControllerBase
{
private readonly IWeatherProvider _defaultWeatherProvider;
public WeatherController([FromKeyedServices(WeatherProvider.AccuWeather)] IWeatherProvider defaultWeatherProvider)
{
_defaultWeatherProvider = defaultWeatherProvider;
}
// actions omitted for brevity
}
We won't be able to query any of the actions of this controller due to a controller activation failure with an exception:
System.ArgumentException: Expression of type 'KeyedServicesExample.Api.Providers.WeatherProvider' cannot be used for parameter of type 'System.Object' of method 'System.Object GetService(System.IServiceProvider, System.Type, System.Type, Boolean, System.Object)' (Parameter 'arg4')
at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1, Expression arg2, Expression arg3, Expression arg4)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.BuildFactoryExpression(ConstructorInfo constructor, Nullable`1[] parameterMap, Expression serviceProvider, Expression factoryArgumentArray)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactoryInternal(Type instanceType, Type[] argumentTypes, ParameterExpression& provider, ParameterExpression& argumentArray, Expression& factoryExpressionBody)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactory(Type instanceType, Type[] argumentTypes)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.CreateActivator(ControllerActionDescriptor descriptor)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.CreateControllerFactory(ControllerActionDescriptor descriptor)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerCache.GetCachedResult(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Routing.ControllerRequestDelegateFactory.<>c__DisplayClass12_0.<CreateRequestDelegate>b__0(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
I believe it doesn't work because the key in this example is an enum, which is a value type. If we replace the enum key with a string key, it all works like a charm.
Expected Behavior
It should be possible to resolve keyed services using FromKeyedServices
attribute via controller constructor.
Steps To Reproduce
Link to an example project: https://github.com/inayelle/keyed-services-api-example
Exceptions (if any)
System.ArgumentException: Expression of type 'KeyedServicesExample.Api.Providers.WeatherProvider' cannot be used for parameter of type 'System.Object' of method 'System.Object GetService(System.IServiceProvider, System.Type, System.Type, Boolean, System.Object)' (Parameter 'arg4')
at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1, Expression arg2, Expression arg3, Expression arg4)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.BuildFactoryExpression(ConstructorInfo constructor, Nullable`1[] parameterMap, Expression serviceProvider, Expression factoryArgumentArray)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactoryInternal(Type instanceType, Type[] argumentTypes, ParameterExpression& provider, ParameterExpression& argumentArray, Expression& factoryExpressionBody)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactory(Type instanceType, Type[] argumentTypes)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.CreateActivator(ControllerActionDescriptor descriptor)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.CreateControllerFactory(ControllerActionDescriptor descriptor)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerCache.GetCachedResult(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Routing.ControllerRequestDelegateFactory.<>c__DisplayClass12_0.<CreateRequestDelegate>b__0(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
.NET Version
8.0.100
Anything else?
.NET SDK:
Version: 8.0.100
Commit: 57efcf1350
Workload version: 8.0.100-manifests.6c33ef20
Runtime Environment:
OS Name: arch
OS Version:
OS Platform: Linux
RID: linux-x64
Base Path: /home/inayelle/local/lib/dotnet/sdk/8.0.100/
.NET workloads installed:
Workload version: 8.0.100-manifests.6c33ef20
There are no installed workloads to display.
Host:
Version: 8.0.0
Architecture: x64
Commit: 5535e31a71
.NET SDKs installed:
7.0.404 [/home/inayelle/local/lib/dotnet/sdk]
8.0.100 [/home/inayelle/local/lib/dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 7.0.14 [/home/inayelle/local/lib/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.0 [/home/inayelle/local/lib/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 7.0.14 [/home/inayelle/local/lib/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.0 [/home/inayelle/local/lib/dotnet/shared/Microsoft.NETCore.App]
Other architectures found:
None
Environment variables:
DOTNET_ROOT [/home/inayelle/local/lib/dotnet]
global.json file:
Not found
Learn more:
https://aka.ms/dotnet/info
Download .NET:
https://aka.ms/dotnet/download