2009-10-13 7 views
19

J'utilise ASP.NET MVC 2 Preview 2 et j'ai écrit une méthode d'extension HtmlHelper personnalisée pour créer une étiquette à l'aide d'une expression. Le TModel provient d'une classe simple avec des propriétés et les propriétés peuvent avoir des attributs pour définir les exigences de validation. J'essaye de découvrir si un certain attribut existe sur la propriété que l'expression représente dans ma méthode d'étiquette.Obtenir des attributs personnalisés à partir de l'expression de propriété Lambda

Le code de la classe et l'étiquette est:

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

Ensuite, je qualifierais l'étiquette comme ceci:

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

est-il un moyen de savoir si la propriété de la valeur d'expression passé à la méthode Label a l'attribut Required? Je me suis rendu compte que faire ce qui suit m'obtient l'attribut s'il existe, mais j'espère qu'il y a une manière plus propre d'accomplir ceci.

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

Répondre

40

Votre logique d'analyse d'expression pourrait utiliser un peu de travail. Plutôt que de traiter les types réels, vous convertissez en chaînes.

Voici un ensemble de méthodes d'extension que vous pouvez utiliser à la place. Le premier obtient le nom du membre. La deuxième/troisième combinaison pour vérifier si l'attribut est sur le membre. GetAttribute renvoie l'attribut demandé ou null, et le IsRequired vérifie juste pour cet attribut spécifique.

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

Espérons que cela vous aide.

+0

C'est beaucoup mieux, merci! Est-il possible de changer GetAttribute pour être une méthode d'extension de l'expression? Cela permettrait de vérifier facilement toute expression pour un attribut. – Bernd

+0

+1 Super code homme! Je vais le mentionner dans mon livre "ASP.NET MVC Cookbook" (http://groups.google.com/group/aspnet-mvc-2-cookbook-review) –

+0

J'ai donc utilisé cette solution pendant longtemps, mais récemment revisité quand on travaille avec 'DbSet.Include' de EntityFramework, qui ne parvient pas à charger correctement les propriétés imbriquées (c'est-à-dire' Thing1.Thing2' de 'o => o.Thing1.Thing2'). Il y a une [version légèrement plus robuste] (http://stackoverflow.com/a/2916344/1037948) de la vôtre qui prend en compte 'UnaryExpression', mais la conversion de chaîne que vous avez suggéré d'éviter [semble être le moyen le plus simple] (http : //stackoverflow.com/a/17220748/1037948) pour obtenir le nom "complet". – drzaus

6

Que diriez-vous de ce code (du projet MVC sur 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

Je ne comprends pas vraiment ce code, mais je l'ai coupé et collé dans mes HtmlHelperExtensionMethods et cela a fonctionné tel quel. :) L'autre solution n'a pas fonctionné pour moi parce que j'utilise MetadataType. – RitchieD

Questions connexes