mvc example dataannotations data custom annotation c# asp.net-mvc localization resources

c# - example - DataAnnotation con ResourceProvider personalizado



dataannotations mvc c# (3)

He creado un ResourceProvider personalizado para extraer información de localización de una base de datos. Ahora quiero usar DataAnnotation para agregar validación al modelo.

DataAnnotation tiene las propiedades ErrorMessageResourceType y ErrorMessageResourceName pero ErrorMessageResourceType solo acepta System.Type (es decir, un archivo de recursos compilado)

¿Hay alguna manera de hacer que DataAnnotation use el ResourceProvider personalizado?


Agregaré mis hallazgos ya que tuve que luchar con esto. Tal vez ayude a alguien.

Cuando se deriva de RequiredAttribute, parece romper la validación del lado del cliente. Así que para solucionar esto implementé IClientValidatable e implementé el método GetClientValidationRules. Resources.GetResources es un método auxiliar estático que tengo alrededor de HttpContext.GetGlobalResourceObject.

El atributo personalizado requerido:

public class LocalizedRequiredAttribute : RequiredAttribute, IClientValidatable { public LocalizedRequiredAttribute(string resourceName) { this.ErrorMessage = Resources.GetResource(resourceName); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { yield return new ModelClientValidationRule { ErrorMessage = this.ErrorMessage, ValidationType= "required" }; } }

Uso:

[LocalizedRequired("SomeResourceName")] public string SomeProperty { get; set; }

Y mi ayudante de Recursos si alguien está interesado:

public class Resources { public static string GetResource(string resourceName) { string text = resourceName; if (System.Web.HttpContext.Current != null) { var context = new HttpContextWrapper(System.Web.HttpContext.Current); var globalResourceObject = context.GetGlobalResourceObject(null, resourceName); if (globalResourceObject != null) text = globalResourceObject.ToString(); } return text; } }


He utilizado la validación fluida para lograr esto. Me ahorra mucho tiempo. Así es como se ve mi validador globalizado. Significa que no usa anotaciones de datos, pero a veces las anotaciones de datos se vuelven un poco grandes y desordenadas.

Aquí hay un ejemplo:

(Errors.Required, Labels.Email y Errors.AlreadyRegistered están en mi carpeta de recursos de blobal).

public class CreateEmployerValidator : AbstractValidator<CreateEmployerModel> { public RegisterUserValidator() { RuleFor(m => m.Email) .NotEmpty() .WithMessage(String.Format(Errors.Required, new object[] { Labels.Email })) .EmailAddress() .WithMessage(String.Format(Errors.Invalid, new object[] { Labels.Email })) .Must(this.BeUniqueEmail) .WithMessage(String.Format(Errors.AlreadyRegistered, new object[] { Labels.Email })); } public bool BeUniqueEmail(this IValidator validator, string email ) { //Database request to check if email already there? ... } }

Como dije, es una forma de alejar las anotaciones de datos, ¡solo porque ya tengo demasiadas anotaciones en mis métodos!


Me doy cuenta de que esta es una pregunta antigua, pero quería agregar un poco. Me encontré en la misma situación y no parece haber ninguna documentación / blogumentación sobre este tema. Sin embargo, descubrí una forma de utilizar un proveedor de recursos personalizado, con una advertencia. La advertencia es que estoy en una aplicación MVC, por lo que todavía tengo HttpContext.GetLocalResourceObject() disponible. Este es el método que utiliza asp.net para localizar elementos. La ausencia del objeto de recurso no le impide escribir nuestra propia solución, incluso si es una consulta directa de las tablas de base de datos. Sin embargo, pensé que valía la pena señalar.

Si bien no estoy terriblemente feliz con la siguiente solución, parece funcionar. Para cada atributo de validación que quiero usar, heredo de dicho atributo y sobrecargo el IsValid (). La decoración se ve así:

[RequiredLocalized(ErrorMessageResourceType= typeof(ClassBeginValidated), ErrorMessageResourceName="Errors.GenderRequired")] public string FirstName { get; set; }

El nuevo atributo se ve así:

public sealed class RequiredLocalized : RequiredAttribute { public override bool IsValid(object value) { if ( ! (ErrorMessageResourceType == null || String.IsNullOrWhiteSpace(ErrorMessageResourceName) ) ) { this.ErrorMessage = MVC_HtmlHelpers.Localize(this.ErrorMessageResourceType, this.ErrorMessageResourceName); this.ErrorMessageResourceType = null; this.ErrorMessageResourceName = null; } return base.IsValid(value); } }

Notas

  • Necesita decorar su código con el atributo derivado, no el estándar
  • Estoy usando ErrorMessageResourceType para pasar el tipo de la clase que se valida. Con eso quiero decir que si estoy en una clase de cliente y validando la propiedad FirstName pasaría typeof (cliente) . Estoy haciendo esto porque en el backend de mi base de datos estoy usando el nombre completo de la clase (espacio de nombres + nombre de clase) como clave (de la misma manera que se usa una URL de página en asp.net).
    • MVC_HtmlHelpers.Localize es solo un contenedor simple para mi proveedor de recursos personalizado

El código de ayuda (semi-robado) se ve así ...

public static string Localize (System.Type theType, string resourceKey) { return Localize (theType, resourceKey, null); } public static string Localize (System.Type theType, string resourceKey, params object[] args) { string resource = (HttpContext.GetLocalResourceObject(theType.FullName, resourceKey) ?? string.Empty).ToString(); return mergeTokens(resource, args); } private static string mergeTokens(string resource, object[] args) { if (resource != null && args != null && args.Length > 0) { return string.Format(resource, args); } else { return resource; } }