Reputation: 1185
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
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
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):
Validator.ValidateObject
in any placeUpvotes: 2