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

时间:2017-05-09 01:31:14

标签: c# asp.net-mvc reflection data-annotations

问题描述

我的问题与this question类似,但不是通过反射将数据注释应用于属性Name(由ModelMetadata.DisplayName处理),而是将它们应用于值(不由ModelMetadata处理)。

详细说明

在ASP.NET MVC程序的上下文中 假设我有一个Model类

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个不同的视图,其中每个属性的值必须完整显示。 (有时候对于每一个例子,其他时间为少数,其他时间为一个)。

我可以手动列出其内部的每个Property访问权限 <td>@Html.DisplayFor(item.<Property>)</td>
对于每个观点。

但是如果稍后Model的定义扩展为包含新属性(如描述,关系和可靠性),那对我没有帮助。然后,我需要手动更新完整模型列表的每一个匹配项。

我可以使用反射来迭代PropertyInfo列表并节省必须使用
手动列出每个属性 <td>@property.GetValue(item)</td>

DisplayFor(x)不支持与x=>property.GetValue(item)一样复杂的表达式,表示我丢失了将DateTime格式化为数据的数据注释 01/01/1990
而不是 01-Jan-90 12:00:00 AM
并且可能还会导致丢失所有类型的注释,包括验证。

问题解决方案

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

  • [失败] 手动制作一个模仿@property.GetValue(item)的功能的表达式 [编辑]
  • [失败] 传递DisplayFor一个MethodInfo对象,表示属性访问器DisplayFor(x => property.GetGetMethod())以及.Invoke在x上。 [/编辑]
  • 正常手动检索值,并且
    • 在其上执行一个方法,以便在this question建议的视图元素中插入之前手动检索并实现其上的注释数据,或者
    • 在显示模板视图中根据需要重新实现DisplayFor处理数据注释,并通过DisplayFor直接将其应用于this question建议
  • 将Model类重构为仅包含&#39; Prop&#39;的列表(SortedList?)实例,其中&#39; Prop&#39;是一个表示具有Name和Value元素的Property的类。

这最后一个解决方案会破坏 @Html.DisplayFor(m=>property.GetValue(item)
理论上的工作 @Html.DisplayFor(m=>item.Properties[i].Value)
除了通过(.Value)获取名为Name(属性[&#34; Name&#34;])的属性的略微不直观的需求之外,似乎是最可行的解决方案,代价是模型清晰度。

[编辑]
最近我创建了一个Utility方法,它从PropertyInfo中检索DisplayFormatAttribute并返回DisplayFormatString或默认的&#34; {0}&#34;如果没有注释格式字符串。然后,我使用它在ViewModel中创建预格式化属性值的集合。 现在看来,这是我所知道的最优雅的方式,即尽可能将View与模型分离,同时仍然从中检索必要的数据。
[/编辑]

问题

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

2 个答案:

答案 0 :(得分:0)

也许类似于:

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

答案 1 :(得分:0)

成功

重新审视我原先尝试手动创建表达式,我发现this article正是我想要做的,并且主要使用Microsoft提供的代码!

虽然很难找到Microsoft代码(文章中的链接被破坏,代码I 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扩展和文章中的唯一区别是此函数调用中的null对象参数(加上从EditorFor到DisplayFor的明显转换):

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

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