对于它的价值,这是我在当前项目中找到的:
我有Models、Repositories(如果你愿意,可以叫他们Services)和ViewModels。我尽量避免编写自定义模型绑定器,因为 (a) 它很无聊,并且 (b) 放置验证的地方很奇怪,恕我直言。对我来说,模型绑定器只是从请求中获取项目并将它们推入对象中。例如,PHP 在将项目从标头提取到 $_POST 数组时不做任何验证;这是我们插入数组的东西,它关心它的内容。
我的Model 对象通常不允许自己进入无效状态。这意味着在构造函数期间传入必需的参数,如果尝试将其设置为无效值,属性将引发异常。而且,一般来说,我尝试将我的Model 对象设计为不可变的。例如,我有一个用于邮寄地址的Address 对象,该对象由AddressBuilder 对象构成,通过检查可以从AddressSchemeRepository 检索的AddressScheme 来查看给定国家/地区的字段要求。呸。但我认为这是一个很好的例子,因为它在概念上很简单(“验证邮寄地址”)并使其在现实世界的使用中变得复杂(“我们接受来自 30 多个国家/地区的地址,并且这些格式规则位于数据库中,而不是在我的代码中”)。
由于构造这个 Model 对象有点痛苦——它应该也是这样,因为它对加载到其中的数据非常特别——我有一个,比如说,InputAddressViewModel 对象,我的视图绑定到。 InputAddressViewModel 实现 IDataErrorInfo 以便我获得 ASP.NET MVC 的 DefaultModelBinder 以自动将错误添加到 ModelState。对于我提前知道的简单验证例程(电话号码格式、需要名字、电子邮件地址格式),我可以在 InputAddressViewModel 中实现这些。
拥有视图模型的另一个优点是,因为它无耻地为特定视图量身定制,所以您的真实模型更具可重用性,因为它不必做出任何奇怪的让步以使其适合 UI 显示(例如,需要实现INotifyPropertyChanged 或Serializable 或任何混乱)。
在我与实际的Model 中的AddressScheme 交互之前,我不会知道有关地址的其他验证错误。这些错误将是控制器编排到ModelState 的工作。比如:
public ActionResult InputAddress(InputAddressViewModel model)
{
if (ModelState.IsValid)
{
// "Front-line" validation passed; let's execute the save operation
// in the our view model
var result = model.Execute();
// The view model returns a status code to help the
// controller decide where to redirect the user next
switch (result.Status)
{
case InputAddressViewModelExecuteResult.Saved:
return RedirectToAction("my-work-is-done-here");
case InputAddressViewModelExecuteResult.UserCorrectableError:
// Something went wrong after we interacted with the
// datastore, like a bogus Canadian postal code or
// something. Our view model will have updated the
// Error property, but we need to call TryUpdateModel()
// to get these new errors to get added to
// the ModelState, since they were just added and the
// model binder ran before this method even got called.
TryUpdateModel(model);
break;
}
// Redisplay the input form to the user, using that nifty
// Html.ValidationMessage to convey model state errors
return View(model);
}
}
switch 可能看起来令人反感,但我认为这是有道理的:视图模型只是一个普通的旧类,对Request 或HttpContext 没有任何了解。这使得视图模型的逻辑易于单独测试,而无需借助模拟,并且通过以在网站上有意义的方式解释模型的结果,将控制器代码留给 control --它可以重定向,它可以设置cookie等。
InputAddressViewModel 的 Execute() 方法看起来像(有些人会坚持将此代码放入控制器将调用的服务对象中,但对我来说,视图模型会对数据进行如此多的处理为了使它适合真实的模型,放在这里是有意义的):
public InputAddressViewModelExecuteResult Execute()
{
InputAddressViewModelExecuteResult result;
if (this.errors.Count > 0)
{
throw new InvalidOperationException(
"Don't call me when I have errors");
}
// This is just my abstraction for clearly demarcating when
// I have an open connection to a highly contentious resource,
// like a database connection or a network share
using (ConnectionScope cs = new ConnectionScope())
{
var scheme = new AddressSchemeRepository().Load(this.Country);
var builder = new AddressBuilder(scheme)
.WithCityAs(this.City)
.WithStateOrProvinceAs(this.StateOrProvince);
if (!builder.CanBuild())
{
this.errors.Add("Blah", builder.Error);
result = new InputAddressViewModelExecuteResult()
{
Status = InputAddressViewModelExecuteStatus
.UserCorrectableError
};
}
else
{
var address = builder.Build();
// save the address or something...
result = new InputAddressViewModelExecuteResult()
{
Status = InputAddressViewModelExecuteStatus.Success,
Address = address
};
}
}
return result;
}
这有意义吗?这是最佳实践吗?我不知道;这当然很冗长;这是我在过去两周思考这个问题后才想到的。我认为您将有一些重复验证-您的 UI 不能完全愚蠢,并且在将它们提交到您的模型/存储库/服务/之前不知道哪些字段是必需的不管怎样——否则表单可以简单地自己生成。
我应该补充一点,这样做的动力是我一直有点讨厌微软的“设置一个属性 -> 验证一个属性”的心态,因为现实中从来没有这样的工作。你总是最终得到一个无效的对象,因为有人在去数据存储的路上忘记了调用IsValid 或类似的东西。所以拥有视图模型的另一个原因是它会根据这种让步进行自我调整,因此我们可以很容易地从请求中提取项目、模型状态中的验证错误等大量 CRUD 工作损害我们模型本身的完整性。如果我手头有一个Address 对象,我知道这很好。如果我手头有一个InputAddressViewModel 对象,我知道我需要调用它的Execute() 方法来获得那个金色的Address 对象。
我期待阅读其他一些答案。