DataAnnotaciones que unen atributos de forma dinámica

Aparentemente, es posible vincular dinámicamente los atributos de la Anotación de Datos a las propiedades del objeto en el tiempo de ejecución y, como tal, lograr la validación dinámica.

¿Alguien puede proporcionar una muestra de código sobre esto?

MVC tiene un gancho para proporcionar su propio ModelValidatorProvider. De forma predeterminada, MVC 2 usa una subclase de ModelValidatorProvider llamada DataAnnotationsModelValidatorProvider que puede usar los atributos System.DataAnnotations.ComponentModel.ValidationAttribute para la validación.

DataAnnotationsModelValidatorProvider utiliza la reflexión para encontrar todos los ValidationAttributes y simplemente recorre la colección para validar sus modelos. Todo lo que necesita hacer es anular un método llamado GetValidators e inyectar sus propios atributos de la fuente que elija. Utilizo esta técnica para hacer validaciones de convenciones, las propiedades con el atributo DataType.Email siempre se pasan a través de una expresión regular, y utilizo esta técnica para extraer información de la base de datos para aplicar validaciones más restrictivas para usuarios “no poderosos”.

El siguiente ejemplo simplemente dice “siempre se requieren propiedades de primer nombre”:

public class CustomMetadataValidationProvider : DataAnnotationsModelValidatorProvider { protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable 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() {new RequiredAttribute()}; return base.GetValidators(metadata, context, attributes); } } 

Todo lo que tiene que hacer es registrar el proveedor en su archivo Global.asax.cs:

  protected void Application_Start() { ModelValidatorProviders.Providers.Add(new CustomMetadataValidationProvider()); AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); } 

El final resulto:

resultado final

con este modelo:

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

En su archivo.asax global, debe borrar los Proveedores de ModelValidator antes de agregar el nuevo. De lo contrario, agregará cada anotación dos veces lo que le dará un “Los nombres de tipo de validación en las reglas de validación discretas del cliente deben ser únicas.” – error.

 protected void Application_Start() { ModelValidatorProviders.Providers.Clear(); ModelValidatorProviders.Providers.Add(new CustomMetadataValidationProvider()); AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); } 

No creo que pueda agregar atributos a los miembros en el tiempo de ejecución, pero probablemente podría usar un proveedor de metadatos personalizado para manejar esto por usted.

Deberías echar un vistazo a esta publicación en el blog .

El enfoque de usar un MetadataValidationProvider personalizado con GetValidators anulado tiene algunas debilidades:

  • Algunos atributos, como DisplayAttribute , no están relacionados con la validación, por lo que agregarlos en la etapa de validación no funciona.
  • Puede no ser a prueba de futuro; una actualización del marco podría hacer que deje de funcionar.

Si desea que las anotaciones de datos aplicadas dinámicamente funcionen de manera coherente, puede DataAnnotationsModelMetadataProvider subclase DataAnnotationsModelMetadataProvider y DataAnnotationsModelValidatorProvider . Después de hacer esto, reemplace los frameworks a través de ModelMetadataProviders.Current y ModelValidatorProviders.Providers al inicio de la aplicación. (Puede hacerlo en Application_Start ).

Cuando subclasifica los proveedores integrados, una forma sistemática y, con suerte, a prueba de futuro de aplicar sus propios atributos es anular GetTypeDescriptor . Lo hice con éxito, pero implicó crear una implementación de ICustomTypeDescriptor y PropertyDescriptor , que requería mucho código y tiempo.