Html.Labelfor use DisplayName of object not property
asp.net-mvc-3, c#, razor
Solution
I just created a custom Html helper for label that does what you want:
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));
}
}
My example model:
public class Parent
{
public object Child { get; set; }
}
[DisplayName("yo")]
public class Child
{
public int Id { get; set; }
}
View:
@Html.LabelForCustom(m => m.Child) @*prints yo*@
Problem
Given a view model like this: ``` public class ParentViewModel { public object ChildViewModel { get; set; } } ``` If I use `Html.LabelFor` like this: ``` @Html.LabelFor(model => model.ChildViewModel) ``` I would get an output like this: ``` <label for="Model_ChildViewModel">ChildViewModel</label> ``` What I actually want though is for the generated label to use the `DisplayName` attribute applied to the object E.G. ``` [DisplayName("My Custom Label")] public class ChildViewModel { } ``` with an output of: ``` <label for="Model_ChildViewModel">My Custom Label</label> ``` I understand that the `Html.LabelFor` method takes an expression that expects a property and it will look for the `DisplayName` attribute on that property rather than the object itself. I have created an Html helper method to achieve what I want that looks like this: ``` 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()); } ``` Instead of taking an expression, my helper takes a `Func<TModel, object>` which returns the object that I want to check for the `DisplayName` attribute. The first problem I had was when I tried to call this method in razor like this: ``` @Html.CreateLabel(model => model.ChildObject) ``` I get the following error: ``` The type arguments for method 'CreateLabel<TModel>(this HtmlHelper, Func<TModel, object>) cannot be inferred from usage. Try specifying the arguments explicitly.' ``` So I call the method like this instead: ``` @{ Html.CreateLabel<ChildViewModel>(model => model.ChildObject); } ``` but nothing gets rendered. If I use the debugger to step through my helper method, the label tag is being generated but nothing is shown when my page is rendered. So my questions are: - How do I fix this to generate the label to my view? - What do I have to do so that the generic parameter can be inferred? - Is there any way to write the Html helper to do the same thing but using an expression? I have no experience using expressions so don't know where to start. Update I thought I'd post the final code as I made a few minor changes. First of all I took a look at the helpers in the MVC source code and decided to split the method into three separate methods in line with the provided examples. I also removed all the `TagBuilder` stuff as all I really needed was to generate the text that was to be injected between the `<legend></legend>` tags. The final code is below. Once again, thanks to Sylon for helping me out with this. ``` 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; } } ```