ASP.NET MVC 使用数据注释迭代模型属性

ASP.NET MVC Iterate over Model Properties with Data Annotations

问题描述

我的问题类似于 this question 但不是通过反射将数据注释应用于 属性 名称(由 ModelMetadata.DisplayName 处理)我将它们应用于值(未处理通过模型元数据)。

详细说明

在 ASP.NET MVC 程序的上下文中。
假设我有一个模型 class

public class Model
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string NickName { get; set; }
    public string Address { get; set; }
    public int Phone { get; set; }

    [Display(Name = "Start Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
    public DateTime StartDate { get; set }

    [Display(Name = "End Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
    public DateTime EndDate { get; set }
}

然后假设此模型至少用于 5 个不同的视图,其中每个 属性 的值必须完整显示。 (有时针对每个实例,有时针对少数实例,有时针对一个实例)。

我可以手动列出每个 属性 访问权限
<td>@Html.DisplayFor(item.<Property>)</td>
对于每个视图。

但是,如果稍后模型的定义扩展到包括新的属性(如描述、关系和可靠性),那对我没有帮助。然后我需要手动更新每次出现的完整模型列表。

我可以使用反射遍历 属性Info 的列表,并且不必使用
手动列出每个 属性 <td>@property.GetValue(item)</td>

但是 DisplayFor(x) 不支持像 x=>property.GetValue(item) 这样复杂的表达式,而 这个 意味着我丢失了将我的日期时间格式化为
01/01/1990
而不是
01-Jan-90 12:00:00 AM
并且可能还会导致丢失所有类型的注释,包括验证。

问题解决方案

到目前为止,我已经考虑(并在某些情况下尝试过)以下解决方案:

最后一个解决方案会把坏掉的
@Html.DisplayFor(m=>property.GetValue(item)
进入理论上可行的
@Html.DisplayFor(m=>item.Properties[i].Value)
除了通过 (.Value) 获得名为 Name (Properties["Name"]) 的 属性 有点不直观的需求之外,这似乎是最可行的解决方案,但代价是模型清晰度。

[编辑]
最近我创建了一个 Utility 方法,它从 属性Info 和 returns DisplayFormatString 或默认的“{0}”中检索 DisplayFormatAttribute(如果未注释格式字符串)。然后我用它在 ViewModel 中创建了一组预先格式化的 属性 值。 目前看来,这是我所知道的尽可能将视图与模型分离同时仍从中检索必要数据的最优雅的方法。
[/编辑]

问题

目前这是一个纯粹的学习练习,但我想知道...
是否有可能在我失败的地方取得成功,并且既有我的反射蛋糕也有数据注释?还是我必须寻求替代解决方案?
如果我必须寻求替代解决方案,是否有我错过的路线,或者我至少在正确的轨道上?

可能类似于:

@foreach (var property in Model.GetType().GetProperties())
{
    <li>@property.GetValue(Model, null)</li>
}

大获成功

重新审视我最初尝试动态手动制作表达式的尝试,我发现 this article 完全符合我的要求,并且主要使用 Microsoft 提供的代码!

虽然微软的代码很难找到(文章中的link被破坏了,例子对于我did find的代码来说有点过时了),但我还是可以使用它,效果很好实现我自己的 DisplayFor 扩展方法。
不幸的是,由于我的模型是一个列表而不是单个实例,我仍然需要创建一个局部视图来传递一个实例,这样我就可以通过模型从生成的表达式中访问属性。

我的视图代码现在看起来像这样:

@foreach (var thing in Model.CollectionOfThings)
{
    <tr>
        @foreach (var prop in typeof(Thing).GetProperties())
        {
            <td>
                @{
                    Html.RenderPartial("~/Views/Shared/_DisplayForReflectedProperty.cshtml", 
                    new Tuple<Thing, PropertyInfo>(thing, prop));
                }
            </td>
        }
}

使用 _DisplayForReflectedProperty 就像

一样简单
@using WebApplication1.Models
@using System.Reflection
@using WebApplication1.Extensions

@model Tuple<Thing, PropertyInfo>

@Html.DisplayFor("Item1."+Model.Item2.Name)

我的 DisplayFor 扩展与文章中的唯一区别是此函数调用中的空对象参数(加上从 EditorFor 到 DisplayFor 的明显转换):

var lambda = System.Linq.Dynamic.DynamicExpression.ParseLambda(typeof(TModel), 
             null, expression);

使用此模板,我现在可以生成模型属性的任意(从代码角度,具体从业务规则角度)子集,以我希望的任何方式显示,而无需针对每个特定子集定制我的视图,同时保留使用 'For' 助手的所有好处!