Thursday, October 18, 2012

Dependency injection in ASP.NET MVC 3 validation attributes

In previous article(Dependency injection in ASP.NET MVC 3 action filters) we have seen how to inject dependencies in MVC Action Filters. Now let's try to inject dependency in Validation attribute. Validation attributes is used in MVC Model Validation. There are many useful built-in attributes, but often you need to write your own.

Let's imagine that we need attribute that validate that user's login(typed in registration form) is not occupied. So we write custom attribute:
 public class LoginExistanceValidatorAttribute : ValidationAttribute  
 {  
   public IRegistrator Registrator { get ; set ; }  
   public override bool IsValid(object value)  
   {  
      var valueStr = value as string;  
      if (string.IsNullOrEmpty(valueStr))  
       return false;  
      return Registrator.CheckLoginExistance(valueStr);  
    }  
 }  

Our attribute needs IRegistrator dependency to be injected. So, how to inject it? We have problem like one than we have discussed in previous article with Action Filters. And we have similar solution(as before, we use Castle Windsor IoC container):
 var container = new WindsorContainer();   
 container.Register(Component.For<ModelValidator>().ImplementedBy<WindsorModelValidator>().LifeStyle.Transient);   
 DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory((metadata, context, attribute) => container.Resolve<ModelValidator>(new { metadata, context, attribute }));  

All we need is to write custom Model Validator WindsorModelValidator that will inject dependencies in Validation attribute at some point. Example:
 public class WindsorModelValidator : DataAnnotationsModelValidator  
 {  
   public IWindsorContainer Container { get ; set ; }  
   public WindsorModelValidator(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute): base(metadata, context, attribute)
   {  
   }  
   public override IEnumerable<ModelValidationResult> Validate(object container)  
   {  
     // inject the services from the container that  
     // the Validation attribute requires  
     Container.Kernel.InjectProperties(Attribute);  
     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 WindsorServiceProvider(Container), null );  
     context.DisplayName = Metadata.GetDisplayName();  
     return context;  
    }  
 }  

All magic is in this line:
 Container.Kernel.InjectProperties(Attribute);  

We again use this extension method to inject dependencies in object. You can view code of this method in previous article about Action filters.

2 comments:

  1. So meny lines of code for so simple thing...
    I Prefere to use public statis wrapper for IoC and resolve any interface wharever I need (If i can't do constructor injection).

    ReplyDelete
  2. You right. Your solution is much simpler. But i wanted the cleanest solution in this scenario.

    ReplyDelete