4

This question was asked in the past and the answer here https://stackoverflow.com/a/45236544/3074765 has been a life saver for me. The problem is that when I migrated to .Net Core 3.0, it broke. The reason can be found here https://github.com/aspnet/AspNetCore/issues/8678 . In a nutshell, Microsoft had made some functions public that were in a class that was labeled internal. Fixing this removed access to ExpressionMetadataProvider. Consider the following HTML Helper

using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;

public static class MvcHtmlHelpers
{
    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));
    }

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

    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 ?? m.DisplayName ?? m.Name;
                }
            }
            //Return a default value if the property doesn't have a DisplayAttribute
            return m.DisplayName ?? m.Name;
        });
    }
}

When you follow the steps to migrate to .Net Core 3.0, ExpressionMetadataProvider and ExpressionHelper can no longer be resolved.

Jim Wilcox
  • 1,480
  • 16
  • 33

1 Answers1

8

In .Net Core 3.0, Microsoft provided a way to get to the same MetaData using Dependecy Injection and a service called ModelExpressionProvider. You can get access to the service via the HttpContext found in the IHtmlHelper. Just replace the using to be:

using Microsoft.AspNetCore.Mvc.ViewFeatures;

Which removes the .Internal, and replace the MetaDataFor function with:

    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));

        ModelExpressionProvider modelExpressionProvider = (ModelExpressionProvider)html.ViewContext.HttpContext.RequestServices.GetService(typeof(IModelExpressionProvider));

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

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

The other two extension functions will work fine once MetaDataFor is fixed to retrieve what they need.

Cheers!

Jim Wilcox
  • 1,480
  • 16
  • 33
  • 1
    How do you call the function MetaDataFor from an Edit.cshtml file in Razor ASP.NET Core 3 to get the attribute value for "ShortName"? – Ash Machine Mar 31 '20 at 23:25
  • I generally use it in the Index.cshtml but I am pretty sure it should work in the Edit.cshtml. The syntax I use is @Html.ShortNameFor(model => model.MyDataModel[0].MyField) . Keep in mind that this is a column header. You should be able to use it anywhere you use DisplayNameFor. – Jim Wilcox Apr 01 '20 at 12:34
  • That gives me the error IHtmlHelper does not contain a definition for ShortNameFor ..... – Ash Machine Apr 07 '20 at 23:52
  • 1
    @AshMachine I used a class in the namespace called Extensions to create my extension methods. In the top of my .cshtml, I need to have "@using Extensions". You will need to add a using for your extension methods. – Jim Wilcox Apr 08 '20 at 15:33