【发布时间】: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);
...我们很好地将数据作为填写好的表格返回。
=> 这很好用,但我们没有任何客户端验证。为此,我们需要一些元数据……因为我们正在动态创建模型,所以我们不再通过注释获得这些元数据。
为了提供这些元数据,我创建了一个继承自 DataAnnotationsModelMetadataProvider 的 CustomModelMetadataProvider,并将其注册为 Global.asax 中的新 ModelMetadataProvider。 CreateMetadata() 函数在创建 ViewModel 时被调用,并且对于我的 ViewModel 的每个属性......到目前为止都很好。
问题从哪里开始:为了向当前属性添加一些元数据,我首先需要确定我当前正在查看的属性(“名称”的最大长度为 200,“日期出生”没有,所以我不能在默认情况下为每个属性分配一个最大长度)。不知何故,我还没有做到这一点,因为所有属性都具有相同的名称 Value 和相同的容器类型 ModelProperty。
我尝试通过反射访问属性的容器,但由于 ModelAccessor 的目标是 ViewModel 本身(因为 lambda 表达式 m => 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