我做了很多研究,包括这里关于SO,但我似乎找不到明确的方向。我目前有一个 ASP.NET 的 MVC3 应用程序,其服务层位于存储库之上。
在我的服务层中,我具有以下功能:
public class MyService{
public void CreateDebitRequest(int userId, int cardId, decimal Amount, .... )
{
//perform some sort of validation on parameters, save to database
}
public void CreateCreditRequest(.....)
}
//perform some sort of validation on parameters, save to database
}
public void CreateBatchFile()
{
//construct a file using a semi-complex process which could fail
//write the file to the server, which could fail
}
public PaymentTransaction ChargePaymentCard(int paymentCardId, decimal amount)
{
//validate customer is eligible for amount, call 3rd party payments api call,
//...save to database, other potential failures, etc.
}
}
我看到有人说参数验证不是很特别,所以抛出异常不是很合适。我也不喜欢传入 out 参数(例如字符串)并检查空值的想法。我已经考虑过实现一个 ValidationDictionary 类,并使其成为任何给定服务类的属性(它将包含一个 IsValid 布尔值和一个错误消息列表,并且可以在服务层中的任何给定函数调用后进行检查以查看情况)。我可以在运行任何给定函数后检查验证字典状态:
var svc = new MyService();
svc.CreateBatchFile();
if (svc.ValidationDictionary.IsValid)
//proceed
else
//display values from svc.ValidationDictionary.Messages...
我不喜欢这一点的是,我必须为每个服务层函数调用更新它,以避免它保留旧值(如果我选择不将其用于许多或大多数函数,人们仍然希望它在运行任何给定函数后具有有意义的或 null 值)。我考虑的另一件事是为每个可能具有详细验证信息的函数调用传递 ValidationDictionary,但随后我又回到使用 out 参数......
你们有什么建议吗?我似乎想不出任何干净的方法来做到这一点。有时为函数返回 null 就足够了,但有时我希望将更多的验证信息传递回调用方。任何建议将不胜感激!
编辑以澄清:我的服务层不知道正在使用它的是一个 MVC 应用程序。服务层只有某些公共函数,如 CreateBatchFile() 或 AddDebitRequest()。有时返回 null 足以让使用者(在本例中为控制器,但可能是其他内容)知道发生了什么,有时使用者希望从服务层获得更多信息(如果使用者是控制器,则可能传递给 ModelState)。如何从服务层本身冒泡
我所做的。 有一个用于验证的类,而不是传递参数,而是传递视图模型。 所以在你的例子中,像这样,其中ValidationResult只是一个带有MemberName和ErrorMessage属性的简单类:
public class DebitRequestValidator{
public IEnumerable<ValidationResult> Validate(DebitRequestModel model){
//do some validation
yield return new ValidationResult {
MemberName = "cardId",
ErrorMessage = "Invalid CardId."
}
}
}
然后创建一个控制器扩展方法,将这些验证结果复制到模型状态。
public static class ControllerExtensions
{
public static void AddModelErrors(this ModelStateDictionary modelState, IEnumerable<ValidationResult> validationResults)
{
if (validationResults == null) return;
foreach (var validationResult in validationResults)
{
modelState.AddModelError(validationResult.MemberName, validationResult.ErrorMessage);
}
}
}
然后在您的控制器中执行类似操作
[HttpPost]
public ActionResult DebitRequest(DebitRequestModel model) {
var validator = new DebitRequestValidator();
var results = validator.Validate(model);
ModelState.AddModelErrors(results);
if (!ModelState.IsValid)
return View(model)
//else do other stuff here
}
然后,您可以在视图中像往常一样显示错误。
@Html.ValidationMessageFor(m => m.CardId)
我使用了一个系统,它传递一个消息数组(或类的集合),每个元素都有代码、描述和友好的消息。我们过去只是简单地检查那里是否有任何东西。它在 UI 和另一个"服务"层之间工作得很好,所有异常都被很好地捕获,它们被转换为这些验证规则......只是一个想法
使用在视图和控制器操作方法之间传递的 ViewModel 对象。对象可以通过Validate(ValidationDictionary validationDictionary)
方法处理验证。
控制器必须先在 ViewModel 对象上调用验证方法,然后才能调用服务层中的任何方法。这应该只对 http POST 操作是必需的。
然后,您的视图必须显示验证消息。
此解决方案要求视图模型对象在控制器操作和视图之间传递,但现在主要由 MVC 中的 ModelBinder 处理。
您的控制器(http post)操作将如下所示:
[HttpPost]
public ActionResult Foo(BarViewModel viewModel)
{
viewModel.Validate(ValidationDictionary);
if (!ModelState.IsValid)
{
return View(viewModel);
}
// Calls to servicelayer
}
视图模型中的验证方法将如下所示:
public void Validate(ValidationDictionary validationDictionary)
{
if (SomeProperty.Length > 30)
{
validationDictionary.AddError("SomeProperty", "Max length is 30 chars");
}
}
如果您只是在执行 ViewModel 验证,那么 FluentValidation 是一个出色的库。
如果要将业务验证作为对用户的反馈包含在内,则可以使用适配器模式,它会为您提供所需的内容。
创建一个接口(IValidationDictionary或类似的东西)。此接口将定义一个 AddError 方法,并将传递给您的服务以添加错误消息。
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
}
为 mvc 应用程序创建 ModelStateAdapter。
public class ModelStateAdapter : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ModelStateAdapter(ModelStateDictionary modelState)
{
_modelState = modelState;
}
public void AddError(string key, string errorMessage)
{
_modelState.AddModelError(key, errorMessage);
}
}
需要验证的服务调用将需要 IValidationDictionary
public class MyService
{
public void CreateDebitRequest(int userId, int cardId, decimal Amount, .... , IValidationDictionary validationDictionary)
{
if(userId == 0)
validationDictionary.AddError("UserId", "UserId cannot be 0");
}
}
然后,您将依赖于IValidationDictionary
而不是MVC,这也使您的解决方案可测试。
如果您需要在没有ModelStateDictionary
的应用程序中实现服务,您只需在用于保存错误的类上实现IValidationDictionary
接口。
控制器示例:
public ActionResult Test(ViewModel viewModel)
{
var modelStateAdapter = new ModelStateAdapter(ModelState);
_serviceName.CreateDebitRequest(viewModel.UserId, viewModel.CardId, ... , modelStateAdapter);
if(ModelState.IsValid)
return View("Success")
return View(viewModel);
}
这种方法的优点:
- 不依赖于调用库
- 可以模拟IValidationDictionary进行测试。
这种方法的缺点:
您需要将 IValidationDictionary 传递给要对其执行验证的每个方法,这些方法将返回给用户。
或
您需要在要验证的每个控制器操作中初始化服务的验证字典(如果您决定将 IValidationDictionary 作为私有字段)。