2011-03-13 43 views
1

也許這是不完全的解決方案,我需要的,但是這是我想做的事:如何填充在控制器的數據和避免的ModelState錯誤

我有一個公司的登記表,每家公司需要管理用戶。管理用戶可能會管理多個公司,因此在公司註冊表單中,您可以從下拉菜單中選擇一個現有用戶。

公司視圖模型看起來是這樣的:

public class CompanyViewModel { 
    [Required] 
    public string Name { get; set; } 
    // other properties... 
    public UserViewModel Administrator { get; set; } 
    public IEnumerable<UserViewModel> AvailableUsers { get; set; } 
} 

和用戶視圖模式是這樣的:

<div><input type="radiobutton" name="chooseuser" id="existing"/>Choose an Existing User:</div. 
<div>@Html.DropDownListFor(m => m.Administrator.Id, Model.AvailableUsers.Select(u => new SelectListItem { Text = string.Format("{0} - {1} {2}", u.UserName, u.FirstName, u.LastName), Value = u.Id.ToString() }), "<Choose existing user>", new { id = "existingusers" }) 
     </div> 

<div><input type="radiobutton" name="chooseuser" id="createnew"/>Create a new User:</div> 
<div><label>Username:</label> @Html.EditorFor(m => m.Administrator.UserName)</div> 

public class UserViewModel { 
    [Required] 
    public string UserName { get; set; } 
    [Required] 
    public string Password { get; set; } 
    // other properties... 
} 
在公司登記觀點

通過JavaScript,基於單選按鈕選擇下拉列表將被禁用,並顯示新的用戶窗體,或者隱藏新的用戶窗體並啓用下拉列表。

的問題是在控制器中保存的動作按下保存後,如果選擇現有用戶並沒有數據填寫在表格上ModelState.IsValid是假的。如果用戶選擇輸入新用戶,則驗證成功。

什麼是處理這個問題的最好方法?

一個選項是所有用戶的所有數據加載到數據結構在JavaScript中,當現有用戶下拉列表中值的變化,可以填充隱藏的「創建新的」表單域。但這看起來很蹩腳,因爲密碼將以純文本形式存在於html中。一旦新用戶被保存,我可以更加友好並使用ajax創建新的表單,並在原始表單上填充用戶標識,但是如果可能的話,我希望將其全部保留在一個表單中。

似乎喜歡我理想情況下能夠從數據庫中加載現有的用戶數據並在控制器中保存模型狀態保存操作,但手動編寫此代碼(即使使用反射)似乎馬虎。如果有內置的方法來做到這一點會很好。

任何想法?

謝謝。

回答

4

這是一個典型的場景,它完美地說明了聲明性驗證(又名數據註釋)的侷限性。爲了處理它,你可以編寫一個自定義驗證屬性,將應用於CompanyViewModel而不是單獨的屬性,並允許您執行基於用戶選擇哪個單選按鈕執行驗證邏輯(順便說一句,您將需要在您的視圖屬性模型將代表單選按鈕選擇)。模型驗證器的問題在於,您可能會遇到一些難以處理錯誤突出顯示的問題。

這就是爲什麼我使用FluentValidation.NET而不是數據註釋的原因之一。這使我可以使驗證邏輯遠離模型,並以一種必要的方式完成。它還允許我使用基於視圖模型上某些屬性值應用的條件驗證器(在這種情況下,這將是單選按鈕選擇)。

0

你可能要考慮一個自定義ModelBinder的。

以下是我的網站上的一些示例代碼 - 這是購物車結帳頁面的一部分 - 用戶可以輸入地址,但發送了美國StateCd,並且發送了非美國StateOrProvince。因此,我們查看該國家並刪除不適用的其他財產的任何模型錯誤。

我覺得這是非常相似,你描述(你有兩種情況需要不同的規則,但您要使用同一型號)。

這裏最重要的代碼是bindingContext.ModelState.Remove(...)從而消除模型的狀態,並允許IsValid返回true。

public class AddressModelBinder : DefaultModelBinder 
{ 
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     base.OnModelUpdated(controllerContext, bindingContext); 

     // get the address to validate 
     var address = (Address)bindingContext.Model; 

     // remove statecd for non-us 
     if (address.IsUSA) 
     { 
      address.StateOrProvince = string.IsNullOrEmpty(address.StateCd) ? null : CountryCache.GetStateName(address.StateCd); 
      bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateOrProvince"); 
     } 
     else 
     { 
      address.StateCd = null; 
      bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateCd"); 
     } 

     // update country 
     address.Country = CountryCache.GetCountry(address.CountryCode, true).Name; 

     // validate US zipcode 
     if (address.CountryCode == "US") 
     { 
      if (new Regex(@"^\d{5}([\-]\d{4})?$", RegexOptions.Compiled).Match(address.ZipOrPostal ?? "").Success == false) 
      { 
       bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".ZipOrPostal", "The value " + address.ZipOrPostal + " is not a valid zipcode"); 
      } 
     } 

     // all other modelbinding attributes such as [Required] will be processed as normal 
    } 
} 

注意:您需要在global.asax中註冊此模型綁定器。模型綁定組件非常聰明,可以讓您爲模型的任何部分創建不同的模型綁定器,只要它包含不同的對象。

ModelBinders.Binders[typeof(UI.Address)] = new AddressModelBinder(); 

希望這會有所幫助。我認爲這適用於你的情況。