Mike Dymond
Mike Dymond

Reputation: 1185

How do I use Unity IoC in an MVC Validation Attribute?

I have Unity Ioc injection working in Global Filters as well as Action and Controller filters. Now I need to get IoC working in Validation Attributes.

What is the best way to achieve this in MVC5?

Cheers Mike

Upvotes: 3

Views: 4237

Answers (2)

Mike Dymond
Mike Dymond

Reputation: 1185

OK, I have adapted the suggested above for the Unity DI container.

Here is the new Model 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;
    }
}

As well as performing the Buildup on the attribute it also adds the container as a ServiceProvider to the context so it can be used directly in the attribute. I know this is an anti-pattern and I do not want to use it myself, but it is included here for completeness sake.

Here is the unity config code

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

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

Finally this means that we can build our validation attributes like this

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!");
    }
}

Please note that you would only actually use one of these methods. The first uses the UserService that has been injected, the second uses the ServiceLocator in the context to grab it itself.

I much prefer the former.

Cheers Mike

Upvotes: 3

Sławomir Rosiek
Sławomir Rosiek

Reputation: 4073

There is no good way to inject dependencies to validation attributes. But there are two workarounds (http://forloop.co.uk/blog/resolving-ioc-container-services-for-validation-attributes-in-asp.net-mvc):

  1. Create own ModelValidator that inherit from DataAnnotationModelValidator and inject dependencies. This solution is more elegant but your attributes will work only in MVC.
  2. Use service-locator inside validation attribute - less elegant but it will be work globaly. You will be able to execute Validator.ValidateObject in any place

Upvotes: 2

Related Questions