I'm using SimpleInjector 4 and FluentValidation 7. My AbstractValidators have a dependency on my DbContext.
public class Validator : AbstractValidator<LocationModel>
{
public LocationModelValidator(IReadOnlyRepository repository)
{
// Check the database to see if this location is already present
RuleFor(x => x.LocationId).Must(x => !repository.Location.Any(i => i.LocationId == x)).WithMessage("A Location with this ID already exists.");
}
}
My composition root looks as follows:
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Register<IReadOnlyRepository, LocationDbContext>(Lifestyle.Scoped);
container.Register<IValidatorFactory>(() => new ServiceProviderValidatorFactory(GlobalConfiguration.Configuration));
container.Register(typeof(IValidator<>), assemblies, Lifestyle.Scoped);
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
container.Verify();
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
ValidatorFactory implementation
public class ServiceProviderValidatorFactory : ValidatorFactoryBase
{
private readonly HttpConfiguration _config;
public ServiceProviderValidatorFactory(HttpConfiguration config)
{
_config = config;
}
public override IValidator CreateInstance(Type validatorType)
{
return (IValidator) _config.DependencyResolver.GetService(validatorType);
}
}
WebApiConfig.cs
// throws error: instance is requested outside the context of an active (Async Scoped) scope
// FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration, x => x.ValidatorFactory = container.GetInstance<IValidatorFactory>());
FluentValidationModelValidatorProvider.Configure(config, x => x.ValidatorFactory = new ServiceProviderValidatorFactory(config));
The validation kicks in and works fine for the first API request but all subsequent API requests give the The operation cannot be completed because the DbContext has been disposed. error.
I've also tried setting up ServiceProviderValidatorFactory to use IServiceProvider approach and calling FluentValidationModelValidatorProvider.Configure(config, x => x.ValidatorFactory = new ServiceProviderValidatorFactory(container));
as shown here:
https://stackoverflow.com/a/43883455/654708
but that doesn't work either.
It looks as though once the AbstractValidators have loaded, they are cached/never disposed of but the DbContext is.
I'm guessing a hybrid lifestyle might work here (as setting the IReadOnlyRepository to a singleton lifestyle works fine) but I haven't had any luck there either.
UPDATE
Looks as though the AbstractValidator classes are Singletons. I've updated the validators and factory to be transient in the composition root but it doesn't seem to work as they are still only getting instantiated once.
container.Register<IValidatorFactory>(() => new ServiceProviderValidatorFactory(container), Lifestyle.Scoped);
container.Register(typeof(IValidator<>), assemblies, Lifestyle.Transient);
I've verified this by setting a break point inside one my AbstractValidator classes and can see that it only gets called once, i.e. on the first web api request but not on subsequent requests.
UPDATE 2
I was able to somewhat get around this problem by inject a Func<IReadOnlyRepository> repository into my AbstractValidator classes:
public class LocationModelValidator : AbstractValidator<LocationModel>
{
public LocationModelValidator(Func<IReadOnlyRepository> repository)
{
// Check the database to see if this location is already present
RuleFor(x => x.LocationId).Must(x => !repository.Invoke().Locations.Any(i => i.LocationId == x)).WithMessage("A Location with this ID already exists.");
}
}
And updating my composition root with the following:
container.RegisterSingleton<Func<IReadOnlyRepository>>(() => container.GetInstance<IReadOnlyRepository>);
I'd rather not have to do this but given I can't figure out why the AbstractValidators are always singletons, it will do for now.