2013-12-18 2 views
2

У меня есть инъекция Unity Ioc, работающая в глобальных фильтрах, а также фильтры Action и Controller. Теперь мне нужно заставить IoC работать с атрибутами проверки.Как использовать Unity IoC в атрибуте проверки MVC?

Каков наилучший способ достичь этого в MVC5?

Приветствие Майк

+1

Посмотрите на [этот ответ на CodePlex] (https://simpleinjector.codeplex.com/discussions/472187#post1137461). В нем говорится об извлечении логики из атрибута и размещении его внутри службы. Хотя речь идет о веб-API и Simple Injector, я думаю, вы найдете это полезным, поскольку решение для Unity будет почти таким же. – Steven

ответ

2

ОК, я применил предложенное выше для контейнера Unity DI.

Вот новая модель Validator

public class UnityModelValidator : DataAnnotationsModelValidator 
{ 
    private readonly IUnityContainer unityContainer; 

    public UnityModelValidator(ModelMetadata metadata, 
     ControllerContext context, 
     ValidationAttribute attribute) 
     : base(metadata, context, attribute) 
    { 
     this.unityContainer = DependencyResolver.Current.GetService<IUnityContainer>(); 
    } 

    public override IEnumerable<ModelValidationResult> Validate(object container) 
    { 
     try 
     { 
      unityContainer.BuildUp(Attribute.GetType(), Attribute); 
     } 
     catch (ResolutionFailedException ex) 
     { 
      //Don't understand why it sometimes tries to use Unity to create an attribute rather than just build up an existing object. If this happens it can fail but we want to ignore it. 
     } 

     ValidationContext context = CreateValidationContext(container); 
     ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context); 
     if (result != ValidationResult.Success) 
     { 
      yield return new ModelValidationResult 
      { 
       Message = result.ErrorMessage 
      }; 
     } 
    } 

    protected virtual ValidationContext CreateValidationContext(object container) 
    { 
     var context = new ValidationContext(container ?? Metadata.Model, new UnityServiceLocator(unityContainer), null); 
     context.DisplayName = Metadata.GetDisplayName(); 
     return context; 
    } 
} 

Как и выполнение Buildup на атрибуте он также добавляет контейнер в качестве ServiceProvider к контексту, поэтому он может быть использован непосредственно в атрибуте. Я знаю, что это анти-шаблон, и я не хочу использовать его сам, но он включен здесь для полноты.

Вот код единства конфигурации

container.RegisterType<ModelValidator, UnityModelValidator>(new TransientLifetimeManager()); 

    DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory(
     (metadata, context, attribute) => container.Resolve<ModelValidator>(new ParameterOverrides() { { "metadata", metadata }, { "context", context }, { "attribute", attribute } })); 

Наконец это означает, что мы можем строить наши проверки атрибутов как этот

public class InitialsMustBeAvailableAttribute : ValidationAttribute 
{ 
    [Dependency] 
    public IUserService UserService { get; set; } 

    public override bool IsValid(object value) 
    { 
     return UserService.AreInitialsAvailable((string)value); 
    } 

    protected override ValidationResult IsValid(object value, ValidationContext validationContext) 
    { 
     var userService = (IUserService)validationContext.GetService(typeof(IUserService)); 

     if (userService != null) 
     { 
      if (userService.AreInitialsAvailable((string) value)) 
      { 
       return null; 
      } 
     } 

     return new ValidationResult("Initals must be unique!"); 
    } 
} 

Пожалуйста, обратите внимание, что вы на самом деле только использовать один из этих методов. Первый использует UserService, который был введен, второй использует ServiceLocator в контексте, чтобы захватить его сам.

Я предпочитаю первый.

Приветствия Майк

+0

Я немного изменил код. В определенных обстоятельствах он терпел неудачу, когда пытался создать атрибут из фреймворка. Он ошибся с ошибкой, «несколько конструкторов с 1 найденным параметром не могут устранить двузначность». Не знаю, зачем ему нужно получить доступ к конструктору для выполнения наращивания, но мы можем смело игнорировать эту ошибку. –

2

Там не существует хороший способ придать зависимости атрибутов валидации. Но есть два способа решения (http://forloop.co.uk/blog/resolving-ioc-container-services-for-validation-attributes-in-asp.net-mvc):

  1. Создать собственную ModelValidator, которые наследуют от DataAnnotationModelValidator и инъекционные зависимостей. Это решение более элегантно, но ваши атрибуты будут работать только в MVC.
  2. Используйте атрибут service-locator внутри атрибута проверки - менее элегантный, но он будет работать глобально. Вы сможете выполнить Validator.ValidateObject в любом месте
+2

Не согласен с вашими предложениями, однако некоторые считают, что «сервисный локатор» является [анти-шаблоном] (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) –

+2

@Veleous - I знайте это, но в некоторых случаях (например, это) нет лучшего способа. Я думаю, что нет ничего плохого в использовании анти-шаблона, если вы это намеренно делаете. –

+0

Есть ли у кого-нибудь пример пользовательского ModelValidator, который использует контейнер Unity, а не контейнер Castle Windsor? –

Смежные вопросы