在具有存储库,服务层和使用模型绑定器的ASP.Net MVC场景中应该进行验证?

时间:2009-04-24 02:55:11

标签: asp.net-mvc validation

相关: What’s the best way to implement field validation using ASP.NET MVC?

让我们假设一个包含以下项目的解决方案:

Foo; // the MVC web project
Foo.Models;
Foo.Repositories;
Foo.Services;

Foo.Models是包含所有实体的应用程序的域,无论使用EF,NH,POCO还是其他什么都无关紧要。这是一个例子:

public class User
{
    public string Username { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

Foo.Repositories中有一个UserRepository,在Foo.Services中有一个UserService

在Web应用程序中,让我们考虑一下如下的模型绑定器:

public class UserBinder : DefaultModelBinder
{
    //...
}

我在验证的位置上看到了三种不同的选项:

  • Foo.Models中,如下所示:

    public class User
    {
        public string Username { get; set; }
    
        public string Email { get; set; }
    
        public string Password { get; set; }
    
        public ICollection<KeyValuePair<string, string>> ValidateErrors()
        {
            //Validate if Username, Email and Password has been passed
        }
    }
    
  • Foo.Services喜欢:

    public class UserService
    {
        public ICollection<KeyValuePair<string, string>> ValidateErrors()
        {
            //Validate if Username, Email and Password has been passed
        }
    }
    
  • 在模型活页夹内的Foo中:

    public class UserBinder : DefaultModelBinder
    {
        protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var user = (User)bindingContext.Model;
    
            // validate everything here
    
            base.OnModelUpdated(controllerContext, bindingContext);
        }
    }
    

另外需要注意的是,考虑前两个选项[模型和服务],还有另一个决定:ValidateErrors方法可以直接在控制器上或在Binder内部调用。

我对该方案有2个问题:

  1. 验证应该是:

    • 在从控制器调用的模型中?
    • 在从活页夹调用的模型中?
    • 在从控制器调用的服务中?
    • 在从活页夹中调用服务?
    • 直接在Binder?
    • 还有其他想法吗?
  2. 以上所有场景都讨论了用户创建。但是用户登录呢? 假设用户使用用户名和密码登录应用程序,因此不需要验证电子邮件。 这个验证应该在哪里?

    • 在从控制器调用的模型中?
    • 在从控制器调用的服务中?
    • 还有其他想法吗?

6 个答案:

答案 0 :(得分:1)

在我的意见中查看ASP.NET MVC Contact Manager Sample Application它有一个非常好的架构

http://www.asp.net/learn/mvc/tutorial-26-cs.aspx'> http://www.asp.net/learn/mvc/tutorial-26-cs.aspx

答案 1 :(得分:1)

我非常喜欢从控制器调用验证并让验证例程返回ActionResult,以便控制器可以知道如何处理结果。

答案 2 :(得分:0)

对于它的价值,这就是我在当前项目中所追求的:

我有ModelsRepositories(如果愿意,可以称之为Services)和ViewModels。我试图避免编写自定义模型绑定器,因为(a)它很无聊和(b)一个奇怪的地方进行验证,恕我直言。对我来说,模型绑定器只是从请求中提取项目并将它们推送到对象中。例如,PHP将项目从标题中提取到$ _POST数组时不进行任何验证;这就是我们将数组插入其中关注其内容的事情。

我的Model个对象通常不允许自己进入无效状态。这意味着在构造函数期间传入必需的参数,如果尝试使用无效值设置属性,属性将抛出异常。而且,一般来说,我尝试将Model对象设计为不可变的。例如,我有一个Address对象,用于通过AddressBuilder对象构建的邮件地址,通过检查可以从中检索的AddressScheme来查看给定国家/地区的字段要求。 AddressSchemeRepository。唷。但我认为这是一个很好的例子,因为它在概念上很简单(“验证邮件地址”)并使其在现实世界中使用变得复杂(“我们接受来自30多个国家/地区的地址,并且这些格式规则位于数据库中,而不是在我的代码“)。

由于构造这个Model对象是一种痛苦 - 它也应该是,因为对于加载到它中的数据非常特别 - 我有一个,比方说,{{1}我的视图所绑定的对象。 InputAddressViewModel实现了InputAddressViewModel,这样我就可以让ASP.NET MVC的IDataErrorInfo自动向DefaultModelBinder添加错误。对于我提前知道的简单验证例程(电话号码格式,所需的名字,电子邮件地址格式),我可以在ModelState中实现这些。

拥有视图模型的另一个好处是,因为它是为特定视图量身定制的,所以您的真实模型更具可重用性,因为它不需要做任何奇怪的让步以使其适合UI显示(例如,需要实施InputAddressViewModelINotifyPropertyChanged或任何混乱)。

在我与实际Serializable中的AddressScheme互动之前,我不会知道有关地址的其他验证错误。这些错误将由控制器编排到Model中。类似的东西:

ModelState

public ActionResult InputAddress(InputAddressViewModel model) { if (ModelState.IsValid) { // "Front-line" validation passed; let's execute the save operation // in the our view model var result = model.Execute(); // The view model returns a status code to help the // controller decide where to redirect the user next switch (result.Status) { case InputAddressViewModelExecuteResult.Saved: return RedirectToAction("my-work-is-done-here"); case InputAddressViewModelExecuteResult.UserCorrectableError: // Something went wrong after we interacted with the // datastore, like a bogus Canadian postal code or // something. Our view model will have updated the // Error property, but we need to call TryUpdateModel() // to get these new errors to get added to // the ModelState, since they were just added and the // model binder ran before this method even got called. TryUpdateModel(model); break; } // Redisplay the input form to the user, using that nifty // Html.ValidationMessage to convey model state errors return View(model); } } 似乎令人厌恶,但我认为这是有道理的:视图模型只是一个普通的老类,并且不了解switchRequest 。这使得视图模型的逻辑易于单独测试而无需借助于模拟,并通过以在网站上有意义的方式解释模型的结果来保留控制器代码,以及 control - 它可以重定向,它可以设置cookie等。

HttpContext的{​​{1}}方法看起来像(有些人会坚持将此代码放入控制器可以调用的Service对象中,但对我来说,视图模型会做的很多对数据进行重新分析以使其适合将其放在此处的真实模型:)

InputAddressViewModel

这有意义吗?这是最佳做法吗?我不知道;它肯定是冗长的;这是我在考虑这个问题后的两周内刚刚提出的问题。我认为你将要进行一些重复验证 - 你的用户界面不能完全愚蠢,在将它们提交给你的模型/存储库/服务之前不知道哪些字段是必需的无论如何 - 否则表格可以简单地自我生成。

我应该补充一点,对此的推动是我总是有点厌恶微软的“设置一个属性 - &gt;验证一个属性”的心态,因为在现实中没有任何东西能像现在这样运作。并且总是最终导致无效对象持久存在,因为有人忘记在前往数据存储的路上调用Execute()或其他某些对象。所以拥有一个视图模型的另一个原因是它适应了这个让步,所以我们得到了大量的CRUD工作,从请求中提取项目,模型状态中的验证错误等,很容易没有损害我们模型本身的完整性。如果我手中有 public InputAddressViewModelExecuteResult Execute() { InputAddressViewModelExecuteResult result; if (this.errors.Count > 0) { throw new InvalidOperationException( "Don't call me when I have errors"); } // This is just my abstraction for clearly demarcating when // I have an open connection to a highly contentious resource, // like a database connection or a network share using (ConnectionScope cs = new ConnectionScope()) { var scheme = new AddressSchemeRepository().Load(this.Country); var builder = new AddressBuilder(scheme) .WithCityAs(this.City) .WithStateOrProvinceAs(this.StateOrProvince); if (!builder.CanBuild()) { this.errors.Add("Blah", builder.Error); result = new InputAddressViewModelExecuteResult() { Status = InputAddressViewModelExecuteStatus .UserCorrectableError }; } else { var address = builder.Build(); // save the address or something... result = new InputAddressViewModelExecuteResult() { Status = InputAddressViewModelExecuteStatus.Success, Address = address }; } } return result; } 个物体,我知道它很好。如果我手头有IsValid个对象,我知道我需要调用它的Address方法来获取该黄金InputAddressViewModel对象。

我期待着阅读其他一些答案。

答案 3 :(得分:0)

经过大量研究后,我想我得到了问题的答案,所以我决定分享。

验证码应该在Model上。 根据“瘦控制器,胖模型”的想法,并考虑模型将知道它需要验证什么。

例如,假设我决定在其他解决方案中使用Foo.Models,但我决定不使用任何其他项目,并且验证在其他项目中。 在这种情况下,我将不得不重新编码整个验证,这完全是浪费时间,对吗?

行。验证代码必须在模型中,但应该在哪里调用?

必须在将数据保存到数据库或文件的地方调用此验证。 在提议的场景中我将存储库视为域,然后我们应该考虑在更改保存之前进行验证[在此示例中我使用的是实体框架,但它没有必要,只是为了显示]:


public class UserRepository : IRepository<User>
{
    public void Create(User user)
    {
        user.Validate();

        var db = dbFooEntities();

        db.AddToUsers(user);
        db.SaveChanges();
    }
}

根据MS建议,模型验证应引发异常,控制器必须使用发现的错误填充ModelState [我会在完成应用程序后尝试使用示例代码更新此答案]。

有了这个,我们就可以回答问题#1。

关于登录验证问题#2怎么样?

由于登录不是您持久保存数据的情况,因此在此情况下,登录是服务,因此验证应保留在服务上。

所以,问题的答案是:

  1. 在从 REPOSITORY 调用的模型中[由控制器调用]

  2. 在从控制器调用的服务中

答案 4 :(得分:0)

这非常有趣,它帮助我决定在何处进行验证。 目前我对每个实现“验证”方法的模型感觉最大,该方法是从存储库或服务调用的。

但是,验证所选用户名是否唯一? 该代码应该在User模型内部,还是在UserService类内部,或者在UserRepository类中?

如果唯一性验证应该在User模型中,那么User模型应该可以访问UserService或UserRepository类。这样可以,还是反对任何“最佳实践”模式?

例如:

class User
{
  string Username { get; set; }
  string Email { get; set; }
  string Password { get; set; } // hashed and salted of course :)

  IEnumerable<RuleViolation> Validate()
  {
    List<RuleViolation> violations = new List<RuleViolation>();
    IUserService service = MyApplicationService.UserService; // MyApplicationService is a singleton class, especialy designed so that the User model can access application services

    // Username is required
    if ( string.IsNullOrEmpty(Username) )
       violations.Add(new RuleViolation("Username", "Username is required"));

    // Username must be unique: Should uniqueness be validated here?
    else if( !service.IsUsernameAvailable(Username)
       violations.Add(new RuleViolation("Username", "Username is already taken!"));

    // Validate email etc...

    return violations;
  }
}

interface IUserRepository
{
  void Save(User item);
}

interface IUserService
{
  IUserRepository UserRepository { get; }
  void Save(User item);
}

class UserService : IUserService
{
  public UserService(IUserRepository userRepository)
  {
     this.UserRepository = userRepository;
  }

  IUserRepository UserRepository { get; private set}

  public void Save(User user)
  {
     IEnumerable<RuleViolation> violations = user.Validate();

     if(violations.Count() > 0)
         throw new RuleViolationException(violations); // this will be catched by the Controller, which will copy the violations to the ModelState errors collection. But the question is, should we validat the user here, or in the UserRepository class?

      UserRepository.Save(user);
  }
}

class UserRepository : IUserRepository
{
   void Save(User item)
   {
      IEnumerable<RuleViolation> violations = user.Validate();

     if(violations.Count() > 0)
         throw new RuleViolationException(violations); // this will be catched by the Controller, which will copy the violations to the ModelState errors collection. But the question is, should we validate the user here, or in the UserService class?

      UserRepository.Save(user);

   }
}

我的猜测是验证应该尽可能接近模型。所以我要说UserRepository应该是负责验证它被添加的模型的人。

对我来说最重要的问题是:用户模型是否应该了解IUserService / IUserRepository接口,以便它可以验证用户名的唯一性? 或者IUserService服务应该验证唯一性吗?

我很好奇你对此的看法!

答案 5 :(得分:0)

我正在将DataAnnotations属性与MVC模型绑定器结合使用来进行验证,非常棒。由于我将用户输入视为命令视图模型,因此它是保持域清除外部问题的最简洁方法。

http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html

这也让我可以利用LosTechies.com的AutoForm:

http://www.lostechies.com/blogs/hex/archive/2009/06/17/opinionated-input-builders-part-8-the-auto-form.aspx

我希望MVC 2中的客户端验证工具VS 2010也能利用这些属性。

所以我现在以狂暴的速度掏出用户输入视图模型和命令,并将它们不仅绑定到AutoForm功能,而且还绑定到我自己的自定义UI模板,以便从这些属性中获取AutoGrid和AutoOutput。

没有什么比说:

更好
Html.AutoForm(Model);

或者

Html.AutoGrid(Model.Products);

以非常干燥和正交的方式获得验证和html生成。我的控制器很轻,我的域是原始的,我的时间没有被在每个具有FirstName属性的对象上写入相同的if(string.IsNullOrEmpty())方法。

对我而言,这种方法并不像其他人所描述的那样具有“哲学性”。我正在尝试对MVC开发非常务实,我从这些位中获得了大量的优势。