如何在MVC验证属性中使用Unity IoC?

时间:2013-12-18 17:45:03

标签: c# unity-container asp.net-mvc-5

我在全局过滤器以及操作和控制器过滤器中使用Unity Ioc注入。现在我需要让IoC在Validation Attributes中工作。

在MVC5中实现这一目标的最佳方法是什么?

干杯迈克

2 个答案:

答案 0 :(得分:3)

好的,我已经针对Unity DI容器调整了上面的建议。

这是新的模型验证器

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来自己抓取它。

我更喜欢前者。

干杯迈克

答案 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