2009-08-30 49 views
9

我在某些情況下隱藏了我的頁面上的某些面板。使用DataAnnotations有條件驗證ASP.NET MVC模型的某些部分?

例如,如果選中「ShippingSameAsBilling」複選框,我可能會有'帳單地址'和'送貨地址',我不想驗證'送貨地址'。

我正在嘗試使用ASP.NET MVC 2的新DataAnnotations capabilities(預覽1)來實現此目的。

我需要防止「送貨地址」在未顯示時驗證,並且需要找到實現此目的的方式。我主要是說服務器端,而不是由using jquery

我該如何做到這一點?我有幾個想法,涉及到自定義模型綁定,但我目前的最佳解決方案如下。對此方法有何反饋?

回答

6

對於我使用這種方法CheckoutModel(大多數領域隱藏):

[ModelBinder(typeof(CheckoutModelBinder))] 
public class CheckoutModel : ShoppingCartModel 
{   
    public Address BillingAddress { get; set; } 
    public Address ShippingAddress { get; set; } 
    public bool ShipToBillingAddress { get; set; } 
} 

public class Address 
{ 
    [Required(ErrorMessage = "Email is required")] 
    public string Email { get; set; } 

    [Required(ErrorMessage = "First name is required")] 
    public string FirstName { get; set; } 

    [Required()] 
    public string LastName { get; set; } 

    [Required()] 
    public string Address1 { get; set; } 
} 

的自定義模型粘合劑消除了與「ShippingAddress」開頭,如果它發現任何領域的所有的ModelState錯誤。然後'TryUpdateModel()'將返回true。

public class CheckoutModelBinder : DefaultModelBinder 
    { 
     protected override void OnModelUpdated(ControllerContext controllerContext, 
               ModelBindingContext bindingContext) { 

      base.OnModelUpdated(controllerContext, bindingContext); 

      var model = (CheckoutModel)bindingContext.Model; 

      // if user specified Shipping and Billing are the same then 
      // remove all ModelState errors for ShippingAddress 
      if (model.ShipToBillingAddress) 
      { 
       var keys = bindingContext.ModelState.Where(x => x.Key.StartsWith("ShippingAddress")).Select(x => x.Key).ToList(); 
       foreach (var key in keys) 
       { 
        bindingContext.ModelState.Remove(key); 
       } 
      } 
     }  
    } 

任何更好的解決方案?

+2

好主意,但我不喜歡在添加它們之後從列表中刪除錯誤的想法。我寧願不添加他們在第一個地方。 – 2009-10-28 20:43:02

2

我可以看到你的困境。我正在尋找其他驗證解決方案,這些解決方案可能適用於給定模型對象上的多個屬性,甚至是來自對象圖中不同模型對象的許多屬性(如果您的運氣不足以驗證鏈接對象喜歡這個)。

IDataErrorInfo接口的侷限性是,僅當沒有任何屬性出現錯誤時,模型對象纔會滿足有效狀態。這就是說,一個有效的對象是其所有屬性也是有效的。但是,如果屬性A,B和C有效 - 那麼整個對象是有效的.. ,但也可以如果屬性A無效但B C,則該對象滿足有效性。我無法用接口/ DataAnnotations屬性來描述這個條件/規則。

所以我發現這個delegate approach。現在,在編寫本文時,MVC中許多有益的進展並不存在,但核心概念應該對您有所幫助。而不是使用屬性來定義對象的確認條件,我們創建一個驗證更復雜的要求委託功能,因爲他們授權我們可以重新使用它們。當然這更多的工作,但使用代表的意思是,我們應該可以寫在一個地方(也許服務層)的驗證規則碼一次店內所有驗證規則(庫爾位)甚至使用MVC 2 DefaultModelBinder自動調用驗證(沒有在我們的控制器動作檢查堆 - 就像斯科特的博客說,我們可以用DataAnnotations做「強類型UI輔助」標題之前,請參閱last paragraph)!

我敢肯定,你可以在上面的文章中提出一些建議,例如Func<T>Predicate<T>等匿名代表,併爲驗證規則編寫自定義代碼塊將啓用交叉屬性條件(例如您提到的條件如果您的ShippingSameAsBilling屬性爲true,那麼您可以忽略關於送貨地址的更多規則等)。

DataAnnotations用於對物體做簡單驗證規則真的很容易只有很少的代碼。但隨着您的需求發展,您需要驗證更復雜的規則。 MVC2模型綁定器中的新虛擬方法應該繼續爲我們提供將未來驗證發明集成到MVC框架中的方法。

2

確保您不想驗證的字段不會發布到操作中。我們只驗證實際發佈的字段。

編輯:(由提問者)

此行爲MVC2 RC2已經改變:

默認驗證系統驗證 整個模型的默認驗證 系統在ASP.NET MVC 1.0和 RC之前的ASP.NET MVC 2的預覽 2僅驗證 發佈到服務器的模型屬性。在MVC 2的ASP.NET 中,新行爲是所有 模型屬性在 模型驗證時進行驗證,而不管 是否發佈了新值。 依賴於 ASP.NET MVC 1.0行爲的應用程序可能需要 更改。有關 此更改的更多信息,請參閱Brad Wilson博客上的ASP.NET MVC中Input Validation vs. Model Validation條目 。

0

這是不相關的DataAnnotations但你看了Fluent Validation項目?它爲您提供了對驗證的良好的紋理控制,如果您有對象到對象的驗證,那麼這兩個對象的聚合對象將會讓您順利進行。

此外,它似乎已經與MVC一起構建,但它也有自己的「運行時」,以便您可以在其他.NET應用程序中使用它,這也是我的書中的另一個優點。

1

對於更復雜的情況,我將其從簡單的DataAnnotations移至以下值:Validation with visitors and extension methods。有一種方法

public IEnumerable<ErrorInfo> BrokenRules (Payment payment) 
{ 
    // snip... 
    if (string.IsNullOrEmpty (payment.CCName)) 
    { 
     yield return new ErrorInfo ("CCName", "Credit card name is required"); 
    } 
} 

驗證通過DataAnnotations通過名稱的屬性(我沒有ATM):

如果你想利用你的DataAnnotations,你會像下面取代的東西。

1

我創建了一個部分模型聯編程序,它只驗證提交的鍵。出於安全原因(如果我要更進一步),我會創建一個數據註釋屬性,標記允許哪些字段從模型中排除。然後,OnModelUpdated檢查字段屬性以確保不會發生不希望的下傳。

public class PartialModelBinder : DefaultModelBinder 
{ 
    protected override void OnModelUpdated(ControllerContext controllerContext, 
     ModelBindingContext bindingContext) 
    { 
     // default model binding to get errors 
     base.OnModelUpdated(controllerContext, bindingContext); 

     // remove errors from filds not posted 
     // TODO: include request files 
     var postedKeys = controllerContext.HttpContext.Request.Form.AllKeys; 
     var unpostedKeysWithErrors = bindingContext.ModelState 
      .Where(i => !postedKeys.Contains(i.Key)) 
      .Select(i=> i.Key).ToList(); 
     foreach (var key in unpostedKeysWithErrors) 
     { 
      bindingContext.ModelState.Remove(key); 
     } 
    }  
}