-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Microsoft.Extensions.DependencyInjection does not support co- and contravariance #82372
Comments
Reposted from aspnet/DependencyInjection#453 because if it is a breaking change, then it could happen on .Net 8 release |
Current: runtime/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs Line 135 in 7908e8e
Proposed: Func<ServiceProviderEngineScope, object?>? realizedService;
if (!_realizedServices.TryGetValue(serviceType, out realizedService)
{
realizedService = _realizedServices
.Where(kv => kv.Key.IsAssignableTo(serviceType))
.Select(kv => kv.Value)
.FirstOrDefault();
realizedService ??= _createServiceAccessor;
_realizedServices.TryAdd(serviceType, realizedService);
} |
Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection Issue DetailsDescriptionRelated: aspnet/DependencyInjection#453 (resolution: wont fix) Contravariant handler (see reproduction) is not resolved from MS.Ext.DI container. For example, my project has a ton of inheritence for some models, and each model has CreateXXXCommand and UpdateXXXCommand. Currently, a workaround (see workaround) is required to register handlers for each concrete type of CreateXXXCommand and UpdateXXXCommand. Reproduction Stepsusing Microsoft.Extensions.DependencyInjection;
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<ICommandHandler<PaintCarCommand>, PaintCarCommandHandler>();
using var serviceProvider = serviceCollection.BuildServiceProvider();
var anyColorHandler = serviceProvider.GetRequiredService<ICommandHandler<PaintCarCommand>>(); // Does not throw
var redHandler = serviceProvider.GetRequiredService<ICommandHandler<PaintCarRedCommand>>(); // Throws
var greenHandler = serviceProvider.GetRequiredService<ICommandHandler<PaintCarGreenCommand>>(); // Throws
abstract record PaintCarCommand;
record PaintCarRedCommand : PaintCarCommand;
record PaintCarGreenCommand : PaintCarCommand;
interface ICommandHandler<in TCommand>
{
void Handle(TCommand command);
}
class PaintCarCommandHandler : ICommandHandler<PaintCarCommand>
{
public void Handle(PaintCarCommand command)
{
Console.WriteLine($"Command type: {command.GetType().Name}");
}
} Expected behaviorRed paint command is handled by Actual behaviorHandler was not resolved for both red and green commands. Regression?No, it was never working for MS.Ext.DI. Known WorkaroundsManual handler resolution is required. That means that handler should be resolved for base class of the command, recursively (if for example concrete command is grandchild of the base class, or even further). Either way, for each concrete command, Just look at this beauty: private static void Workaround(IServiceCollection services)
{
RegisterFor<CreateFlowActionCommandHandler>(typeof(CreateDelayFlowActionCommand),
typeof(CreateActivateWebhookFlowActionCommand),
typeof(CreateChangeCheckinStatusFlowActionCommand),
typeof(CreateChangeReservationStatusFlowActionCommand),
typeof(CreateExecuteFlowFlowActionCommand),
typeof(CreateSendChatMessageFlowActionCommand));
RegisterFor<UpdateFlowActionCommandHandler>(typeof(UpdateDelayFlowActionCommand),
typeof(UpdateActivateWebhookFlowActionCommand),
typeof(UpdateChangeCheckinStatusFlowActionCommand),
typeof(UpdateChangeReservationStatusFlowActionCommand),
typeof(UpdateExecuteFlowFlowActionCommand),
typeof(UpdateSendChatMessageFlowActionCommand));
RegisterFor<CreateFlowConditionCommandHandler>(typeof(CreateWithConstantComparisonFlowConditionCommand),
typeof(CreateWithVariableComparisonFlowConditionCommand),
typeof(CreateCronExpressionFlowConditionCommand),
typeof(CreateDateRangeFlowConditionCommand),
typeof(CreateDayOfWeekFilterFlowConditionCommand),
typeof(CreateTimeRangeFlowConditionCommand));
RegisterFor<UpdateFlowConditionCommandHandler>(typeof(UpdateWithConstantComparisonFlowConditionCommand),
typeof(UpdateWithVariableComparisonFlowConditionCommand),
typeof(UpdateCronExpressionFlowConditionCommand),
typeof(UpdateDateRangeFlowConditionCommand),
typeof(UpdateDayOfWeekFilterFlowConditionCommand),
typeof(UpdateTimeRangeFlowConditionCommand));
RegisterFor<CreateFlowTriggerCommandHandler>(typeof(CreateNamedEventFlowTriggerCommand));
RegisterFor<UpdateFlowTriggerCommandHandler>(typeof(UpdateNamedEventFlowTriggerCommand));
void RegisterFor<TCommandHandler>(params Type[] commands)
{
foreach (var iHandler in commands.Select(command => typeof(INotificationHandler<>).MakeGenericType(command)))
services.AddScoped(iHandler, typeof(TCommandHandler));
}
} Configuration.Net 7 Other informationNo response
|
How is this different than the proposal in aspnet/DependencyInjection#453 that was rejected? |
Probably not any different. The feature to me seems not so hard to implement, and would probably be nice-to-have to every current CQRS project. Also, AFAIU, it should not break other containers, since they are build on DependencyInjection.Abstractions nuget nowadays, not the DependencyInjection nuget itself. (Or I don't get what Eilon ment by saying that) |
Description
Related: aspnet/DependencyInjection#453 (resolution: wont fix)
Related: jbogard/MediatR#819
Contravariant handler (see reproduction) is not resolved from MS.Ext.DI container.
If would be really usefull for CQRS applications that use mediator pattern.
For example, my project has a ton of inheritence for some models, and each model has CreateXXXCommand and UpdateXXXCommand. Currently, a workaround (see workaround) is required to register handlers for each concrete type of CreateXXXCommand and UpdateXXXCommand.
Reproduction Steps
Expected behavior
Red paint command is handled by
PaintCarCommandHandler
, as well as green command.Actual behavior
Handler was not resolved for both red and green commands.
Regression?
No, it was never working for MS.Ext.DI.
Known Workarounds
Manual handler resolution is required. That means that handler should be resolved for base class of the command, recursively (if for example concrete command is grandchild of the base class, or even further).
Sometimes it might not be possible, if service resolution goes inside of 3rd party library (for example, MediatR) and you don't have a chance to resolute handler manually there.
Either way, for each concrete command,
PaintCarCommandHandler
should be registered as implementation ofICommandHandler<ConcretePaintCarCommand>
. This either requires registering them by hand, or using reflection (Scrutor?) to register them automatically.Just look at this beauty:
Configuration
.Net 7
MS.Ext.DI 7.0.*
Other information
No response
The text was updated successfully, but these errors were encountered: