2012-04-11 85 views
9

我已經做了大量的研究,包括這裏的SO,我似乎無法找到明確的方向。我目前有一個ASP.NET MVC3應用程序,它有一個位於存儲庫之上的服務層。如何將服務層驗證消息傳回給調用者?

在我的服務層,我具備的功能,如:

public class MyService{ 

    public void CreateDebitRequest(int userId, int cardId, decimal Amount, ....) 
    { 
    //perform some sort of validation on parameters, save to database 
    } 

    public void CreateCreditRequest(.....) 
    } 
     //perform some sort of validation on parameters, save to database 
    } 

    public void CreateBatchFile() 
    { 
     //construct a file using a semi-complex process which could fail 
     //write the file to the server, which could fail 
    } 


    public PaymentTransaction ChargePaymentCard(int paymentCardId, decimal amount) 
    { 
     //validate customer is eligible for amount, call 3rd party payments api call, 
     //...save to database, other potential failures, etc. 
    } 

} 

我見過的人說,參數驗證,是不是很特殊,所以拋出異常是不是很恰當。我也不喜歡傳遞一個參數,比如一個字符串,並檢查一個空值。我已經考慮實現一個ValidationDictionary類,並將它作爲任何給定服務類的屬性(它將包含一個IsValid布爾值和一個錯誤消息列表,並且可以在服務層中的任何給定函數調用後檢查,以查看事情去了)。在運行任何給定的功能後,我可以檢查ValidationDictionary狀態:

var svc = new MyService(); 
svc.CreateBatchFile(); 
if (svc.ValidationDictionary.IsValid) 
    //proceed 
else 
    //display values from svc.ValidationDictionary.Messages... 

我不喜歡這個的事情是,我將不得不更新每一個服務層函數調用,以避免它保留舊值(如果我選擇不將它用於許多或大多數函數,則在運行任何給定函數後,仍然會期望它具有有意義的值或空值)。我認爲另一件事是通過在ValidationDictionary每個函數調用可能有詳細的驗證信息,但後來我回使用out參數...

做任何你有建議?我似乎無法弄清楚這樣做的任何干淨方式。有時爲函數返回null是足夠的信息,但有時候我想多一些驗證信息傳遞給調用者。任何意見,將不勝感激!

編輯澄清: 我的服務層不知道它是一個正在使用它的MVC應用程序。服務層只具有某些公用函數,如CreateBatchFile()或AddDebitRequest()。有時,消費者(在這種情況下是一個控制器,但可能是別的東西)返回空就足以知道發生了什麼,有時消費者希望從服務層獲得更多信息(如果消費者是消費者,可以傳遞給ModelState一個控制器)。我怎麼從服務層本身冒出來呢?

回答

1

我用它被傳遞消息(或類集合)的陣列,每個元素有代碼,描述,友好消息的系統。我們過去只是檢查是否有任何東西。它的用戶界面和其他「服務」層,所有的異常都很好地抓住了,他們被翻譯成這些驗證規則之間偉大的工作......只是一個想法

1

屬於視圖和控制器的操作方法之間通過使用視圖模型對象。 ViewModel對象可以通過Validate(ValidationDictionary validationDictionary)方法處理驗證。

控制器將具有呼叫在服務層中的任何方法之前調用視圖模型對象上的驗證方法。這隻適用於http POST操作。然後

您的意見將不得不顯示的驗證消息。

此解決方案要求的視圖模型對象被控制器動作和視圖,但是時下主要由ModelBinder的MVC中處理之間通過。

你的控制器(HTTP POST)的行動將是這個樣子:

[HttpPost] 
public ActionResult Foo(BarViewModel viewModel) 
{ 
    viewModel.Validate(ValidationDictionary); 

    if (!ModelState.IsValid) 
    { 
     return View(viewModel); 
    } 

    // Calls to servicelayer 
} 

您在您的視圖模型驗證方法是這樣的:

public void Validate(ValidationDictionary validationDictionary) 
{ 
    if (SomeProperty.Length > 30) 
    { 
     validationDictionary.AddError("SomeProperty", "Max length is 30 chars"); 
    } 
} 
+0

嗯......我想我是在談論較低的水平。我們假設用戶已經按下了一個按鈕,並且視圖模型驗證沒有中斷。然後,我的控制器會調用服務層中的一個函數,並且該函數可能由於各種原因而失敗(或者視圖模型驗證比服務實際需要的更慷慨,所以當服務驗證params自身時失敗) 。我如何將這些信息從服務層傳遞迴控制器,甚至可以通過viewmodel或modelstate顯示出來? – Josh 2012-04-11 17:29:03

+1

我確保所有驗證邏輯都存在於視圖模型中。爲什麼你想驗證邏輯遍佈整個地方?輸入驗證屬於用戶界面模塊。如果服務層出現問題,您應該拋出異常,以提供具有更好錯誤消息的日誌記錄。然後,您的MVC應用程序可以根據異常/ http代碼重定向到友好的錯誤頁面。 – Marcus 2012-04-11 19:38:30

+0

假設我在服務層,並驗證傳入CreateDebitRequest(...)的日期將來不會超過三天。似乎我應該在服務層驗證這一點,除了我在鏈中更高的位置進行驗證之外。你會同意嗎?如果是這樣,那麼你認爲我應該在我的服務層執行驗證,但只是在驗證失敗時拋出異常?或者我應該不在服務層驗證這個日期(我會對依賴服務使用者感到緊張)?謝謝... – Josh 2012-04-11 19:55:14

9

這是我做的。有一個類用於驗證,而不是傳遞參數傳遞視圖模型。所以你的情況是這樣的,其中的ValidationResult僅僅是一個簡單的類W /成員名稱和的ErrorMessage屬性:

public class DebitRequestValidator{ 

    public IEnumerable<ValidationResult> Validate(DebitRequestModel model){ 

    //do some validation 
    yield return new ValidationResult { 
     MemberName = "cardId", 
     ErrorMessage = "Invalid CardId." 
    } 
    } 

}

然後創建一個控制器擴展方法,這些驗證結果複製到模型狀態。

public static class ControllerExtensions 
{ 
    public static void AddModelErrors(this ModelStateDictionary modelState, IEnumerable<ValidationResult> validationResults) 
    { 
     if (validationResults == null) return; 

     foreach (var validationResult in validationResults) 
     { 
      modelState.AddModelError(validationResult.MemberName, validationResult.ErrorMessage); 
     } 
    } 
} 

然後在你的控制器做類似

[HttpPost] 
public ActionResult DebitRequest(DebitRequestModel model) { 
    var validator = new DebitRequestValidator(); 
    var results = validator.Validate(model); 
    ModelState.AddModelErrors(results); 
    if (!ModelState.IsValid) 
    return View(model) 

    //else do other stuff here 
} 

然後在您的視圖可以顯示像正常的錯誤。

@Html.ValidationMessageFor(m => m.CardId) 
+0

我的服務層不知道它是一個正在使用它的MVC應用程序。它只是具有某些公共功能,如CreateBatchFile或AddDebitRequest。有時,返回null足以讓控制器知道發生了什麼,有時控制器會從服務層獲得更多信息(可能傳遞給ModelState以及其他什麼)。我怎麼從服務層本身冒出來呢? – Josh 2012-04-11 17:31:08

+0

@Josh - 此示例中的服務層不依賴於MVC,只有ValidationResult類,它只是我創建的常規類。你可以通過傳遞ModelState來做同樣的事情,但我認爲返回一個IEnumerable 是一個更好的方法,因爲它會更容易測試。 – 2012-04-11 19:30:41

+0

我在哪裏可以返回這個IEnumberable ?我明白驗證應該在某個層次上完成,然後才能進入服務函數調用,但是如果在服務層的實際功能中發生錯誤(例如在svc.CreateBatchFile()中)會發生什麼情況?我需要將一個空的IEnumerable作爲一個輸出參數傳遞給服務函數,然後在調用我的服務函數後檢查它嗎?例如:svc.CreateBatchFile(out validationResults)?謝謝 – Josh 2012-04-11 19:44:39

1

如果你只是在做視圖模型驗證,FluentValidation是個很好的書房。

如果您想將業務驗證作爲反饋添加到用戶,您可以使用適配器模式,它會給你想要的。

創建一個接口(IValidationDictionary或類似的東西)。此接口將定義一個AddError方法,並將傳遞給您的服務以添加錯誤消息。

public interface IValidationDictionary 
{ 
    void AddError(string key, string errorMessage); 
} 

爲您的mvc應用程序創建一個ModelStateAdapter。需要確認將要求IValidationDictionary

public class MyService 
    {   
     public void CreateDebitRequest(int userId, int cardId, decimal Amount, .... , IValidationDictionary validationDictionary) 
      { 
       if(userId == 0) 
        validationDictionary.AddError("UserId", "UserId cannot be 0"); 
      } 
    } 

那麼你將不得不依賴於IValidationDictionary但不能在MVC這樣也會使您的解決方案可測試

public class ModelStateAdapter : IValidationDictionary 
{ 
    private ModelStateDictionary _modelState; 

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

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

你的服務電話。

如果您需要在沒有ModelStateDictionary的應用中實現服務,那麼您只需在用於保存錯誤的類上實現IValidationDictionary接口。

控制器例如:

public ActionResult Test(ViewModel viewModel) 
{ 
    var modelStateAdapter = new ModelStateAdapter(ModelState); 
    _serviceName.CreateDebitRequest(viewModel.UserId, viewModel.CardId, ... , modelStateAdapter); 

    if(ModelState.IsValid) 
     return View("Success") 

    return View(viewModel); 
} 

這種方法的Pro的:

  • 在調用庫
  • 這是可能的嘲笑IValidationDictionary做檢查無依賴性。

反對的這種做法:

  • 您需要IValidationDictionary傳遞給你想要做對的將是返回給用戶驗證每一個方法。

    或者

    你需要初始化服務的驗證字典(如果您決定IValidationDictionary作爲一個私有字段),您要驗證對每個控制器動作。