In an ASP.NET Core 1.1.2 web project, which depends on Microsoft.Extensions.DependencyInjection 1.1.1, I'm trying to register a generic FluentValidation validator both with its implementation and with its interface. At runtime, during host building, I got following exception:
An unhandled exception of type 'System.ArgumentException' occurred in Microsoft.Extensions.DependencyInjection.dll
Cannot instantiate implementation type 'MyProj.Shared.Api.WithUserCommandValidator`1[T]' for service type 'FluentValidation.IValidator`1[MyProj.Shared.Model.WithUser`1[T]]'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceTable..ctor(IEnumerable`1 descriptors)
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, Boolean validateScopes)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at MyProj.Program.Main(String[] args) in X:\MySolution\src\MyProj\Program.cs:line 31
This is the (generic, non-abstract) validator class:
public class WithUserCommandValidator<TCommand> : AbstractValidator<WithUser<TCommand>>
where TCommand : ICommand
{
public WithUserCommandValidator(IValidator<TCommand> commandValidator)
{
RuleFor(cmd => cmd).NotNull();
RuleFor(cmd => cmd.UserId).NotEqual(Guid.Empty);
RuleFor(cmd => cmd.Content).NotNull()
.SetValidator(commandValidator);
}
}
(not sure is important to show WithUser<T>
class, it just holds a Guid UserId
and a T Content
).
Registration happens like so:
someInterface = typeof(FluentValidation.IValidator<WithUser<>>);
someImplementation = typeof(Shared.Api.WithUserCommandValidator<>);
// this is for top-level command validators, injected by interface
services.AddScoped(someInterface, someImplementation);
// this is for nested validators, injected by implementation
services.AddScoped(someImplementation);
and at runtime those variables hold:
+ someInterface {FluentValidation.IValidator`1[MyProj.Shared.Model.WithUser`1[TCommand]]} System.Type {System.RuntimeType}
+ someImplementation {MyProj.Shared.Api.WithUserCommandValidator`1[TCommand]} System.Type {System.RuntimeType}
which seems correct.
I've looked around for similar problems and questions on SO as well, but they're not really related, as they were about generic implementation, while this is a concrete one.
I've stepped through DependencyInjection module code, and exception is thrown here. For some reason, serviceTypeInfo.IsGenericTypeDefinition
returns false (shouldn't that be true, as the interface type is generic?) and so the else-branch is taken. There, instead, the implementation type return true to implementationTypeInfo.IsGenericTypeDefinition
, so there you have the exception.
Runtime values are:
+ serviceTypeInfo {FluentValidation.IValidator`1[MyProj.Shared.Model.WithUser`1[TCommand]]} System.Reflection.TypeInfo {System.RuntimeType}
+ implementationTypeInfo {MyProj.Shared.Api.WithUserCommandValidator`1[TCommand]} System.Reflection.TypeInfo {System.RuntimeType}
While fiddling, I tried registering only the implementation, without also registering interface+implementation, and that exception goes away. But I'd like to inject the interface, if possible, not the implementation.
What am I doing wrong? TA
What you are trying to do is to map a partially closed type (namely IValidator<WithUser<T>>
), which is something that is not supported by .NET Core's DI container. When it comes to generic types, the .NET Core container is a very minimalistic, simplistic implementation. It is unable to handle things like:
If you require this behavior and want to keep continuing to use the built-in container for this, you will have to register each closed implementation by hand:
AddScoped(typeof(IValidator<WithUser<Cmd1>>),typeof(WithUserCommandValidator<Cmd1>));
AddScoped(typeof(IValidator<WithUser<Cmd2>>),typeof(WithUserCommandValidator<Cmd2>));
AddScoped(typeof(IValidator<WithUser<Cmd3>>),typeof(WithUserCommandValidator<Cmd3>));
// etc
If this is leads to an unmaintainable Composition Root, you should pick a different container. The only DI Container that I know actually fully supports these kinds of generic designs is Simple Injector.
With Simple Injector, you can simply do the following registration:
container.Register(
typeof(IValidator<>),
typeof(WithUserCommandValidator<>),
Lifestyle.Scoped);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With