为业务规则验证抛出 UserFriendlyException 和异常处理的替代方法

Alternative way for Throwing UserFriendlyException and Exception Handling for Business Rule Validation

考虑到抛出异常的成本,另一种方法是这样的:

public interface IValidationDictionary
{
    void AddError(string key, string message);
    bool IsValid { get; }
}

public class ModelStateWrapper : IValidationDictionary
{
    private ModelStateDictionary _modelState;

    public ModelStateWrapper(ModelStateDictionary modelState)
    {
        _modelState = modelState;
    }

    public void AddError(string key, string errorMessage)
    {
        _modelState.AddModelError(key, errorMessage);
    }

    public bool IsValid
    {
        get { return _modelState.IsValid; }
    }
}
public interface IApplicationService
{
    void Initialize(IValidationDictionary validationDictionary);
}
public interface IUserService : IApplicationService
{
    Task CreateAsync(UserCreateModel model);
}
public class UserService : IUserService
{
    private readonly IUnitOfWork _uow;
    private IValidationDictionary _validationDictionary;

    public UserService(IUnitOfWork uow)
    {
        _uow = uow ?? throw new ArgumentNullException(nameof(uow));
    }

    public void Initialize(IValidationDictionary validationDictionary)
    {
        _validationDictionary = validationDictionary ?? throw new ArgumentNullException(nameof(validationDictionary));
    }

    public Task CreateAsync(UserCreateModel model)
    {
        //todo: logic for create new user

        if (condition)
            //alternative: throw new UserFriendlyException("UserFriendlyMessage");
            _validationDictionary.AddError(string.Empty, "UserFriendlyMessage");

        if (other condition)
            //alternative: throw new UserFriendlyException("UserFriendlyMessage");
            _validationDictionary.AddError(string.Empty, "UserFriendlyMessage");
    }
}

public class UsersController : Controller
{
    private readonly IUserService _service;

    public UsersController(IUserService service)
    {
        _service = service ?? throw new ArgumentNullException(nameof(service));
        _service.Initialize(new ModelStateWrapper(ModelState));
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromForm]UserCreateModel model)
    {
        if (!ModelState.IsValid) return View(model);

        await _service.CreateAsync(model);

        //todo: Display ModelState's Errors
    }
}

考虑到输入验证(如验证 DTO)和业务规则验证之间存在差异 https://ayende.com/blog/2278/input-validation-vs-business-rules-validation

Input Validation for me is about validating the user input. Some people call "Name must not be empty" a business rule, I think about it as input validation. Business Rules validation is more complex, because a business rule for me is not "Name must not be empty", it is a definition of a state in the system that requires an action. Here is a definition of a business rule:

An order should be payed within 30 days, this duration can be extended, to a maximum of three times.

是否可以发送一些出现在应用程序服务方法逻辑之间的业务规则验证错误消息

Another approach

public class Result
{
    public bool Success { get; private set; }
    public string Error { get; private set; }
    public bool Failure { /* … */ }
    protected Result(bool success, string error) { /* … */ }
    public static Result Fail(string message) { /* … */ }
    public static Result<T> Ok<T>(T value) {  /* … */ }
}

public class Result<T> : Result
{
    public T Value { get; set; }
    protected internal Result(T value, bool success, string error)
        : base(success, error)
    {
        /* … */
    }
}

方法是命令,不能失败:

public void Save(Customer customer)

该方法是一个查询,不能失败:

public Customer GetById(long id)

该方法是一个命令,它可能会失败:

public Result Save(Customer customer)

该方法是一个查询,它可能会失败

public Result<Customer> GetById(long id)