SimpleInjector и FluentValidationFactory
Я пытаюсь автоматизировать проверку моих моделей представлений, я знаю, что могу просто добавить атрибут, чтобы указать мою проверку, но есть возможность настроить фабрику для автоматизации всего этого, на что я смотрел: этот ответ и придумал это с помощью простого инжектора 3.1:
public class CustomValidatorFactory:ValidatorFactoryBase
{
private readonly Container siContainer;
public CustomValidatorFactory(Container siContainer)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
this.siContainer = siContainer;
this.siContainer.Register(typeof(IValidator<>), assemblies);
}
public override IValidator CreateInstance(Type validatorType)
{
//var instances = siContainer.GetAllInstances(validatorType);
var implementation = ((IServiceProvider)siContainer).GetService(validatorType);
var validatorInstance = implementation != null ? (implementation as IValidator) : null;
return validatorInstance;
}
}
Тогда вид модели может быть чем-то вроде
public class Person {
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class PersonValidator : AbstractValidator<Person> {
public PersonValidator() {
RuleFor(x => x.Id).NotNull();
RuleFor(x => x.Name).Length(0, 10);
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Age).InclusiveBetween(18, 60);
}
}
Однако переменная реализации всегда равна нулю, я также пробовал RegisterCollection, но все еще имею ту же проблему, похоже, что простой инжектор не знает, как разрешить IValidator, когда валидатор наследует от AbstractValidator(это класс, который реализует IValidator)
2 ответа
Зарегистрируйте Fluid Validation Factory в Simple Injector следующим образом:
public class ApplicationValidatorFactory : IValidatorFactory
{
private readonly Container _container;
/// <summary>The constructor of the factory.</summary>
/// <param name="container">The Simple Injector Container</param>
public ApplicationValidatorFactory(Container container)
{
_container = container;
}
/// <summary>Gets the validator for the specified type.</summary>
public IValidator<T> GetValidator<T>()
{
return _container.GetInstance<IValidator<T>>();
}
/// <summary>Gets the validator for the specified type.</summary>
public IValidator GetValidator(Type type)
{
var validator = typeof(IValidator<>).MakeGenericType(type);
return (IValidator)_container.GetInstance(validator);
}
}
И затем в вашем Composition Root зарегистрируйте его для ASP.NET MVC:
// Register the validators and factory
var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
container.Register<IValidatorFactory, ApplicationValidatorFactory>(Lifestyle.Singleton);
container.Register(typeof(IValidator<>), assemblies);
// Register Simple Injector validation factory in FV
FluentValidationModelValidatorProvider.Configure(provider => {
provider.ValidatorFactory = new ApplicationValidatorFactory(container);
provider.AddImplicitRequiredValidator = false;
}
);
FluentValidationModelValidatorProvider
находится в FluentValidation.MVC
пакет интеграции. Не забудьте получить версию для вашей версии MVC.
ОБНОВИТЬ
Вы можете зарегистрировать пустой валидатор для объекта, который не имеет валидатора, например:
/// <summary>
/// Adds an unregistered type resolution for objects missing an IValidator.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
internal sealed class ValidateNothingDecorator<T> : AbstractValidator<T>
{
// I do nothing :-)
}
// Add unregistered type resolution for objects missing an IValidator<T>
// This should be placed after the registration of IValidator<>
container.RegisterConditional(typeof(IValidator<>), typeof(ValidateNothingDecorator<>), Lifestyle.Singleton, context => !context.Handled);
Я хотел бы поделиться своим опытом интеграции Simple Injector с FluentValidation здесь.
Моя первая попытка похожа на то, что сделал @janhartmann, реализовать конкретную ValidatorFactoryBase
который берет простой инжектор Container
как зависимость:
public class SimpleInjectorValidatorFactory : ValidatorFactoryBase
{
private readonly Container _container;
public SimpleInjectorValidatorFactory(Container container)
=> _container = container;
public override IValidator CreateInstance(Type validatorType)
=> (IValidator)_container.GetInstance(validatorType);
}
public static class CompositionRoot
{
public static void RegisterDependencies()
{
var container = new Container();
FluentValidationModelValidatorProvider.Configure(
provider => provider.ValidatorFactory =
new SimpleInjectorValidatorFactory(container));
}
}
Это работает, однако, я использую эту фабрику в контексте проекта ASP.NET MVC, и для моделей представления, которые полагаются на магию привязки модели, которые не имеют IValidator
для них зарегистрирован простой инжектор ActivationException
и вылетает приложение.
Моя вторая попытка, конечно, поставить блок try-catch вокруг GetInstance
:
public class SimpleInjectorValidatorFactory : ValidatorFactoryBase
{
private readonly Container _container;
public SimpleInjectorValidatorFactory(Container container)
=> _container = container;
public override IValidator CreateInstance(Type validatorType)
{
try
{
object validator = _container.GetInstance(validatorType);
return (IValidator)validator;
}
catch (ActivationException)
{
// FluentValidation will handle null properly
return null;
}
}
}
Но тогда я не удовлетворен тем фактом, что try-catch, очевидно, замедлит разрешение валидаторов, поэтому я оглядываюсь на SO, чтобы найти API, который делает Container
вернуть ноль вместо того, чтобы выдавать исключение. Это оказывается возможным, потому что Container
инвентарь IServiceProvider
явно, и IServiceProvider
вернусь null
если тип не зарегистрирован.
Моя третья и последняя попытка, и поскольку эта фабрика валидаторов больше не зависит от Simple Injector, я переименовал ее в ServiceProviderValidatorFactory
:
public class ServiceProviderValidatorFactory : ValidatorFactoryBase
{
private readonly IServiceProvider _serviceProvider;
public ServiceProviderValidatorFactory(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public override IValidator CreateInstance(Type validatorType)
=> (IValidator)_serviceProvider.GetService(validatorType);
}
public static class CompositionRoot
{
public static void RegisterDependencies()
{
var container = new Container();
FluentValidationModelValidatorProvider.Configure(
provider => provider.ValidatorFactory =
new ServiceProviderValidatorFactory(container));
}
}
Это работает и полностью отсоединяет фабрику валидаторов от Simple Injector в качестве дополнительного преимущества.