Reputation: 2190
I'm trying to separate the concerns across my application and I thought this answer by Steven fits my scenario beautifully but I'm trying to remove the ninject element: Validation: How to inject A Model State wrapper with Ninject?
I've added all required classes from the answer.
I've got my service:
public class PromotionService {
private readonly IValidationProvider validationProvider;
public PromotionService(IValidationProvider validationProvider) {
this.validationProvider = validationProvider;
}
public void CreatePromotion(string promoName) {
//build the model
Promotion promo = new Promotion() {
//Name = promoName
};
//validate and throw validation exception
validationProvider.Validate(promo);
}
}
My ConfigureServices:
//Register Business Logic services
services.AddScoped<PromotionService>();
//get scope factory
var scopeFactory = services
.BuildServiceProvider()
.GetRequiredService<IServiceScopeFactory>();
//https://stackoverflow.com/questions/4776396/validation-how-to-inject-a-model-state-wrapper-with-ninject
Func<Type, IValidator> validatorFactory = type =>
{
var valType = typeof(Validator<>).MakeGenericType(type);
return (IValidator)scopeFactory.CreateScope().ServiceProvider.GetRequiredService(valType);
};
services.AddSingleton<IValidationProvider>(x => new ValidationProvider(validatorFactory));
services.AddScoped<Validator<Promotion>, PromotionValidator>();
Problem I get an exception when I call validationProvider.Validate(promo);
InvalidOperationException: No service for type 'VepoPortal.Services.Validators.Validator`1[VepoCustomerDatabase.Models.Promotion]' has been registered.
But I registered it here: services.AddScoped<Validator<Promotion>, PromotionValidator>();
Question How do I resolve this without the need for Ninject?
Edit: fixed with actual code:
services.AddScoped<Validator<Promotion>, PromotionValidator>();
services.AddScoped<IValidationProvider>(sp => {
Func<Type, IValidator> validatorFactory = type => {
var valType = typeof(Validator<>).MakeGenericType(type);
return (IValidator)sp.GetRequiredService(valType);
};
return new ValidationProvider(validatorFactory);
});
Upvotes: 1
Views: 668
Reputation: 387557
var scopeFactory = services
.BuildServiceProvider()
.GetRequiredService<IServiceScopeFactory>();
With this, you are building a separate service provider and use that service provider’s services to get a service scope factory.
So when you later do the following:
return (IValidator)scopeFactory.CreateScope().ServiceProvider.GetRequiredService(valType);
You are using that separate service provider. And when you resolve services from that service provider, then these are completely separate from the ones your application is running inside.
And since you have registered the Validator<Promotion>
only after you have created that separate service provider, that service provider will not include your validator service.
It is generally not a good idea to have multiple service providers within the same application. It will just cause services to have multiple lifetimes, e.g. singletons exist once per service provider, and you will get problems when you interact with services from other providers (like you have now). Instead, you should try to solve your problem using only a single service provider.
In your case, you could get there for example by changing the registration of your validation provider:
services.AddSingleton<IValidationProvider>(sp =>
{
Func<Type, IValidator> validatorFactory = type =>
{
var valType = typeof(Validator<>).MakeGenericType(type);
return sp.GetRequiredService(valType);
};
return new ValidationProvider(validatorFactory);
});
The sp
that gets passed to the factory there is the service provider, the one correct one. So you can use that directly to resolve services you need to initialize the provider.
You will notice that I do not create a new service scope within the validatorFactory
. This is because when you use service scopes, you should actually always dispose them after use. If you just create them and resolve a service from them, then the service scope will be kept open and you may run into issues later.
In your case, you cannot dispose of the service scope properly, because you need to return the resolved object and want to use it afterwards. So that means that using a service scope is not a good idea here. You should also consider whether you really need a service scope in general: ASP.NET Core already creates a service scope for every request, so it would make sense to just reuse that scope if you really need a scoped dependency.
Unfortunately, this also means that your implementation of ValidationProvider
is not really compatible to this. You could make ValidationProvider
itself a scoped service, so that it can safely access scoped services from the service provider (without having to manage its own service scope) or if you really need it to be a singleton, you could also move this logic into the validation provider implementation itself.
Upvotes: 3