Apparently it is possible to dynamically attach DataAnnotation attributes to object properties at runtime and as such achieve dynamic validation.
Can someone provide code sample on this?
Apparently it is possible to dynamically attach DataAnnotation attributes to object properties at runtime and as such achieve dynamic validation.
Can someone provide code sample on this?
MVC has a hook to provide your own ModelValidatorProvider. By default MVC 2 uses a sub class of ModelValidatorProvider called DataAnnotationsModelValidatorProvider that is able to use System.DataAnnotations.ComponentModel.ValidationAttribute attributes for validation.
The DataAnnotationsModelValidatorProvider uses reflection to find all the ValidationAttributes and simply loops through the collection to validate your models. All you need to do is override a method called GetValidators and inject your own attributes from whichever source you choose. I use this technique to do convention validations, the properties with DataType.Email attribute always gets passed through a regex, and use this technique to pull information from the database to apply more restrictive validations for "non-power" users.
The following example simply says "always make any FirstName properties required":
public class CustomMetadataValidationProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
//go to db if you want
//var repository = ((MyBaseController) context.Controller).RepositorySomething;
//find user if you need it
var user = context.HttpContext.User;
if (!string.IsNullOrWhiteSpace(metadata.PropertyName) && metadata.PropertyName == "FirstName")
attributes = new List<Attribute>() {new RequiredAttribute()};
return base.GetValidators(metadata, context, attributes);
}
}
All you have to do is register the provider in your Global.asax.cs file:
protected void Application_Start()
{
ModelValidatorProviders.Providers.Add(new CustomMetadataValidationProvider());
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
The end result:
with this model:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
}
In your global.asax you have to clear the ModelValidatorProviders before adding the new one. Otherwise it will add every annotation two times which will give you a "Validation type names in unobtrusive client validation rules must be unique."-error.
protected void Application_Start()
{
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new CustomMetadataValidationProvider());
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
The approach of using a custom MetadataValidationProvider
with an overridden GetValidators
has a few weaknesses:
DisplayAttribute
aren't related to validation, so adding them at the validation stage doesn't work.If you want your dynamically-applied data annotations to work consistently, you can subclass DataAnnotationsModelMetadataProvider
and DataAnnotationsModelValidatorProvider
. After doing this, replace the framework's ones via ModelMetadataProviders.Current
and ModelValidatorProviders.Providers
at application start-up. (You could do it in Application_Start
.)
When you subclass the built-in providers, a systematic and hopefully future-proof way to apply your own attributes is to override GetTypeDescriptor
. I've done this successfully, but it involved creating an implementation of ICustomTypeDescriptor
and PropertyDescriptor
, which required a lot of code and time.
I don't think you can add attributes to members at runtime, but you could probably use a custom metadata provider to handle this for you.
You should check out this blog post.