开发者

DataAnnotations dynamically attaching attributes

开发者 https://www.devze.com 2023-01-14 00:43 出处:网络
Apparently it is possible to dynamically attach DataAnnotation attributes to object properties at runtime and as such achieve dynamic validation开发者_Go百科.

Apparently it is possible to dynamically attach DataAnnotation attributes to object properties at runtime and as such achieve dynamic validation开发者_Go百科.

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:

DataAnnotations dynamically attaching attributes

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:

  • Some attributes such as DisplayAttribute aren't related to validation, so adding them at the validation stage doesn't work.
  • It may not be future-proof; a framework update could cause it to stop working.

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消