2009-10-13 15 views
19

Używam programu ASP.NET MVC 2 Preview 2 i napisałem niestandardową metodę rozszerzenia HtmlHelper, aby utworzyć etykietę za pomocą wyrażenia. TModel pochodzi z prostej klasy z właściwościami, a właściwości mogą mieć atrybuty określające wymagania walidacji. Próbuję dowiedzieć się, czy istnieje atrybut na właściwość wyrażenie reprezentuje w mojej metodzie etykiety.Uzyskaj niestandardowe atrybuty z Lambda Property Expression

Kod dla klasy i etykiety:

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

Następnie nazwałbym etykietę tak:

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

Czy istnieje sposób, aby dowiedzieć się, czy majątek wartości ekspresji przekazane do metody Etykieta ma atrybut Wymagany?

Uznałem, że wykonanie poniższych czynności daje mi atrybut, jeśli istnieje, ale mam nadzieję, że istnieje lepszy sposób osiągnięcia tego.

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

Odpowiedz

40

Twoja logika parsowania wyrażeń może wykorzystać trochę pracy. Zamiast zajmować się rzeczywistymi typami, konwertujesz na ciągi.

Oto zestaw metod rozszerzeń, których można użyć zamiast tego. Pierwszy dostaje imię członka. Drugi/trzeci łącznik, aby sprawdzić, czy atrybut jest na elemencie. GetAttribute zwróci żądany atrybut lub wartość null, a IsRequired sprawdza tylko ten konkretny atrybut.

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

Mam nadzieję, że to ci pomoże.

+0

Jest to o wiele lepiej, dzięki! Czy można zmienić metodę GetAttribute na metodę rozszerzenia wyrażenia? Umożliwiłoby to łatwe sprawdzenie dowolnego wyrażenia dla atrybutu. – Bernd

+0

+1 Świetny człowiek kodu! Wspomnę o tym w mojej książce "Książka kucharska ASP.NET MVC" (http://groups.google.com/group/aspnet-mvc-2-cookbook-review) –

+0

Użyłem tego rozwiązania przez długi czas, ale ostatnio powtórzył go podczas pracy z EntityFramework w 'DbSet.Include', który nie może poprawnie załadować zagnieżdżonych właściwości (np.' Thing1.Thing2' z 'o => o.Thing1.Thing2'). Istnieje [nieco bardziej rozbudowana wersja] (http://stackoverflow.com/a/2916344/1037948) twojego, która bierze pod uwagę 'UnaryExpression', ale konwersja ciągów, którą sugerujesz, unikając [wydaje się być najłatwiejszą metodą] (http : //stackoverflow.com/a/17220748/1037948), aby uzyskać nazwę "w pełni kwalifikowaną". – drzaus

6

Jak o tym kodzie (od projektu MVC na 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

Naprawdę nie rozumiem tego kodu, ale wyciąłem go i wkleiłem w moim HtmlHelperExtensionMethods i działało jak jest. :) Inne rozwiązanie nie działa dla mnie, ponieważ używam MetadataType. – RitchieD

Powiązane problemy