【问题标题】:How to add MetaData to a dynamically build MVC3 ViewModel?如何将元数据添加到动态构建的 MVC3 ViewModel?
【发布时间】:2012-09-14 14:32:30
【问题描述】:

我正在处理的项目的一个关键特性是用户能够基于预先存在的字段类型(众所周知的类型)配置表单(如“表单”中的填充) ,例如“用户名”、“出生日期”等,还有“通用类型”,如“字符串”、“日期时间”等)。

我们曾经有一个静态 ViewModel 可以很好地用于“知名”类型,看起来像这样:

public class UserInputModel
{
    [StringLength(200)]
    public string Name { get; set; }

    [Required(ErrorMessageResourceName = "BirthDateEmptyError", ErrorMessageResourceType = typeof(Resources.ErrorMessages))]
    public DateTime BirthDate { get; set; }

    //Here comes a lot of other properties
}

所有已知属性都已列出,我们根据上下文显示或隐藏它们。

但最后一个要求来了,改变了这一切。用户现在可以根据需要添加任意数量的泛型类型字段。为了做到这一点,我们决定让这个 InputModel 完全动态化。现在看起来像这样:

public class UserInputModel
{
    // Each ModelProperty has an "Id" and a "Value" property
    public ICollection<ModelProperty> Properties { get; set; }
}

这就像一个魅力。 razor 视图只需要遍历集合,为集合的每个属性创建相应的控件,而不是标准的方式:

@Html.TextBoxFor(m => m.Properties[index].Value);

...我们很好地将数据作为填写好的表格返回。

=> 这很好用,但我们没有任何客户端验证。为此,我们需要一些元数据……因为我们正在动态创建模型,所以我们不再通过注释获得这些元数据。

为了提供这些元数据,我创建了一个继承自 DataAnnotationsModelMetadataProviderCustomModelMetadataProvider,并将其注册为 Global.asax 中的新 ModelMetadataProviderCreateMetadata() 函数在创建 ViewModel 时被调用,并且对于我的 ViewModel 的每个属性......到目前为止都很好。

问题从哪里开始:为了向当前属性添加一些元数据,我首先需要确定我当前正在查看的属性(“名称”的最大长度为 200,“日期出生”没有,所以我不能在默认情况下为每个属性分配一个最大长度)。不知何故,我还没有做到这一点,因为所有属性都具有相同的名称 Value 和相同的容器类型 ModelProperty

我尝试通过反射访问属性的容器,但由于 ModelAccessor 的目标是 ViewModel 本身(因为 lambda 表达式 m =&gt; m.Properties),以下构造为我提供了整个 ViewModel,而不仅仅是 ModelProperty:

var container = modelAccessor.Target.GetType().GetField("container");
var containerObject = (UserInputModel)container.GetValue(modelAccessor.Target);

我一直在翻来覆去,但找不到一种方法来确定我手头的 ModelProperty。有没有办法做到这一点?

更新:在把它从各个可能的方向翻转了一段时间之后,我们终于走了另一条路。我们基本上使用不显眼的 javascript 来使用 MVC 的验证功能,而无需触及属性或元数据。简而言之,我们将value-data="true" 之类的HTML 属性(以及所有其他必需属性)添加到@Html.TextBoxFor() 语句中。这对于所有原子验证(必需、字符串长度等)都非常有效。

【问题讨论】:

    标签: c# asp.net-mvc asp.net-mvc-3 modelmetadata modelmetadataprovider


    【解决方案1】:

    Tim,您可以利用似乎是通过 Ajax 进行的客户端验证,并在您的属性上使用 Remote attribute

    基本上,您需要设置一个验证控制器,然后将一些智能写入该控制器。但至少您可以编写一些辅助方法并将它们全部保存在一个地方。根据您向最终用户提供的元数据,您将拥有一系列验证器,并且每个验证器方法都适用于具有良好重用性的特定类型。

    这种方法的一个缺陷是您需要为要支持的每种类型和条件编写验证方法。不过,听起来你无论如何都必须走那条路。

    希望这会有所帮助。

    【讨论】:

    • 很遗憾,感谢您的回答-除非我误解了您-我认为您没有抓住重点。这里的整个问题不是验证本身,而是属性元数据的注入。
    【解决方案2】:

    看看这篇文章是否对你有帮助:Technique for carrying metadata to View Models with AutoMapper

    也可以将此用于想法(自定义模型元数据提供程序):changing viewmodel's MetadataType attribute at runtime

    Fluent validation 可能是我心目中最适合您的选择,但显然您可以在上述选项中选择最佳匹配。

    更新

    尝试使用ModelMetadata 并覆盖ModelMetadataProviderDive Deep Into MVC: ModelMetadata and ModelMetadataProvider。通过这种方式,您可以完全自定义模型元数据(这取代了数据注释),并且您可以完全控制正在发生的事情,而不是依赖 ASP.NET MVC。

    另一个查看它的好地方是Creating your own ModelMetadataProvider to handle custom attributes

    希望这一切对你有所帮助。

    【讨论】:

    • 感谢您的回答。关于第一个链接,作者似乎仍然使用 PropertyName 作为鉴别器......这正是我无法做到的(我所有的属性都称为“Value”)。关于第二个链接,他们使用属性的类型来区分它......但同样,我所有的同类属性......
    • “流利验证”怎么样?我知道(我没有使用过)很多人都这样做了,而且非常成功,并且大受好评。尝试采用完全不同的方法:这是最重要的(提供您自己的)ModelMetadataProvider。请参阅我的答案中的更新。
    • 我也没有使用 Fluent 验证,但据我所知,它使用的 lambdas 依赖于属性类型......所以这又不适合我。关于您的更新,这正是我正在做的;我创建了我的 CustomModelMetadataProvider 并试图修改事物的流程。这正是我遇到问题的地方,因为我无法根据其名称或类型识别属性...
    • 嗨,蒂姆,请问对您有帮助吗?
    • 很遗憾没有,我还是卡在那里。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多