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

1

1

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)
Loire answered 21/7, 2017 at 9:57 Comment(0)
M
4

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;
    });
}
Mer answered 21/7, 2017 at 11:29 Comment(11)
is it possible to update the signature in order to make it work with a collection? like public static string DisplayShortNameFor<TModelItem, TResult>( this IHtmlHelper<IEnumerable<TModelItem>> htmlHelper, Expression<Func<TModelItem, TResult>> expression) , because as described in OP the view will not digest the collectionLoire
You don't need one, you can just call it in the view like this: @Html.ShortNameFor(m => m.First().SomeProperty)Mer
yes, but where there are no items I will get errors... and this a one more call t the collection, I mean, actual ASP.NET Code is with Collection inside, not a memberLoire
No you won't get any errors, try it out. It's based on expressions not an executed Func.Mer
ok, just because the signature I proposed with collection is actually in the DispayNameFor MS Core codeLoire
I tried to modify your MetaDataFor in private static IHtmlContent MetaDataFor<TModel, TValue>(this IHtmlHelper<IEnumerable<TModel>> html, Expression<Func<TModel, TValue>> expression, Func<ModelMetadata, string> property) but does not compile because of FromLambdaExpressionLoire
Well it's a lot more complicated to do it for the IEnumerable case, I'll leave that as an exercise for you because the option above works perfectly well.Mer
The problem I discovered with this solution is that it does not support the localization... if I have the Models.MyModel.en.resx it doesn't care about...Loire
With Core 3.0 the ExpressionMetadataProvider is no longer available as a static class (it's there but internal only) instead the following can be used: var expressionMetadataHelper = (ModelExpressionProvider)html.ViewContext.HttpContext.RequestServices.GetService(typeof(ModelExpressionProvider)); var modelExplorer = expressionMetadataHelper.CreateModelExpression(html.ViewData, expression);Capuche
@Capuche Tak a look at https://mcmap.net/q/1330528/-asp-net-core-3-0-shortname-in-the-display-attribute-dataannotations/3074765 .Manzano
Is there a way to set Name property of the Display attribute to automatically the proper itself? [Display] public string Email { get; set; } So location key is automatically "Email" ?Caneghem

© 2022 - 2024 — McMap. All rights reserved.