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;
}
}
}
...
}
//see:https://github.com/aspnet/Mvc/blob/eea297975db6e77a39b4809489a28ed65841584c/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultDisplayTemplates.cs
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>