MVC 中的验证规则和业务规则

Validation Rules and Business Rules in MVC

我有一个 MVC 网络项目。根据最佳实践,添加我的验证规则和业务规则的正确位置在哪里?

验证规则 将是必填字段和必填格式。

业务规则将是"this email is already taken in the database"

这是我目前在 注册模型 中的做法:

public class RegisterModel : IValidatableObject
{
    [Display(Name = "Email address")]
    [Required(ErrorMessage = "The email address is required")]
    [EmailAddress(ErrorMessage = "Invalid Email Address")]
    public string Email { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var retVal = new List<ValidationResult>();
        using (var entity = new AcademicUniteDatabaseEntities())
        {
            if (entity.UserProfiles.Any(x => x.UserName == this.Email))
            {
                retVal.Add(new ValidationResult("Email already exist", new List<string> { "Email" }));
            }
        }

        return retVal;
    }
}

这是我的注册控制器

    public ActionResult Register()
    {
        var model = new RegisterModel();
        return this.View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Register(RegisterModel model)
    {
        if (!this.ModelState.IsValid)
        {
            return this.View(model);
        }

        model.CreateAccount();
        return this.View("WaitConfirmEmail");
    }

为什么我要这样做

  1. 当我在控制器中检查 ModelState.IsValid 时,它会检查电子邮件的格式以及它是否已存在于数据库中。我不在我的控制器中调用任何数据库,只在我的模型中调用。 (这是最佳做法吗?)
  2. 它还将 "Email already exist" 验证结果绑定到我的电子邮件 属性 以便我可以在我的视图中显示验证结果。

这是最佳做法吗?

  1. 这是在 MVC 中添加业务规则的正确方法吗?
  2. 为什么或为什么不?
  3. 如果这不是最佳做法,您能否提供一个示例,说明如何最好地编写此注册模型来检查业务规则(如果电子邮件已经存在)?

一种替代方法是创建自定义验证器属性。使用您的解决方案,您最终可能会得到非常大的模型。另一种选择是创建可以插入控制器的静态助手。这里没有正确答案,这只是您想要组织代码的方式。我的偏好是将业务逻辑分散到可以即插即用的自定义属性中。这样你就可以无缝重构。

使用注释来验证你的模型对于需要验证的小型应用程序来说很好,但是当你的验证规则开始变得越来越复杂并且你的模型开始变得越来越复杂时,我建议你看看像这样的库Fluent Validation

通过属性验证模型的问题是它可能非常混乱,您将开始遭受大量重复的困扰;使用像 Fluent Validation 这样的库的好处是:

  • 让您的模型保持干净整洁
  • 由于 DI 支持,创建更易于维护和可重用的验证规则。
  • 不像 ModelState.IsValid 那样绑定到 UI,因此可以在您的业务逻辑层等中重复使用。

典型的 Fluent Validation 规则示例:

public class CustomerValidator: AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        RuleFor(customer => customer.Surname).NotEmpty();
        RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
        RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
        RuleFor(customer => customer.Address).Length(20, 250);
        RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
    }

    private bool BeAValidPostcode(string postcode)
    {
        // custom postcode validating logic goes here
    }
}

Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);

bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;