serge
serge

Reputation: 15229

ASP.NET Core: ShortName in the Display attribute (DataAnnotations)

In an ASP .NET Core 1.1 project (VS 2017) I try to use the ShortName attrubute of the Display property in order to use the DisplayFor HTML Helper:

[Display(Name="Project Name", ShortName="Name", Description="The name of the project")]
public string Name { get; set; }

I read the following answer that does the trick for the Description. Unfortunately for a reason I don't understand, this doesn't work for the ShortName.

There is the code I tried, the first method seems OK, but the second does not compile, so I would like to fix it:

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

namespace MyProject.Helpers
{
    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)}");
            //////// Description is OK 
            return new HtmlString(modelExplorer.Metadata.Description);
        }

        public static IHtmlContent ShortNameFor<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., html.MetadataProvider);
            if (modelExplorer == null) throw new InvalidOperationException($"Failed to get model explorer for {ExpressionHelper.GetExpressionText(expression)}");
            //////// ShortName DOES NOT EXIST !!!!!!!!!!!!!!!!
            return new HtmlString(modelExplorer.Metadata.ShortName);
        }
    }
}

More that than, reviewing the MS code of the DisplayNameFor

the signature of the method should change for something like this:

public static string DisplayShortNameFor<TModelItem, TResult>(
    this IHtmlHelper<IEnumerable<TModelItem>> htmlHelper,
    Expression<Func<TModelItem, TResult>> expression)    

and not

public static IHtmlContent ShortNameFor<TModel, TValue>(
    this IHtmlHelper<TModel> html, 
    Expression<Func<TModel, TValue>> expression)

Update

For the old signature I tried

public static string DisplayShortNameFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
    string shortNameValue = string.Empty;
    var prop = expression.Body as MemberExpression;
    if (prop != null)
    {
        var DisplayAttrib = prop.Member.GetCustomAttributes<DisplayAttribute>(false).FirstOrDefault();
        if (DisplayAttrib != null)
            shortNameValue = DisplayAttrib.ShortName;
    }
    return shortNameValue;
}

but actually I can't run it because does not compile in the View, because is a IEnumerable

@using MyProject.Helpers
@model IEnumerable<MyProject.Models.Record> <!--<<< IEnumerable to display a collection -->

@Html.DisplayShortNameFor(model => model.Name)

So I need to do

// for my method shortname I need to use FirstOfDefault...
@Html.DisplayShortNameFor(model => model.FirstOrDefault().Name)

// but for ASP.NET DisplayName works
@Html.DisplayNameFor(model => model.Date)

Upvotes: 1

Views: 3377

Answers (1)

DavidG
DavidG

Reputation: 118947

To get the ShortName property using this method, you need to extract the Display attribute manually because it's not part of the default metadata. For example, something like this will work:

var defaultMetadata = m as 
    Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata;
if(defaultMetadata != null)
{
    var displayAttribute = defaultMetadata.Attributes.Attributes
        .OfType<DisplayAttribute>()
        .FirstOrDefault();
    if(displayAttribute != null)
    {
        return displayAttribute.ShortName;
    }
}
return m.DisplayName;

To plug that into your helpers, I would abstract away the method slightly as there's some duplicate code in there, so you would end up with a private method like this:

private static IHtmlContent MetaDataFor<TModel, TValue>(this IHtmlHelper<TModel> html, 
    Expression<Func<TModel, TValue>> expression,
    Func<ModelMetadata, string> property)
{
    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(property(modelExplorer.Metadata));
}

And your two public methods like this:

public static IHtmlContent DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
    return html.MetaDataFor(expression, m => m.Description);
}

public static IHtmlContent ShortNameFor<TModel, TValue>(this IHtmlHelper<TModel> html, 
    Expression<Func<TModel, TValue>> expression)
{
    return html.MetaDataFor(expression, m => 
    {
        var defaultMetadata = m as 
            Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata;
        if(defaultMetadata != null)
        {
            var displayAttribute = defaultMetadata.Attributes.Attributes
                .OfType<DisplayAttribute>()
                .FirstOrDefault();
            if(displayAttribute != null)
            {
                return displayAttribute.ShortName;
            }
        }
        //Return a default value if the property doesn't have a DisplayAttribute
        return m.DisplayName;
    });
}

Upvotes: 4

Related Questions