9

我做了很多研究,包括这里的 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.
    }

}

我见过有人说参数验证不是很例外,所以抛出异常不是很合适。我也不喜欢传入输出参数(例如字符串)并检查空值的想法。我已经考虑实现一个 ValidationDictionary 类,并使其成为任何给定服务类的属性(它将包含一个 IsValid 布尔值和一个错误消息列表,并且可以在服务层中的任何给定函数调用之后检查以查看如何事情过去了)。运行任何给定函数后,我可以检查 ValidationDictionary 状态:

var svc = new MyService();
svc.CreateBatchFile();
if (svc.ValidationDictionary.IsValid)
    //proceed
else
   //display values from svc.ValidationDictionary.Messages...

我不喜欢这一点的是,我必须为每个服务层函数调用更新它,以避免让它保留旧值(如果我选择不将它用于许多或大多数函数,人们仍然会期待它在运行任何给定函数后具有有意义或空值)。我考虑的另一件事是为每个可能具有详细验证信息的函数调用传入 ValidationDictionary,但随后我又回到使用 out 参数...

大家有什么建议吗?我似乎想不出任何干净的方法来做到这一点。有时为函数返回 null 就足够了,但有时我希望将更多的验证信息传递回调用者。任何意见,将不胜感激!

编辑澄清: 我的服务层不知道它是一个 MVC 应用程序正在使用它。服务层只有某些公共函数,例如 CreateBatchFile() 或 AddDebitRequest()。有时返回 null 足以让消费者(在这种情况下是控制器,但可能是其他东西)知道发生了什么,有时消费者希望从服务层获得更多信息(如果消费者是,则可能传递给 ModelState控制器)。我如何从服务层本身冒泡这个?

4

4 回答 4

10

这就是我所做的。有一个用于验证的类,而不是传递参数传递一个视图模型。所以在你的情况下是这样的,其中 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)
于 2012-04-11T16:43:46.860 回答
1

使用在视图和控制器操作方法之间传递的 ViewModel 对象。ViewModel 对象可以通过Validate(ValidationDictionary validationDictionary)方法处理验证。

在调用服务层中的任何方法之前,控制器必须调用 ViewModel 对象的 Validate 方法。这应该只对 http POST 操作是必需的。

然后,您的视图将必须显示验证消息。

此解决方案要求视图模型对象在控制器操作和视图之间传递,但现在主要由 MVC 中的 ModelBinder 处理。

您的控制器(http post)操作将如下所示:

[HttpPost]
public ActionResult Foo(BarViewModel viewModel)
{
    viewModel.Validate(ValidationDictionary);

    if (!ModelState.IsValid)
    {
        return View(viewModel);
    }

    // Calls to servicelayer
}

ViewModel 中的 Validate 方法如下所示:

public void Validate(ValidationDictionary validationDictionary)
{
    if (SomeProperty.Length > 30)
    {
        validationDictionary.AddError("SomeProperty", "Max length is 30 chars");
    }
}
于 2012-04-11T16:27:06.597 回答
1

如果你只是在做 ViewModel Validation,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 作为私有字段)。

于 2014-05-09T06:20:48.937 回答
1

我使用了一个传递消息数组(或类集合)的系统,每个元素都有代码、描述和友好消息。我们过去只是简单地检查是否有任何东西。它在 UI 和另一个“服务”层之间运行良好,所有异常都被很好地捕获,它们被翻译成这些验证规则......只是一个想法

于 2012-04-11T16:20:10.323 回答