mare
mare

Reputation: 13083

DataAnnotations dynamically attaching attributes

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?

Upvotes: 33

Views: 24378

Answers (4)

Sam
Sam

Reputation: 42387

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.

Upvotes: 0

Frank Horemans
Frank Horemans

Reputation: 416

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

Upvotes: 8

John Farrell
John Farrell

Reputation: 24754

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:

end result

with this model:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime Birthday { get; set; }
}

Upvotes: 41

Matthew Abbott
Matthew Abbott

Reputation: 61589

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.

Upvotes: -1

Related Questions