2009-10-13 10 views
19

Estoy utilizando ASP.NET MVC 2 Preview 2 y he escrito un método de extensión HtmlHelper personalizado para crear una etiqueta con una expresión. El modelo TM es de una clase simple con propiedades y las propiedades pueden tener atributos para definir los requisitos de validación. Estoy tratando de averiguar si existe un cierto atributo en la propiedad que la expresión representa en mi método de etiqueta.Obtener atributos personalizados de Expresión de propiedad Lambda

El código de la clase y la etiqueta es:

public class MyViewModel 
{ 
    [Required] 
    public string MyProperty { get; set; } 
} 

public static MvcHtmlString Label<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string label) 
{ 
    return MvcHtmlString.Create(string.Concat("<label for=\"", expression.GetInputName(), "\">", label, "</label>")); 
} 

public static string GetInputName<TModel, TProperty>(this Expression<Func<TModel, TProperty>> expression) 
{ 
    return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1); 
} 

Entonces yo llamaría la etiqueta de la siguiente manera:

Html.Label(x => x.MyProperty, "My Label") 

¿Hay una manera de averiguar si la propiedad en el valor de la expresión pasó al método de etiqueta tiene el atributo requerido?

Me di cuenta de que hacer lo siguiente me da el atributo si existe, pero tengo la esperanza de que hay una manera más clara de lograr esto.

public static MvcHtmlString Label<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string label) 
{ 
    System.Attribute.GetCustomAttribute(Expression.Property(Expression.Parameter(expression.Parameters[0].Type, expression.GetInputName()), expression.GetInputName()).Member, typeof(RequiredAttribute)) 

    return MvcHtmlString.Create(string.Concat("<label for=\"", expression.GetInputName(), "\">", label, "</label>")); 
} 

Respuesta

40

Su lógica de análisis de expresión podría necesitar algo de trabajo. En lugar de tratar con los tipos reales, se está convirtiendo a cadenas.

Aquí hay un conjunto de métodos de extensión que puede usar en su lugar. El primero recibe el nombre del miembro. El segundo/tercero se combinan para verificar si el atributo está en el miembro. GetAttribute devolverá el atributo solicitado o nulo, y el IsRequired solo verifica ese atributo específico.

public static class ExpressionHelpers 
{ 
    public static string MemberName<T, V>(this Expression<Func<T, V>> expression) 
    { 
     var memberExpression = expression.Body as MemberExpression; 
     if (memberExpression == null) 
      throw new InvalidOperationException("Expression must be a member expression"); 

     return memberExpression.Member.Name; 
    } 

    public static T GetAttribute<T>(this ICustomAttributeProvider provider) 
     where T : Attribute 
    { 
     var attributes = provider.GetCustomAttributes(typeof(T), true); 
     return attributes.Length > 0 ? attributes[0] as T : null; 
    } 

    public static bool IsRequired<T, V>(this Expression<Func<T, V>> expression) 
    { 
     var memberExpression = expression.Body as MemberExpression; 
     if (memberExpression == null) 
      throw new InvalidOperationException("Expression must be a member expression"); 

     return memberExpression.Member.GetAttribute<RequiredAttribute>() != null; 
    } 
} 

Espero que esto te ayude.

+0

Esto es mucho mejor, gracias! ¿Es posible cambiar GetAttribute para que sea un método de extensión de Expression? Eso permitiría verificar fácilmente cualquier expresión de un atributo. – Bernd

+0

+1 ¡Gran hombre código! Voy a mencionar esto en mi libro "ASP.NET MVC Cookbook" (http://groups.google.com/group/aspnet-mvc-2-cookbook-review) –

+0

Así que utilicé esta solución durante mucho tiempo, pero recientemente lo volvió a visitar cuando trabajaba con 'DbSet.Include' de EntityFramework, que no carga correctamente las propiedades anidadas (es decir,' Thing1.Thing2' of 'o => o.Thing1.Thing2'). Hay una [versión un poco más robusta] (http://stackoverflow.com/a/2916344/1037948) que tiene en cuenta 'UnaryExpression', pero la conversión de cadenas que sugirió evitar [parece ser la manera más fácil] (http : //stackoverflow.com/a/17220748/1037948) para obtener el nombre "totalmente calificado". – drzaus

6

¿Qué hay de este código (del proyecto de MVC en CodePlex)

public static bool IsRequired<T, V>(this Expression<Func<T, V>> expression, HtmlHelper<T> htmlHelper) 
    { 
     var modelMetadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); 
     string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression)); 
     FieldValidationMetadata fieldMetadata = ApplyFieldValidationMetadata(htmlHelper, modelMetadata, modelName); 
     foreach (var item in fieldMetadata.ValidationRules) 
     { 
      if (item.ValidationType == "required") 
       return true; 
     } 

     return false; 
    } 

    private static FieldValidationMetadata ApplyFieldValidationMetadata(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string modelName) 
    { 
     FormContext formContext = htmlHelper.ViewContext.FormContext; 
     FieldValidationMetadata fieldMetadata = formContext.GetValidationMetadataForField(modelName, true /* createIfNotFound */); 

     // write rules to context object 
     IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(modelMetadata, htmlHelper.ViewContext); 
     foreach (ModelClientValidationRule rule in validators.SelectMany(v => v.GetClientValidationRules())) 
     { 
      fieldMetadata.ValidationRules.Add(rule); 
     } 

     return fieldMetadata; 
    } 
+0

Realmente no entiendo este código, pero lo corté y pegué en mi HtmlHelperExtensionMethods y funcionó como está. :) La otra solución no funcionó porque uso MetadataType. – RitchieD

Cuestiones relacionadas