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. 
} 
...