ASP.NET 6 MVC中DisplayNameFor()等效表达式为何一可行一报错?
为什么两种表达式结果不同?
当视图模型是IEnumerable<launchpad.Models.Application>时,@Html.DisplayNameFor(model => model.AppName)能正常运行,是因为ASP.NET MVC的HtmlHelper对集合类型模型做了特殊处理——它会自动忽略集合本身,转而解析集合元素类型的属性。也就是说,这里的model在表达式解析时被框架隐式当成了集合中的单个Application对象,而非整个IEnumerable集合,所以能正确找到AppName属性的显示名。
而当视图模型是包含IEnumerable<Application>类型属性的ApplicationsViewModel时,model => model.Applications.AppName会直接被编译器解析为访问IEnumerable<Application>类型的AppName属性,显然集合类型本身没有这个属性,所以报CS1061错误。这时候框架不会自动深入到集合的元素层级,必须明确指定要访问的是集合元素的属性。
跨ViewModel获取领域模型属性显示名的最佳实践
明确指定集合元素的表达式:
用First()或Single()等方法指向集合中的单个元素(这些方法仅用于表达式解析,不会实际执行,不用担心空集合问题),示例:@Html.DisplayNameFor(model => model.Applications.First().AppName)框架会通过这个表达式解析到
Application类型的AppName属性,获取正确显示名。在ViewModel中预定义显示名:
写一个静态辅助类来获取属性显示名,然后在ViewModel中直接定义对应字段,避免视图中写复杂表达式:// 辅助类 public static class DisplayNameHelper { public static string GetDisplayName<TModel>(string propertyName) { var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => Activator.CreateInstance<TModel>(), typeof(TModel), propertyName); return metadata.DisplayName ?? propertyName; } } // ViewModel中使用 public class ApplicationsViewModel { public IEnumerable<Application> Applications { get; set; } public string AppNameDisplayName => DisplayNameHelper.GetDisplayName<Application>(nameof(Application.AppName)); }视图中直接调用:
@Model.AppNameDisplayName利用ModelMetadata直接获取:
跳过HtmlHelper,直接通过ModelMetadata解析表达式获取显示名:@ModelMetadata.FromLambdaExpression<ApplicationsViewModel, string>(m => m.Applications.First().AppName, ViewData).DisplayName使用局部视图分离逻辑:
创建以Application为模型的局部视图,处理单个对象的显示逻辑。主视图的表头显示名通过集合元素表达式获取,循环渲染时调用局部视图:// 局部视图 _ApplicationListItem.cshtml @model launchpad.Models.Application <td>@Html.DisplayFor(m => m.AppName)</td> // 主视图中使用 @Html.DisplayNameFor(model => model.Applications.First().AppName) @foreach (var app in Model.Applications) { @Html.Partial("_ApplicationListItem", app) }
内容的提问来源于stack exchange,提问作者Ross Volkmann

