3

我知道你們大多數人會建議我應該使用特定於我使用的窗體的ViewModels,但我很好奇爲什麼我的子對象不能綁定到TryUpdateModel上。實體框架TryUpdateModel子對象?

@using (Html.BeginForm()) { 
    @Html.ValidationSummary(true) 
    <fieldset> 
     <legend>User</legend> 

     @Html.HiddenFor(model => model.UserId) 
    @Html.HiddenFor(model => model.PrimaryAddress.AddressId) 
    <div class="editor-label"> 
      @Html.LabelFor(model => model.PrimaryAddress.FirstName) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.PrimaryAddress.FirstName) 
      @Html.ValidationMessageFor(model => model.PrimaryAddress.FirstName) 
     </div> 

     <div class="editor-label"> 
      @Html.LabelFor(model => model.PrimaryAddress.LastName) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.PrimaryAddress.LastName) 
      @Html.ValidationMessageFor(model => model.PrimaryAddress.LastName) 
     </div> 

     <div class="editor-label"> 
      @Html.LabelFor(model => model.UserName) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.UserName) 
      @Html.ValidationMessageFor(model => model.UserName) 
     </div> 

     <div class="editor-label"> 
      @Html.LabelFor(model => model.Email) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.Email) 
      @Html.ValidationMessageFor(model => model.Email) 
     </div> 

     <div class="editor-label"> 
      @Html.LabelFor(model => model.IsApproved) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.IsApproved) 
      @Html.ValidationMessageFor(model => model.IsApproved) 
     </div> 

     <div class="editor-label"> 
      @Html.LabelFor(model => model.IsEmployee) 
     </div> 
     <div class="editor-field"> 
      @Html.EditorFor(model => model.IsEmployee) 
      @Html.ValidationMessageFor(model => model.IsEmployee) 
     </div> 

     <p> 
      <input type="submit" value="Save" /> 
     </p> 
    </fieldset> 
} 

而控制器代碼:

[HttpPost] 
public ActionResult Edit(int id, FormCollection form) 
{ 
    var user = Token.DB.Users.Include("PrimaryAddress").Single(x => x.UserId == id); 
    if (TryUpdateModel(user, new string[] { "UserName", "Email", "IsApproved", "IsEmployee", "PrimaryAddress.FirstName", "PrimaryAddress.LastName" })) 
    { 
     try 
     { 
      Token.DB.SaveChanges(); 
      return RedirectToAction("index"); 
     } 
     catch (Exception ex) 
     { 
      while (ex.InnerException != null) 
       ex = ex.InnerException; 
      if (ex.Message.ToLowerInvariant().Contains("unique")) 
       ModelState.AddModelError("UserName", "UserName already exists"); 
     } 
    } 
    return View(User); 
} 

代碼不拋出任何異常,它只是不填充表單中的user.PrimaryAddress.FirstName或user.PrimaryAddress.LastName。我想知道爲什麼?

我已經知道我可以用特定的ViewModel解決問題並在後臺映射信息。我也可以這樣做:

<!-- Edit.cshtml --> 
<div class="editor-field"> 
    @Html.EditorFor(model => model.PrimaryAddress.FirstName, null, "FirstName") 
    @Html.ValidationMessageFor(model => model.PrimaryAddress.FirstName) 
</div> 

<div class="editor-label"> 
    @Html.LabelFor(model => model.PrimaryAddress.LastName) 
</div> 
<div class="editor-field"> 
    @Html.EditorFor(model => model.PrimaryAddress.LastName, null, "LastName") 
    @Html.ValidationMessageFor(model => model.PrimaryAddress.LastName) 
</div> 

// UsersController.cs 
if (TryUpdateModel(user, new string[] { "UserName", "Email", "IsApproved", "IsEmployee"}) 
&& TryUpdateModel(user.PrimaryAddress, new string[] {"FirstName", "LastName" })) 

所以真正的問題是爲什麼它沒有在第一個例子中綁定?

+2

不知道爲什麼-1,明確指出我的問題,給了例子。我是否應該盲目地遵循MVC純粹主義的觀點特定模型的一切,而不理解發動機罩下面發生了什麼?感謝Darin解釋TryUpdateModel不支持「嵌套屬性」。 – Sam

回答

9

所以真正的問題是爲什麼它沒有在第一個例子中綁定?

您的問題的答案非常簡單:UpdateModel,TryUpdateModel或[Bind]屬性都不支持包含/排除屬性列表中的「嵌套屬性」。因此,請妥善做好事情並使用視圖模型。防範大規模財產分配攻擊只是爲什麼您應該使用視圖模型的數百萬個原因之一。那麼,您似乎已經通過執行第二個TryUpdateModel找到了解決方法,但是如果您在此域對象上有很多屬性,那麼您的控制器操作代碼可能會很快變成意大利麪條管道代碼。

+0

我並不是說我不想使用視圖模型;我試圖找出爲什麼嵌套的屬性沒有填充。我在網上看到的其他例子似乎暗示他們應該是。 我也很好奇你對「意大利麪條碼」的擔憂嗎?上面的代碼如何擔心你? 在相關說明中,你有沒有提到你提到的「質量屬性注入攻擊」?我嘗試搜索,並一直在依賴注入中獲得一堆無關的東西。 – Sam

+0

@Sam,抱歉,我的意思是「大量財產分配」。諸如RoR或ASP.NET MVC等框架使用某種模型綁定器映射到對象容易受到這種攻擊。例如,您有一個POST操作,它會在數據庫中創建一個新的用戶模型。所以你創建一個很好的表單,允許用戶填寫他的名字和密碼並創建按鈕。但不幸的是,您不遵循使用視圖模型的良好做法,並且您的POST操作直接將您的域用戶模型作爲參數。除了這個領域模型有一個IsAdmin布爾屬性... –

+0

...當然,表單上沒有相應的字段允許普通用戶設置此屬性的值,但攻擊者可以僞造請求並設置IsAdmin = true。默認模型聯編程序將簡單地分配此用戶域模型,並將其保存到數據庫。黑客剛剛爲您的系統創建了一個管理員帳戶。因此,爲了防止這種攻擊,您使用exclude/include屬性設置... –

1

我發現一些情況下,我想做同樣的事情。我還發現子對象不起作用,但子對象的列表/集合確實很奇怪。

我找到了一個很好的link描述如何解決這個問題。

它結束了看起來像這樣:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Save(int id, FormCollection collection) 
{ 
    User user = null; 
    if (id == 0) 
    { 
     user = new User(); 
     UpdateModel(user, "User"); 
     user.Contact = new Contact(); 
     UpdateModel(user.Contact, "User.Contact"); 
     user.Contact.Addresses = new EntitySet<Address>(); 
     UpdateModel(user.Contact.Addresses, "User.Contact.Addresses"); 
    } 
    else 
    { 
     // get current user object from DB, however you normally do this is fine. 
     user = userRepository.GetById(id); 
     UpdateModel(user, "User"); 
     UpdateModel(user.Contact, "User.Contact"); 
     UpdateModel(user.Contact.Addresses, "User.Contact.Addresses"); 
    } 
    // at this point, model "user" and children would have been updated. 
} 
...