2013-04-12 1 views
5

Dato un modello di vista come questo:Html.Labelfor utilizzare DisplayName dell'oggetto non proprietà

public class ParentViewModel 
{ 
    public object ChildViewModel { get; set; } 
} 

se uso Html.LabelFor come questo:

@Html.LabelFor(model => model.ChildViewModel) 

Vorrei avere un output simile a questo:

<label for="Model_ChildViewModel">ChildViewModel</label> 

Ciò che in realtà desidero è che l'etichetta generata utilizzi l'attributo DisplayName applicato a l'oggetto E.G.

[DisplayName("My Custom Label")] 
public class ChildViewModel 
{ 
} 

con una potenza di:

<label for="Model_ChildViewModel">My Custom Label</label> 

Sono consapevole che il metodo Html.LabelFor prende un'espressione che prevede una proprietà e cercherà l'attributo DisplayName su tale proprietà piuttosto che l'oggetto stesso.

ho creato un metodo di supporto Html per ottenere ciò che voglio che assomiglia a questo:

public static IHtmlString CreateLabel<TModel>(this HtmlHelper html, Func<TModel, object> func) 
    where TModel : class 
    { 
     TagBuilder tb = new TagBuilder("label"); 

     var model = html.ViewData.Model as TModel; 
     if (model != null) 
     { 
      object obj = func(model); 
      if (obj != null) 
      { 
       var attribute = obj.GetType().GetCustomAttributes(
        typeof(DisplayNameAttribute), true) 
        .FirstOrDefault() as DisplayNameAttribute; 

       if (attribute != null) 
       { 
        tb.InnerHtml = attribute.DisplayName; 
        return MvcHtmlString.Create(tb.ToString()); 
       } 
       else 
       { 
        tb.InnerHtml = obj.ToString(); 
        return MvcHtmlString.Create(tb.ToString()); 
       } 
      } 
     } 

     tb.InnerHtml = html.ViewData.Model.ToString(); 
     return MvcHtmlString.Create(tb.ToString()); 
    } 

Invece di prendere un'espressione, il mio aiuto prende un Func<TModel, object> che restituisce l'oggetto che voglio per verificare la Attributo DisplayName.

Il primo problema che ho avuto è stato quando ho provato a chiamare questo metodo in rasoio in questo modo:

@Html.CreateLabel(model => model.ChildObject) 

ottengo il seguente errore:

The type arguments for method 'CreateLabel<TModel>(this HtmlHelper, 
Func<TModel, object>) cannot be inferred from usage. Try specifying 
the arguments explicitly.' 

Così chiamo il metodo come questo, invece :

@{ Html.CreateLabel<ChildViewModel>(model => model.ChildObject); } 

ma non viene eseguito il rendering. Se utilizzo il debugger per passare attraverso il mio metodo helper, viene generato il tag label ma non viene mostrato nulla quando viene eseguito il rendering della mia pagina.

Quindi le mie domande sono:

  • Come posso risolvere questo problema per generare l'etichetta per mio punto di vista?
  • Cosa devo fare in modo che il parametro generico possa essere dedotto?
  • C'è un modo per scrivere l'helper Html per fare la stessa cosa ma usando un'espressione? Non ho esperienza nell'uso delle espressioni quindi non so da dove cominciare.

Aggiornamento

ho pensato di pubblicare il codice finale come ho fatto alcune piccole modifiche. Prima di tutto ho dato un'occhiata agli helper nel codice sorgente MVC e ho deciso di suddividere il metodo in tre metodi separati in linea con gli esempi forniti. Ho rimosso anche tutto il materiale TagBuilder poiché tutto ciò di cui avevo veramente bisogno era di generare il testo da iniettare tra i tag <legend></legend>. Il codice finale è sotto. Ancora una volta, grazie a Sylon per avermi aiutato in questo.

public static IHtmlString LegendTextFor<TModel, TObject>(this HtmlHelper<TModel> html, Expression<Func<TModel, TObject>> expression) 
{ 
    return LegendTextHelper(html, 
     ModelMetadata.FromLambdaExpression(expression, html.ViewData), 
     ExpressionHelper.GetExpressionText(expression), 
     expression.Compile().Invoke(html.ViewData.Model)); 
} 

private static IHtmlString LegendTextHelper<TModel, TObject>(this HtmlHelper<TModel> html, ModelMetadata metadata, string htmlFieldName, TObject value) 
{ 
    string resolvedLabelText = metadata.DisplayName ?? value.GetDisplayName() ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last(); 

    if (String.IsNullOrEmpty(resolvedLabelText)) 
     return MvcHtmlString.Empty; 

    return MvcHtmlString.Create(resolvedLabelText); 
} 

private static string GetDisplayName<T>(this T obj) 
{ 
    if (obj != null) 
    { 
     var attribute = obj.GetType() 
      .GetCustomAttributes(typeof(DisplayNameAttribute), false) 
      .Cast<DisplayNameAttribute>() 
      .FirstOrDefault(); 

     return attribute != null ? attribute.DisplayName : null; 
    } 
    else 
    { 
     return null; 
    } 
} 

risposta

2

Ho appena creato un aiutante Html personalizzato per l'etichetta che fa quello che si vuole:

Html Helper:

public static class HtmlHelperExtensions 
{ 

    public static MvcHtmlString LabelForCustom<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) 
    { 
     ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); 

     string customDisplayName = null; 

     var value = expression.Compile().Invoke(html.ViewData.Model); 

     if (value != null) 
     { 
      var attribute = value.GetType().GetCustomAttributes(typeof(DisplayNameAttribute), false) 
      .Cast<DisplayNameAttribute>() 
      .FirstOrDefault(); 

      customDisplayName = attribute != null ? attribute.DisplayName : null; 
     } 

     string htmlFieldName = ExpressionHelper.GetExpressionText(expression); 
     string labelText = metadata.DisplayName ?? customDisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last(); 
     if (String.IsNullOrEmpty(labelText)) 
     { 
      return MvcHtmlString.Empty; 
     } 

     TagBuilder tag = new TagBuilder("label"); 
     tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName)); 
     tag.SetInnerText(labelText); 
     return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal)); 
    } 
} 

Il mio esempio modello:

public class Parent 
{ 
    public object Child { get; set; } 
} 

[DisplayName("yo")] 
public class Child 
{ 
    public int Id { get; set; } 
} 

Vista:

@Html.LabelForCustom(m => m.Child) @*prints yo*@ 
+0

Grazie, funziona magnificamente. Sapevo che dovevo ottenere l'oggetto reale dall'espressione, ma non ero sicuro di come farlo, quindi questa riga 'expression.Compile(). Invoke (html.ViewData.Model)' è particolarmente utile. –