How do I display the DisplayAttribute.Description attribute value?

C#asp.netasp.net Mvc

C# Problem Overview


I have a model class, with a property like this:

[Display(Name = "Phone", Description="Hello World!")]
public string Phone1 { get; set; }

Displaying a label and rendering a textbox for input in my view is pretty easy:

@Html.LabelFor(model => model.Organization.Phone1)
@Html.EditorFor(model => model.Organization.Phone1)
@Html.ValidationMessageFor(model => model.Organization.Phone1)

But how do I render the value of the Description annotation attribute, i.e. "Hello World!"??

C# Solutions


Solution 1 - C#

I ended up with a helper like this:

using System;
using System.Linq.Expressions;
using System.Web.Mvc;

public static class MvcHtmlHelpers
{
    public static MvcHtmlString DescriptionFor<TModel, TValue>(this HtmlHelper<TModel> self, Expression<Func<TModel, TValue>> expression)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, self.ViewData);
        var description = metadata.Description;

        return MvcHtmlString.Create(string.Format(@"<span>{0}</span>", description));
    }
}

Thanks to those who led me in the right direction. :)

Solution 2 - C#

Using the technique from this article about how to Display visual hints for the fields in your form, you can access the value via the following:

@Html.TextBoxFor( 
        model => model.Email , 
        new { title = ModelMetadata.FromLambdaExpression<RegisterModel , string>( 
            model => model.Email , ViewData ).Description } )  

Solution 3 - C#

I was about to use the accepted answer, but it didn't work for ASP.NET Core 1/2 (a.k.a. MVC 6) because ModelMetadata.FromLambdaExpression no longer exists and has been moved to ExpressionMetadataProvider (also usage has been changed slightly).

This is an updated extension method you can use with ASP.NET Core 1.1 & 2:

using System;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;

public static class HtmlExtensions
{
    public static IHtmlContent DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
    {
        if (html == null) throw new ArgumentNullException(nameof(html));
        if (expression == null) throw new ArgumentNullException(nameof(expression));

        var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, html.ViewData, html.MetadataProvider);
        if (modelExplorer == null) throw new InvalidOperationException($"Failed to get model explorer for {ExpressionHelper.GetExpressionText(expression)}");

        return new HtmlString(modelExplorer.Metadata.Description);
    }
}

ASP.NET Core 1

For ASP.NET Core 1, the same code works, but you'll need different namespace usings:

using System;
using System.Linq.Expressions;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ViewFeatures;

Usage

@Html.DescriptionFor(model => model.Phone1)

Solution 4 - C#

In ASP.NET MVC Core you can use the new Tag Helpers, that makes your HTML look like... HTML :)

Like this:

<div class="form-group row">
    <label asp-for="Name" class="col-md-2 form-control-label"></label>
    <div class="col-md-10">
        <input asp-for="Name" class="form-control" aria-describedby="Name-description" />
        <span asp-description-for="Name" class="form-text text-muted" />
        <span asp-validation-for="Name" class="text-danger" />
    </div>
</div>

Note 1: You can use the aria-describedby attribute in the input element as that id will be created automatically in the span element with asp-description-for attribute.

Note 2: In Bootstrap 4, the classes form-text and text-muted replaces the v3 help-block class for block-level help text.

For this magic to happen, you just need to create a new Tag Helper:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;span&gt; elements with an <c>asp-description-for</c> attribute.
/// Adds an <c>id</c> attribute and sets the content of the &lt;span&gt; with the Description property from the model data annotation DisplayAttribute.
/// </summary>
[HtmlTargetElement("span", Attributes = DescriptionForAttributeName)]
public class SpanDescriptionTagHelper : TagHelper
{
    private const string DescriptionForAttributeName = "asp-description-for";

    /// <summary>
    /// Creates a new <see cref="SpanDescriptionTagHelper"/>.
    /// </summary>
    /// <param name="generator">The <see cref="IHtmlGenerator"/>.</param>
    public SpanDescriptionTagHelper(IHtmlGenerator generator)
    {
        Generator = generator;
    }

    /// <inheritdoc />
    public override int Order
    {
        get
        {
            return -1000;
        }
    }

    [HtmlAttributeNotBound]
    [ViewContext]
    public ViewContext ViewContext { get; set; }

    protected IHtmlGenerator Generator { get; }

    /// <summary>
    /// An expression to be evaluated against the current model.
    /// </summary>
    [HtmlAttributeName(DescriptionForAttributeName)]
    public ModelExpression DescriptionFor { get; set; }

    /// <inheritdoc />
    /// <remarks>Does nothing if <see cref="DescriptionFor"/> is <c>null</c>.</remarks>
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (output == null)
        {
            throw new ArgumentNullException(nameof(output));
        }

        var metadata = DescriptionFor.Metadata;

        if (metadata == null)
        {
            throw new InvalidOperationException(string.Format("No provided metadata ({0})", DescriptionForAttributeName));
        }

        output.Attributes.SetAttribute("id", metadata.PropertyName + "-description");

        if( !string.IsNullOrWhiteSpace( metadata.Description))
        {
            output.Content.SetContent(metadata.Description);
            output.TagMode = TagMode.StartTagAndEndTag;
        }
    }
}

And make your Tag Helpers available to all our Razor views. Add the addTagHelper directive to the Views/_ViewImports.cshtml file:

@addTagHelper "*, YourAssemblyName"

Note 1: Replace YourAssemblyName with the assembly name of your project.

Note 2: You just need to do this once, for all your Tag Helpers!

More information on Tag Helpers here: https://docs.asp.net/en/latest/mvc/views/tag-helpers/intro.html

That’s it! Have fun with the new Tag Helpers!

Solution 5 - C#

If anyone is wondering how to use the accepted answer

1- In your solution explorer > Add new folder > name it"Helpers" for example
2- Add a new class, name it "CustomHtmlHelpers" for example
3- Paste the code :

public static class MvcHtmlHelpers
{
    public static string DescriptionFor<TModel, TValue>(this HtmlHelper<TModel> self, Expression<Func<TModel, TValue>> expression)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, self.ViewData);
        var description = metadata.Description;

        return string.IsNullOrWhiteSpace(description) ? "" : description;
    }
}

4- In your model or viewModel using it this:

[Display(Name = "User Name", Description = "Enter your User Name")]
public string FullName { get; set; }

5- In your Razor view, after the @model, type this line

@using YOUR_PROJECT.Helpers	

6- Display the description like this:

@Html.DescriptionFor(m => m.FullName) 

7- You may want to use the description to display text in the input placeholder:

@Html.DisplayNameFor(m => m.FullName)
@Html.TextBoxFor(m => m.FullName, new { @class = "form-control", placeholder = Html.DescriptionFor(m => m.FullName) })

Thanks

Solution 6 - C#

var attrib = (DisplayAttribute)Attribute.GetCustomAttribute(
             member, typeof(DisplayAttribute));
var desc = attrib == null ? "" : attrib.GetDescription()

Solution 7 - C#

Here is an updated version for ASP.NET Core 3.1 and 5:

public static class HtmlExtensions
{
    public static IHtmlContent DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
    {
        if (html == null) throw new ArgumentNullException(nameof(html));
        if (expression == null) throw new ArgumentNullException(nameof(expression));

        var expressionProvider = html.ViewContext?.HttpContext?.RequestServices?.GetService<ModelExpressionProvider>()
            ?? new ModelExpressionProvider(html.MetadataProvider);
        var modelExpression = expressionProvider.CreateModelExpression(html.ViewData, expression);

        return new HtmlString(modelExpression.Metadata.Description);
    }
}

We have to go via ModelExpressionProvider now that ExpressionMetadataProvider is marked internal.

ModelExpressionProvider.CreateModelExpression() calls ExpressionMetadataProvider.FromLambdaExpression() internally anyway:

https://github.com/aspnet/Mvc/blob/04ce6cae44fb0cb11470c21769d41e3f8088e8aa/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelExpressionProvider.cs#L42

Solution 8 - C#

@ViewData.ModelMetadata.Properties
   .Where(m => m.PropertyName == "Phone1").FirstOrDefault().Description

So, if you were using bootstrap, something like

<div class="form-group col-sm-6">
   @Html.LabelFor(m => m.Organization.Phone1)
   @Html.EditorFor(m => m.Organization.Phone1)
   <p class="help-block">
      @ViewData.ModelMetadata.Properties
         .Where(m => m.PropertyName == "DayCount").FirstOrDefault().Description
   </p>
</div>

Solution 9 - C#

You would have to write a custom helper that would reflect on your model to give the Description attribute value.

Solution 10 - C#

...and if you prefer to have the description as a tooltip in the form label, add a Tag Helper like this:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;label&gt; elements with an <c>asp-for</c> attribute.
/// Adds a <c>title</c> attribute to the &lt;label&gt; with the Description property from the model data annotation DisplayAttribute.
/// </summary>
[HtmlTargetElement("label", Attributes = ForAttributeName)]
public class LabelTitleTagHelper : TagHelper
{
    private const string ForAttributeName = "asp-for";

    /// <summary>
    /// Creates a new <see cref="LabelTitleTagHelper"/>.
    /// </summary>
    /// <param name="generator">The <see cref="IHtmlGenerator"/>.</param>
    public LabelTitleTagHelper(IHtmlGenerator generator)
    {
        Generator = generator;
    }

    /// <inheritdoc />
    public override int Order
    {
        get
        {
            return -1000;
        }
    }

    [HtmlAttributeNotBound]
    [ViewContext]
    public ViewContext ViewContext { get; set; }

    protected IHtmlGenerator Generator { get; }

    /// <summary>
    /// An expression to be evaluated against the current model.
    /// </summary>
    [HtmlAttributeName(ForAttributeName)]
    public ModelExpression TitleFor { get; set; }

    /// <inheritdoc />
    /// <remarks>Does nothing if <see cref="TitleFor"/> is <c>null</c>.</remarks>
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (output == null)
        {
            throw new ArgumentNullException(nameof(output));
        }

        var metadata = TitleFor.Metadata;

        if (metadata == null)
        {
            throw new InvalidOperationException(string.Format("No provided metadata ({0})", ForAttributeName));
        }

        if (!string.IsNullOrWhiteSpace(metadata.Description))
            output.Attributes.SetAttribute("title", metadata.Description);
    }
}

That will create a new title attribute with the Description property from the model's data annotation DisplayAttribute.

The beautiful part is that you don't need to touch your generated scaffolded views! Because this Tag Helper is targeting the asp-for attribute of the label element that is already there!

Solution 11 - C#

In addition to Jakob Gade'a great answer:

If you need to Support a DescriptionAttribute instead of a DisplayAttribute, his great solution still works if we override the MetadataProvider:

public class ExtendedModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<System.Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        //Possible Multiple Enumerations on IEnumerable fix
        var attributeList = attributes as IList<System.Attribute> ?? attributes.ToList();

        //Default behavior
        var data = base.CreateMetadata(attributeList, containerType, modelAccessor, modelType, propertyName);

        //Bind DescriptionAttribute
        var description = attributeList.SingleOrDefault(a => typeof(DescriptionAttribute) == a.GetType());
        if (description != null)
        {
            data.Description = ((DescriptionAttribute)description).Description;
        }

        return data;
    }
}

This need to be registeres in the Application_Start Method in Global.asax.cs:

ModelMetadataProviders.Current = new ExtendedModelMetadataProvider();

Solution 12 - C#

HANDL's answer, updated for ASP.NET Core 2.0

using System;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;

public static class HtmlExtensions
{
    public static IHtmlContent DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
    {
        if (html == null) throw new ArgumentNullException(nameof(html));
        if (expression == null) throw new ArgumentNullException(nameof(expression));

        var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, html.ViewData, html.MetadataProvider);
        if (modelExplorer == null) throw new InvalidOperationException($"Failed to get model explorer for {ExpressionHelper.GetExpressionText(expression)}");

        return new HtmlString(modelExplorer.Metadata.Description);
    }
}

Solution 13 - C#

You can always create your own custom extension like this:

    public static MvcHtmlString ToolTipLabel (string resourceKey, string text, bool isRequired, string labelFor = "", string labelId = "",string className="")
    {
        string tooltip = string.Empty;

        StringBuilder sb = new StringBuilder();

        if (!string.IsNullOrEmpty(resourceKey))
        {
            var resources = GetAllResourceValues();

            if (resources.ContainsKey(resourceKey))
            {
                tooltip = resources[resourceKey].Value;
            }
        }

        sb.Append("<label");

        if (!string.IsNullOrEmpty(labelFor))
        {
            sb.AppendFormat(" for=\"{0}\"", labelFor);
        }

        if (!string.IsNullOrEmpty(labelId))
        {
            sb.AppendFormat(" Id=\"{0}\"", labelId);
        }

        if (!string.IsNullOrEmpty(className))
        {
            sb.AppendFormat(" class=\"{0}\"", className);
        }

        if (!string.IsNullOrEmpty(tooltip))
        {

            sb.AppendFormat(" data-toggle='tooltip' data-placement='auto left' title=\"{0}\"",tooltip);

        }
        if (isRequired)
        {
            sb.AppendFormat("><em class='required'>*</em> {0} </label></br>", text);
        }
        else
        {
            sb.AppendFormat(">{0}</label></br>", text);
        }
        return MvcHtmlString.Create(sb.ToString());
    }

and can get it in view like this:

> @HtmlExtension.ToolTipLabel(" "," ",true," "," "," ")

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJakob GadeView Question on Stackoverflow
Solution 1 - C#Jakob GadeView Answer on Stackoverflow
Solution 2 - C#Adam TuliperView Answer on Stackoverflow
Solution 3 - C#huysentruitwView Answer on Stackoverflow
Solution 4 - C#Filipe CarneiroView Answer on Stackoverflow
Solution 5 - C#Adel MouradView Answer on Stackoverflow
Solution 6 - C#Marc GravellView Answer on Stackoverflow
Solution 7 - C#RyanView Answer on Stackoverflow
Solution 8 - C#MikeTView Answer on Stackoverflow
Solution 9 - C#IlluminatiView Answer on Stackoverflow
Solution 10 - C#Filipe CarneiroView Answer on Stackoverflow
Solution 11 - C#Christian GollhardtView Answer on Stackoverflow
Solution 12 - C#Randy GamageView Answer on Stackoverflow
Solution 13 - C#KaranView Answer on Stackoverflow