当我开始使用时 XVAL 对于客户端验证,我只实现了使用域模型对象作为viewmodel或viewmodel中这些对象的嵌入式实例的操作方法。
这种方法大多数情况下工作正常,但有时候视图需要显示和回发模型属性的一个子集(例如,当用户想要更新他的密码,而不是他的其他配置文件数据时) 。
一个(丑陋的)解决方法是在表单上为每个属性设置一个隐藏的输入字段,而该字段在表单上不存在。
显然,这里的最佳做法是创建一个自定义视图模型,该视图模型仅包含与视图相关的属性并通过视图模型填充视图模型 Automapper。它更清晰,因为我只传输与视图相关的数据,但它远非完美,因为我必须重复已经存在于域模型对象上的相同验证属性。
理想情况下,我想通过MetaData属性将域模型对象指定为元类(这通常也称为“伙伴类”),但这不起作用,因为当元数据类具有属性时,xVal会抛出视图模型中不存在。
这有什么优雅的解决方法吗?我一直在考虑攻击xVal源代码,但也许还有其他一些方法我到目前为止都忽略了。
谢谢,
阿德里安
编辑: 随着ASP.NET MVC 2的到来,这不仅仅是与验证属性相关的问题,而且它也适用于编辑器和显示属性。
这是您的输入屏幕不应与模型紧密耦合的典型原因。这个问题实际上会在MVC标签上弹出一个月大约3-4次。如果我能找到上一个问题并且这里的一些评论讨论很有趣,我会愚蠢的。 ;)
您遇到的问题是您试图将模型的两个不同验证上下文强制转换为在大量场景下失败的单个模型。最好的示例是注册新用户,然后让管理员稍后编辑用户字段。您需要在注册期间验证用户对象上的密码,但不会向管理员显示密码字段以编辑用户详细信息。
绕过这些的选择都是次优的。我现在已经为3个项目解决了这个问题,并且实施以下解决方案从来没有干净,通常令人沮丧。我要去试试吧 实际的 并且忘记了其他人正在进行的所有DDD / db / model / hotness。
1)多视图模型
拥有几乎相同的视图模型违反了DRY原则,但我觉得这种方法的成本非常低。通常违反DRY放大维护成本,但恕我直言,这是最低的,并不是很多。假设您不会更改LastName字段可以经常使用的最大数字字符数。
2)动态元数据
MVC 2中有一些钩子,用于为模型提供自己的元数据。使用这种方法,您可以使用任何用于提供元数据的内容,根据当前的HTTPRequest以及Action和Controller排除某些字段。我已经使用这种技术构建了一个数据库驱动的权限系统,该系统进入数据库并告诉DataAnnotationsMetadataProvider的子类排除存储在数据库中的基于属性的值。
这种技术工作得非常好,但唯一的问题是验证 UpdateModel()
。为解决这个问题,我们创建了一个 SmartUpdateModel()
方法也进入数据库并自动生成exclude string []数组,以便不验证任何不允许的字段。我们当然是出于性能原因而缓存的,所以它还不错。
只是想重申我们在模型上使用[ValidationAttributes],然后用运行时的新规则取代它们。最终的结果是 [Required]
如果用户没有访问权限,则不验证User.LastName字段。
3)疯狂的接口动态代理事物
我尝试的最后一种技术是使用ViewModels的接口。最终结果是我有一个继承自接口的User对象 IAdminEdit
和 IUserRegistration
。 IAdminEdit和IUserRegistration都包含DataAnnotation属性,这些属性执行所有特定于上下文的验证,如带有接口的Password属性。
这需要一些hackery,而且比其他任何东西都更像学术活动。 2和3的问题是需要自定义UpdateModel和DataAnnotationsAttribute提供程序以使其了解此技术。
我最大的绊脚石是我不想将整个用户对象发送到视图,所以我最终使用动态代理来创建运行时实例 IAdminEdit
现在我明白这是一个非常xVal的具体问题,但是所有这样的动态验证之路都会导致内部MVC元数据提供商的定制。由于所有的元数据都是新的,所以此时没有什么是干净或简单的。您需要做的工作来定制MVC的验证行为并不难,但需要深入了解所有内部工作的方式。
我们将验证属性移动到ViewModel层。在我们的案例中,无论如何,这提供了更清晰的关注点分离,因为我们随后能够设计我们的域模型,使其无法首先进入无效状态。例如,BillingTransaction对象可能需要Date。所以我们不想让它成为Nullable。但是在我们的ViewModel上,我们可能需要公开Nullable,以便我们可以捕获用户没有输入值的情况。
在其他情况下,您可能需要针对每个页面/表单进行特定验证,并且您希望根据用户尝试执行的命令进行验证,而不是设置一堆内容并询问域模型,“是吗?有效的尝试做XYZ“,在做”ABC“这些值是有效的。
如果假设ViewModel被强加给你,那么我建议他们只强制执行与域无关的要求。这包括诸如“需要用户名”和“正确格式化电子邮件”之类的内容。
如果您从视图模型中的域模型复制验证,那么您已将域紧密耦合到UI。当域验证更改(“每周只能应用2张优惠券”变为“每周只能应用1张优惠券”)时,必须更新UI。一般来说,这将是可怕的,并且不利于敏捷性。
如果您将验证从域模型移动到UI,您实际上已经毁掉了您的域并将验证的责任放在UI上。第二个UI必须复制所有验证,并且您已将两个单独的UI耦合在一起。现在,如果客户想要一个特殊的界面来管理他们的iPhone库存,那么iPhone项目需要复制网站UI中也能找到的所有验证。
这比上面描述的验证重复更加糟糕。
除非您可以预测未来并且可以排除这些可能性,否则只能验证与域无关的要求。
我不知道这将如何用于客户端验证,但如果部分验证是您的问题,您可以修改 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);
}
}
我将冒险投票并说明ViewModels(在ASP.NET MVC中)没有任何好处,特别是考虑到创建和维护它们的开销。如果想法是从域中解耦,那是不可原谅的。与域分离的UI不是该域的UI。用户界面 必须 取决于域,因此您要么将视图/操作耦合到域模型,要么将ViewModel管理逻辑耦合到域模型。因此架构论证没有实际意义。
如果想要阻止用户攻击利用ASP.NET MVC模型绑定到变异字段的恶意HTTP POST,则不应允许他们更改,那么A)域应该强制执行此要求,并且B)操作应该为模型绑定器提供可更新属性的白名单。
除非你的域暴露出像现场内存对象图而不是实体副本一样疯狂的东西,否则ViewModel会浪费精力。因此,要回答您的问题,请在域模型中保留域验证。