【问题标题】:Best practice for validating complex cases in ASP.NET/MVC?在 ASP.NET/MVC 中验证复杂案例的最佳实践?
【发布时间】:2013-11-08 16:39:29
【问题描述】:

我们总是被告知Controller 应该是瘦的,并且验证应该在Model 中完成,而不是Controller。但请考虑以下示例。

这是一个简单的ModelController,用于从编辑屏幕处理POST,我们可以在其上编辑Person 对象。

public class PersonEditModel
{         
     [Required(ErrorMessage = "No ID Passed")]
     public int ID { get; set; }

     [Required(ErrorMessage = "First name Required")]
     [StringLength(50,ErrorMessage = "Must be under 50 characters")]
     public string FirstName { get; set; }

     [Required(ErrorMessage = "Last name Required")]
     [StringLength(50,ErrorMessage = "Must be under 50 characters")]
     public string LastName { get; set; }
}

public class PersonController : Controller
{
    // [HttpGet]View, [HttpGet]Edit Controller methods omitted for brevity

    [HttpPost]
    public ActionResult Edit(PersonEditModel model)
    {
        // save changes to the record 
        return RedirectToAction("View", "Person", new { ID = model.ID});
    }
}

Model 在这里执行两种验证。它验证 FirstNameLastName,但它验证用于访问我们希望更改的记录的私钥 (ID)。是否也应该在Model 中进行此验证?

如果我们想要扩展验证(正如我们应该做的那样)以检查该记录是否存在怎么办?

通常,我会在控制器中验证这一点:

[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
    using(DatabaseContext db = new DatabaseContext())
    {
         var _person = db.Persons.Where(x => x.ID == model.ID);
         if(_person == null)
         {
             ModelState.AddError("This person does not exist!");
             // not sure how we got here, malicious post maybe. Who knows. 
             // so since the ID is invalid, we return the user to the Person List
             return RedirectToAction("List", Person");
         }
         // save changes
    }
    // if we got here, everything likely worked out fine
    return RedirectToAction("View", "Person", new { ID = model.ID});
}

这是不好的做法吗?我是否应该检查模型中某种复杂的自定义验证方法中是否存在记录?我应该把它完全放在其他地方吗?

更新

在相关说明中。 ViewModel 是否应该包含填充数据的方法?

哪些是更好的做法 - 这个

public class PersonViewModel
{    
    public Person person { get; set; }

    public PersonViewModel(int ID){
        using(DatabaseContext db = new DatabaseContext())
        {
             this.person = db.Persons.Where(x => x.ID == ID);
        }
    }
}

[HttpPost]
public ActionResult View(int ID)
{
    return View("View", new PersonViewModel(ID));
}

还是这个?

public class PersonViewModel
{    
    public Person person { get; set; }
}

[HttpPost]
public ActionResult View(int ID)
{
    PersonViewModel model = new PersonViewModel();  
    using(DatabaseContext db = new DatabaseContext())
    {
         model.person = db.Persons.Where(x => x.ID == ID);
    }
    return View("View", model);
}

【问题讨论】:

  • 你在最后一个例子中所做的并不是我所说的验证。 PersonEditModel 仍然有效。问题是这个人不存在。这是一个不同的错误,并且在控制器中得到了正确处理。
  • 看起来不错,没有问题。如果有人说不,请询问原因:)
  • 那么这个应该在控制器中吗?
  • 我没有发现任何问题,我就是这样做的。但我可能是错的。我会在另一个函数(FindPersonByID)中替换查找人员代码。然后你可以在控制器中调用它并抛出异常,否则调用 save 方法。
  • 这对CodeReview来说不是一个更好的话题吗?

标签: c# asp.net asp.net-mvc validation


【解决方案1】:

出于所有目的,我通常更喜欢FluentValidation。它还有一个 Nuget 可以在 VS 中开箱即用地安装它。

来自here 的示例验证码:

using FluentValidation;

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;

看到了吗??使用 Fluent Validation 和干净的方法来验证任何类型的模型都非常容易。可以考虑通过FluentValidation Documentation.

在哪里验证?

假设你有一个模型如下:

public class Category
{
    public int ID { get; set; }
    public string Name { get; set; }
    virtual public ICollection<Image> Images { get; set; }
}

然后,您将在类似的类库中定义另一个验证器模型,或者最好是一个新的类库来处理项目中所有模型的验证。

public class CategoryValidator : AbstractValidator<Category>
{
    public CategoryValidator()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("Category name is required.");
    }
}

因此,您可以在单独的验证器模型中执行此操作,使您的方法和域模型尽可能干净。

【讨论】:

  • 问题是关于在哪里进行验证。
  • 我以前使用过 FluentValidation,它确实有助于处理更复杂的情况,但它并不能真正回答问题。检查模型中是否存在记录是好主意还是坏主意?
  • @roryok 我认为您之前没有真正使用过 FluentValidation,因为 FluentValidation 开箱即用,如果您为特定模型设置了验证器,那么它将自动调用,前提是您已注入 IValidator在您的界面中。
  • @Murali,@roryok 更新了我的答案
【解决方案2】:

当我们谈论Model 时,它包括您的 DAL 和您的业务层。对于小型应用程序或演示,在控制器中看到这种代码并不罕见,但通常您应该将该角色赋予业务或数据层:

[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
    // Validation round one, using attributes defined on your properties
    // The model binder checks for you if required fields are submitted, with correct length
    if(ModelState.IsValid)
    {
        // Validation round two, we push our model to the business layer
        var errorMessage = this.personService.Update(model);

        // some error has returned from the business layer
        if(!string.IsNullOrEmpty(errorMessage))
        {
            // Error is added to be displayed to the user
            ModelState.AddModelError(errorMessage);
        }
        else
        {
            // Update successfull
            return RedirectToAction("View", "Person", new { ID = model.ID});
        }
    }

    // Back to our form with current model values, as they're still in the ModelState
    return View();
}

这里的目标是将控制器从业务逻辑验证和数据上下文的使用中解放出来。它推送提交的数据并在发生错误时得到通知。我使用了一个字符串变量,但你可以随意实现错误管理。发展您的业务规则根本不会影响您的控制器。

【讨论】:

    【解决方案3】:

    这绝对没有错。当涉及到向用户显示哪个视图时,您的控制器负责指导控制流。这样做的一部分是确保视图获得处于可用状态的模型。

    控制器不关心模型是什么,或者模型包含什么,但它确实关心它是否有效。这就是ModelState.IsValid 如此重要的原因,因为控制器不必知道如何执行验证或直接使模型有效的原因。通常,需要在ModelState.IsValid 之后进行的任何验证都可以推送到应用程序的另一层,这再次强制执行关注点分离。

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-03
      • 2010-11-28
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      相关资源
      最近更新 更多