Popular blog tags

create HtmlExtensions.cs 

src\Presentation\Nop.Web.Framework\HtmlExtensions.cs

add class ExpressionMetadataProvider

namespace Microsoft.AspNetCore.Mvc.Rendering
{

    /// <summary>
    /// ExpressionMetadataProvider找不到,加此类 come from:grandnode/Grand.Framework/TagHelpers/Admin/Extend/ExpressionMetadataProvider.cs
    /// </summary>
    internal static class ExpressionMetadataProvider
    {
        public static ModelExplorer FromLambdaExpression<TModel, TResult>(
            Expression<Func<TModel, TResult>> expression,
            ViewDataDictionary<TModel> viewData,
            IModelMetadataProvider metadataProvider)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }

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

            string propertyName = null;
            Type containerType = null;
            var legalExpression = false;

            // Need to verify the expression is valid; it needs to at least end in something
            // that we can convert to a meaningful string for model binding purposes

            switch (expression.Body.NodeType)
            {
                case ExpressionType.ArrayIndex:
                    // ArrayIndex always means a single-dimensional indexer;
                    // multi-dimensional indexer is a method call to Get().
                    legalExpression = true;
                    break;

                case ExpressionType.MemberAccess:
                    // Property/field access is always legal
                    var memberExpression = (MemberExpression)expression.Body;
                    propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
                    if (string.Equals(propertyName, "Model", StringComparison.Ordinal) &&
                        memberExpression.Type == typeof(TModel) &&
                        memberExpression.Expression.NodeType == ExpressionType.Constant)
                    {
                        // Special case the Model property in RazorPage<TModel>. (m => Model) should behave identically
                        // to (m => m). But do the more complicated thing for (m => m.Model) since that is a slightly
                        // different beast.)
                        return FromModel(viewData, metadataProvider);
                    }

                    // memberExpression.Expression can be null when this is a static field or property.
                    //
                    // This can be the case if the expression is like (m => Person.Name) where Name is a static field
                    // or property on the Person type.
                    containerType = memberExpression.Expression?.Type;

                    legalExpression = true;
                    break;

                case ExpressionType.Parameter:
                    // Parameter expression means "model => model", so we delegate to FromModel
                    return FromModel(viewData, metadataProvider);
            }

            if (!legalExpression)
            {
                throw new InvalidOperationException("TemplateLimitations");
            }

            object modelAccessor(object container)
            {
                var model = (TModel)container;
                var func = expression.Compile();
                try
                {
                    return func(model);
                }
                catch (NullReferenceException)
                {
                    return null;
                }
            }

            ModelMetadata metadata = null;
            if (containerType != null && propertyName != null)
            {
                // Ex:
                //    m => m.Color (simple property access)
                //    m => m.Color.Red (nested property access)
                //    m => m.Widgets[0].Size (expression ending with property-access)
                metadata = metadataProvider.GetMetadataForType(containerType).Properties[propertyName];
            }

            if (metadata == null)
            {
                // Ex:
                //    m => 5 (arbitrary expression)
                //    m => foo (arbitrary expression)
                //    m => m.Widgets[0] (expression ending with non-property-access)
                //
                // This can also happen for any case where we cannot retrieve a model metadata.
                // This will happen for:
                // - fields
                // - statics
                // - non-visibility (internal/private)
                metadata = metadataProvider.GetMetadataForType(typeof(TResult));
                Debug.Assert(metadata != null);
            }

            return viewData.ModelExplorer.GetExplorerForExpression(metadata, modelAccessor);
        }

        /// <summary>
        /// Gets <see cref="ModelExplorer"/> for named <paramref name="expression"/> in given
        /// <paramref name="viewData"/>.
        /// </summary>
        /// <param name="expression">Expression name, relative to <c>viewData.Model</c>.</param>
        /// <param name="viewData">
        /// The <see cref="ViewDataDictionary"/> that may contain the <paramref name="expression"/> value.
        /// </param>
        /// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
        /// <returns>
        /// <see cref="ModelExplorer"/> for named <paramref name="expression"/> in given <paramref name="viewData"/>.
        /// </returns>
        public static ModelExplorer FromStringExpression(
            string expression,
            ViewDataDictionary viewData,
            IModelMetadataProvider metadataProvider)
        {
            if (viewData == null)
            {
                throw new ArgumentNullException(nameof(viewData));
            }

            var viewDataInfo = ViewDataEvaluator.Eval(viewData, expression);
            if (viewDataInfo == null)
            {
                // Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
                var propertyExplorer = viewData.ModelExplorer.GetExplorerForProperty(expression);
                if (propertyExplorer != null)
                {
                    return propertyExplorer;
                }
            }

            if (viewDataInfo != null)
            {
                if (viewDataInfo.Container == viewData &&
                    viewDataInfo.Value == viewData.Model &&
                    string.IsNullOrEmpty(expression))
                {
                    // Nothing for empty expression in ViewData and ViewDataEvaluator just returned the model. Handle
                    // using FromModel() for its object special case.
                    return FromModel(viewData, metadataProvider);
                }

                ModelExplorer containerExplorer = viewData.ModelExplorer;
                if (viewDataInfo.Container != null)
                {
                    containerExplorer = metadataProvider.GetModelExplorerForType(
                        viewDataInfo.Container.GetType(),
                        viewDataInfo.Container);
                }

                if (viewDataInfo.PropertyInfo != null)
                {
                    // We've identified a property access, which provides us with accurate metadata.
                    var containerMetadata = metadataProvider.GetMetadataForType(viewDataInfo.Container.GetType());
                    var propertyMetadata = containerMetadata.Properties[viewDataInfo.PropertyInfo.Name];

                    Func<object, object> modelAccessor = (ignore) => viewDataInfo.Value;
                    return containerExplorer.GetExplorerForExpression(propertyMetadata, modelAccessor);
                }
                else if (viewDataInfo.Value != null)
                {
                    // We have a value, even though we may not know where it came from.
                    var valueMetadata = metadataProvider.GetMetadataForType(viewDataInfo.Value.GetType());
                    return containerExplorer.GetExplorerForExpression(valueMetadata, viewDataInfo.Value);
                }
            }

            // Treat the expression as string if we don't find anything better.
            var stringMetadata = metadataProvider.GetMetadataForType(typeof(string));
            return viewData.ModelExplorer.GetExplorerForExpression(stringMetadata, modelAccessor: null);
        }

        private static ModelExplorer FromModel(
            ViewDataDictionary viewData,
            IModelMetadataProvider metadataProvider)
        {
            if (viewData == null)
            {
                throw new ArgumentNullException(nameof(viewData));
            }

            if (viewData.ModelMetadata.ModelType == typeof(object))
            {
                // Use common simple type rather than object so e.g. Editor() at least generates a TextBox.
                var model = viewData.Model == null ? null : Convert.ToString(viewData.Model, CultureInfo.CurrentCulture);
                return metadataProvider.GetModelExplorerForType(typeof(string), model);
            }
            else
            {
                return viewData.ModelExplorer;
            }
        }
    }
...
}

 

namespace Microsoft.AspNetCore.Mvc.Rendering
{

 internal static class ExpressionMetadataProvider
{
...
}

 public static class HtmlHelperFormExtensions
 {

...
  public static IHtmlContent NopLabelFor<TModel, TValue>(this IHtmlHelper<TModel> helper,
                Expression<Func<TModel, TValue>> expression, bool displayHint = true)
        {
            var result = new StringBuilder();
            var hintResource = string.Empty;
            object value;

            result.Append(helper.LabelFor(expression, new { title = hintResource, @class = "control-label" }).ToHtmlString());

            var metadata = ExpressionMetadataProvider.FromLambdaExpression(expression, helper.ViewData, helper.MetadataProvider);
            if (metadata.Metadata.AdditionalValues.TryGetValue("NopResourceDisplayNameAttribute", out value))
            {
                var resourceDisplayName = value as NopResourceDisplayNameAttribute;
                if (resourceDisplayName != null && displayHint)
                {
                    var serviceProvider = helper.ViewContext.HttpContext.RequestServices;
                    var pageHeadBuilder = serviceProvider.GetRequiredService<IWorkContext>();
                    var langId = EngineContext.ServiceProvider.GetRequiredService<IWorkContext>().WorkingLanguage.Id;
                    hintResource = EngineContext.ServiceProvider.GetRequiredService<ILocalizationService>()
                        .GetResource(resourceDisplayName.ResourceKey + ".Hint", langId.ToString(), returnEmptyIfNotFound: true, logIfNotFound: false);
                    if (!String.IsNullOrEmpty(hintResource))
                    {
                        result.Append(helper.Hint(hintResource).ToHtmlString());
                    }
                }
            }

            var laberWrapper = new TagBuilder("div");
            laberWrapper.Attributes.Add("class", "label-wrapper");
            laberWrapper.InnerHtml.AppendHtml(result.ToString());

            return new HtmlString(laberWrapper.ToHtmlString());
        }
...
}

 

use

src\Presentation\Nop.Admin\Views\Topic\_CreateOrUpdate.Info.cshtml

@using Nop.Web.Framework
@model TopicModel
 
<div class="form-group advanced-setting">
                <div class="col-md-3">
                    @Html.NopLabelFor(model => model.IncludeInSitemap)
                </div>
                <div class="col-md-9">
                    @Html.NopEditorFor(x => x.IncludeInSitemap)
                    @Html.ValidationMessageFor(model => model.IncludeInSitemap)
                </div>
            </div>