11

当我开始使用xVal进行客户端验证时,我只实现了将域模型对象用作视图模型或视图模型中这些对象的嵌入实例的操作方法。

这种方法在大多数情况下都可以正常工作,但是在某些情况下,视图只需要显示和回发模型属性的子集(例如,当用户想要更新他的密码,而不是他的其余配置文件数据时) .

一个(丑陋的)解决方法是在表单上为表单上不存在的每个属性设置一个隐藏的输入字段。

显然这里的最佳实践是创建一个自定义视图模型,它只包含与视图相关的属性并通过Automapper填充视图模型。因为我只传输与视图相关的数据,所以它更干净,但它远非完美,因为我必须重复域模型对象上已经存在的相同验证属性。

理想情况下,我想通过 MetaData 属性将域模型对象指定为元类(这通常也称为“伙伴类”),但这不起作用,因为当元数据类具有以下属性时 xVal 抛出不存在于视图模型上。

有什么优雅的解决方法吗?我一直在考虑破解 xVal 源代码,但也许到目前为止我还忽略了其他一些方法。

谢谢,

阿德里安

编辑:随着 ASP.NET MVC 2 的到来,这不仅是一个与验证属性相关的问题,而且它也适用于编辑器和显示属性。

4

5 回答 5

7

这就是为什么您的输入屏幕不应该与您的模型紧密耦合的典型原因。这个问题实际上每月会在 MVC 标签上弹出 3-4 次。如果我能找到上一个问题并且这里的一些评论讨论很有趣,我会欺骗。;)

您遇到的问题是您试图将模型的两个不同验证上下文强制转换为在大量场景下失败的单个模型。最好的例子是注册一个新用户,然后让管理员稍后编辑用户字段。您需要在注册期间验证用户对象的密码,但您不会向编辑用户详细信息的管理员显示密码字段。

绕过这些的选择都是次优的。我现在已经为 3 个项目解决了这个问题,并且实施以下解决方案从来都不是干净的,而且通常令人沮丧。我将尝试变得务实,忘记所有其他人正在进行的 DDD/db/model/hotnessofthemonth 讨论。

1) 多视图模型 拥有几乎相同的视图模型违反了 DRY 原则,但我觉得这种方法的成本非常低。通常违反 DRY 会增加维护成本,但恕我直言,这方面的成本是最低的,而且并不多。假设您不会经常更改 LastName 字段的最大字符数。

2) 动态元数据 MVC 2 中有用于为模型提供您自己的元数据的钩子。使用这种方法,您可以根据当前 HTTPRequest 以及因此 Action 和 Controller 来提供用于提供元数据的任何内容来排除某些字段。我已经使用这种技术构建了一个数据库驱动的权限系统,该系统进入数据库并告诉 DataAnnotationsMetadataProvider 的子类排除存储在数据库中的基于属性的值。

这种技术在 atm 上运行良好,但唯一的问题是使用UpdateModel(). 为了解决这个问题,我们创建了一个SmartUpdateModel()方法,该方法也进入数据库并自动生成排除字符串 [] 数组,以便不验证任何不允许的字段。我们当然出于性能原因缓存了它,所以它还不错。

只是想重申一下,我们在模型上使用了 [ValidationAttributes],然后在运行时用新规则取代了它们。最终结果是,[Required]如果用户没有访问权限,则不会验证 User.LastName 字段。

3) Crazy Interface Dynamic Proxy Thing 我尝试的最后一个技术是使用 ViewModel 的接口。IAdminEdit最终结果是我有一个继承自和之类的接口的 User 对象IUserRegistration。IAdminEdit 和 IUserRegistration 都将包含 DataAnnotation 属性,这些属性执行所有特定于上下文的验证,例如带有接口的 Password 属性。

这需要一些技巧,并且比其他任何事情都更像是一种学术练习。2 和 3 的问题是需要自定义 UpdateModel 和 DataAnnotationsAttribute 提供程序以了解此技术。

我最大的绊脚石是我不想将整个用户对象发送到视图,所以我最终使用动态代理来创建运行时实例IAdminEdit

现在我明白这是一个非常特定于 xVal 的问题,但是像这样的动态验证的所有道路都会导致内部 MVC 元数据提供程序的定制。由于所有元数据的东西都是新的,在这一点上没有什么是干净或简单的。自定义 MVC 的验证行为所要做的工作并不难,但需要深入了解所有内部结构的工作原理。

于 2010-11-01T17:39:07.303 回答
4

我们将验证属性移至 ViewModel 层。在我们的案例中,无论如何,这提供了更清晰的关注点分离,因为我们随后能够设计我们的域模型,使其一开始就不会进入无效状态。例如,BillingTransaction 对象可能需要 Date。所以我们不想让它为 Nullable。但是在我们的 ViewModel 上,我们可能需要公开 Nullable 以便我们可以捕捉到用户没有输入值的情况。

在其他情况下,您可能有特定于每个页面/表单的验证,并且您希望根据用户尝试执行的命令进行验证,而不是设置一堆东西并询问域模型,“你是对尝试执行 XYZ 有效,而在执行“ABC”时,这些值是有效的。

于 2010-01-17T19:45:29.177 回答
3

如果假设 ViewModel 是强加给您的,那么我建议它们只强制执行与域无关的要求。这包括“需要用户名”和“电子邮件格式正确”之类的内容。

如果您从视图模型中的域模型重复验证,那么您已经将域紧密耦合到 UI。当域验证更改(“每周只能申请 2 个优惠券”变为“每周只能申请 1 个优惠券”)时,必须更新 UI。一般来说,这会很糟糕,并且不利于敏捷性。

如果您将验证从域模型转移到 UI,那么您实际上已经破坏了您的域并将验证的责任放在了 UI 上。第二个 UI 必须复制所有验证,并且您已将两个单独的 UI 耦合在一起。现在,如果客户想要一个特殊的界面来管理他们 iPhone 的库存,iPhone 项目需要复制网站 UI 中的所有验证。这将比上面描述的验证重复更糟糕。

除非您可以预测未来并且可以排除这些可能性,否则仅验证与领域无关的需求。

于 2010-01-23T00:36:10.103 回答
2

我不知道这将如何用于客户端验证,但如果部分验证是您的问题,您可以修改DataAnnotationsValidationRunner此处讨论的内容以获取IEnumerable<string>属性名称列表,如下所示:

public static class DataAnnotationsValidationRunner
{
     public static IEnumerable<ErrorInfo> GetErrors(object instance, IEnumerable<string> fieldsToValidate)
     {
           return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>().Where(p => fieldsToValidate.Contains(p.Name))
                  from attribute in prop.Attributes.OfType<ValidationAttribute>()
                  where !attribute.IsValid(prop.GetValue(instance))
                  select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
     }
}
于 2010-02-18T04:22:41.263 回答
0

我将冒险投反对票并声明 ViewModels(在 ASP.NET MVC 中)没有任何好处,尤其是考虑到创建和维护它们的开销。如果这个想法是与域解耦,那是站不住脚的。与域分离的 UI 不是该域的 UI。UI必须依赖于域,因此您要么将视图/操作耦合到域模型,要么将 ViewModel 管理逻辑耦合到域模型。因此,架构论点是没有实际意义的。

如果这个想法是为了防止用户攻击恶意 HTTP POST,利用 ASP.NET MVC 的模型绑定来改变他们不应该被允许更改的字段,那么 A) 域应该强制执行这个要求,并且 B) 操作应该为模型绑定器提供可更新属性的白名单。

除非你的领域正在暴露一些疯狂的东西,比如一个活的、内存中的对象图而不是实体副本,否则 ViewModel 是白费力气。因此,要回答您的问题,请将域验证保留在域模型中。

于 2010-01-23T00:29:01.710 回答