簡體   English   中英

在具有存儲庫,服務層和使用模型綁定器的ASP.Net MVC場景中應該進行驗證?

[英]Where should be the validation in a ASP.Net MVC scenario having Repository, Service Layer and using Model Binder?

相關: 使用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. 驗證應該是:

    • 在從控制器調用的模型中?
    • 在從粘合劑調用的模型中?
    • 在從控制器調用的服務中?
    • 在從綁定器調用服務?
    • 直接在賓德?
    • 還有其他想法嗎?
  2. 以上所有場景都討論了用戶創建。 但是用戶登錄呢? 假設用戶使用用戶名和密碼登錄應用程序,因此不需要驗證電子郵件。 這個驗證應該在哪里?

    • 在從控制器調用的模型中?
    • 在從控制器調用的服務中?
    • 還有其他想法嗎?

查看ASP.NET MVC Contact Manager示例應用程序 ,我認為它具有非常好的架構

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

我非常喜歡從控制器調用驗證並讓驗證例程返回ActionResult,以便控制器可以知道如何處理結果。

這非常有趣,它幫助我決定在何處進行驗證。 目前我對每個實現“驗證”方法的模型感覺最大,該方法是從存儲庫或服務調用的。

但是,驗證所選用戶名是否唯一? 該代碼應該在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服務應該驗證唯一性嗎?

我很好奇你對此的看法!

我正在使用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開發非常務實,我從這些位中得到了大量的幫助。

對於它的價值,這就是我在當前項目中所追求的:

我有ModelsRepositories (如果你願意,可以稱之為Services )和ViewModels 我試圖避免編寫自定義模型綁定器,因為(a)它很無聊和(b)一個奇怪的地方進行驗證,恕我直言。 對我來說,模型綁定器只是從請求中提取項目並將它們推送到對象中。 例如,PHP將項目從標題中提取到$ _POST數組時不進行任何驗證; 這是我們將數組插入其中關注其內容的事情。

我的Model對象通常不允許自己進入無效狀態。 這意味着在構造函數期間傳入必需的參數,如果嘗試使用無效值設置屬性,屬性將拋出異常。 而且,一般來說,我嘗試將我的Model對象設計為不可變的。 例如,我有一個Address郵寄被構造與地址對象AddressBuilder通過檢查一個與外表在對於給定的國家的現場要求對象AddressScheme可從檢索AddressSchemeRepository 唷。 但我認為這是一個很好的例子,因為它在概念上很簡單(“驗證郵件地址”)並使其在現實世界中使用變得復雜(“我們接受來自30多個國家/地區的地址,並且這些格式規則位於數據庫中,而不是在我的代碼“)。

因為構造這個Model對象是一種痛苦 - 它應該是,因為它是非常特別的加載到它的數據 - 我有一個,例如,我的視圖綁定到的InputAddressViewModel對象。 InputAddressViewModel實現了IDataErrorInfo這樣我就可以獲得ASP.NET MVC的DefaultModelBinder來自InputAddressViewModel ModelState添加錯誤。 對於我提前知道的簡單驗證例程(電話號碼格式,所需的名字,電子郵件地址格式),我可以在InputAddressViewModel實現這些。

擁有視圖模型的另一個好處是,因為它可以無恥地為特定視圖量身定制,所以您的真實模型更具可重用性,因為它不需要做任何奇怪的讓步以使其適合UI顯示(例如,需要實現INotifyPropertyChangedSerializable或任何混亂)。

關於我不知道的地址的其他驗證錯誤,直到我在實際ModelAddressScheme交互。 那些錯誤將是控制器編排到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);
    }
}

switch可能看起來令人厭惡,但我認為這是有道理的:視圖模型只是一個普通的舊類,並且不了解RequestHttpContext 這使得視圖模型容易隔離測試,而不訴諸嘲諷的邏輯和離開控制器代碼留下來,好了,通過的方式,使Web站點上的意義解釋模型的結果控制 -它可以重定向,它可以設置cookie等

並且InputAddressViewModelExecute()方法看起來像(有些人會堅持把這個代碼放到控制器會調用的Service對象中,但對我來說,視圖模型會做很多的數據處理以使其適合把它放在這里是有意義的真實模型):


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或其他一些東西。 擁有視圖模型的另一個原因是它使自己適應這種特許權,所以我們得到了很多CRUD工作,從請求中提取項目,模型狀態中的驗證錯誤等, 不必損害我們模型的完整性本身。 如果我手中有一個Address對象,我知道它很好。 如果我手頭有一個InputAddressViewModel對象,我知道我需要調用它的Execute()方法來獲取那個黃金的Address對象。

我期待着閱讀其他一些答案。

經過大量的研究后,我想我得到了問題的答案,所以我決定分享。

驗證代碼應該在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. 在從控制器調用的服務中

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM