将ViewModel模式与MVC 2强类型HTML帮助程序一起使用

时间:2010-01-29 05:53:25

标签: asp.net-mvc html-helper

我正在使用ASP.NET MVC2 RC并且无法弄清楚如何获取HTML帮助器 TextBoxfor 以使用 ViewModel模式。在编辑页面上使用时,在控制器中调用UpdateModel()时,不会保存数据。我从NerdDinner应用程序中获取了以下代码示例。

Edit.aspx

<%@ Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NerdDinner.Models.DinnerFormViewModel>" %>
...
<p>
    // This works when saving in controller (MVC 1)
    <label for="Title">Dinner Title:</label>
    <%= Html.TextBox("Title", Model.Dinner.Title) %>
    <%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
    // This does not work when saving in the controller (MVC 2)
    <label for="Title">Dinner Title:</label>
    <%= Html.TextBoxFor(model => model.Dinner.Title) %>
    <%= Html.ValidationMessageFor(model=> model.Dinner.Title) %>
</p>

DinnerController

// POST: /Dinners/Edit/5

[HttpPost, Authorize]
public ActionResult Edit(int id, FormCollection collection) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    try {
        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {
        ModelState.AddModelErrors(dinner.GetRuleViolations());

        return View(new DinnerFormViewModel(dinner));
    }
}

当使用原始帮助器样式(Http.TextBox)时,UpdateModel(晚餐)调用按预期工作,并保存新值。

当使用新的(MVC2)帮助器样式(Http.TextBoxFor)时,UpdateModel(晚餐)调用不会更新值。是的,当前值会在加载时加载到编辑页面中。

我需要在控制器代码中添加其他内容才能使其正常工作吗?如果我只使用模型而不是ViewModel模式,那么新的帮助程序可以正常工作。

谢谢。

5 个答案:

答案 0 :(得分:19)

这里的问题是你的编辑表单是对DinnerFormViewModel类型使用强类型助手,但是你在Dinner类型上调用UpdateModel。

当您对类型使用强类型帮助程序时,帮助程序会创建表单字段,假设您要发布到的类型。当类型不匹配时,就会出现问题。

但是,这很容易修复。你可以为UpdateModel提供一个前缀,表示你没有尝试编辑整个模型,你试图编辑模型的属性,在这种情况下是晚餐。

UpdateModel(dinner, "Dinner");

另一种方法是在实际的ViewModel上调用UpdateModel。

var viewModel = new DinnerFormViewModel();
viewModel.Dinner = repository.GetDinner(id);
UpdateModel(viewModel);

我认为第一种方法要好得多。

答案 1 :(得分:2)

Wrox Professional ASP.NET MVC 2 一书的第90页上,代码列为:

if (TryUpdateModel(dinner)) {
     dinnerRepository.Save();

     redirectToAction("Details", new { id=dinner.DinnerID });

但它应该是:

if (TryUpdateModel(dinner, "Dinner")) {
     dinnerRepository.Save();

     redirectToAction("Details", new { id=dinner.DinnerID });

此方法重载将尝试使用控制器的值提供程序中的值更新指定的模型[Dinner],而不是默认的[ViewModel]。基本上它所做的就是在提供程序中查找它们时为所有值添加前缀。

因此,当模型正在寻找更新其'Title属性时,它将查找Dinner.Title,而不仅仅是控制器值提供程序中的Title。

在调试时,查看Edit ActionResult方法并检查FormCollection输入参数。当你深入了解它的入口数组时,你会发现所有的键都以你在View中引用的属性对象的前缀开头,在你的情况下是编辑视图,如下所示:

<%: Html.TextBoxFor(model => model.Dinner.Title, new {size=50, @class="prettyForm" })%>

答案 2 :(得分:1)

我不是百分百肯定,但似乎强类型帮助器创建了ids /名称“Dinner.Title”而不仅仅是“Title”,因此 - UpdateModel无法绑定它。

不幸的是 - 我自己没有使用UpdateModel方法,所以我不知道解决方案。

你能添加为这两种方法呈现的html吗?


玩反射器ATM。

这是我发现的:

protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IValueProvider valueProvider) where TModel: class
{
    if (model == null)
    {
        throw new ArgumentNullException("model");
    }
    if (valueProvider == null)
    {
        throw new ArgumentNullException("valueProvider");
    }
    Predicate<string> predicate = delegate (string propertyName) {
        return BindAttribute.IsPropertyAllowed(propertyName, base.includeProperties, base.excludeProperties);
    };
    IModelBinder binder = this.Binders.GetBinder(typeof(TModel));
    ModelBindingContext context2 = new ModelBindingContext();
    context2.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(delegate {
        return base.model;
    }, typeof(TModel));
    context2.ModelName = prefix;
    context2.ModelState = this.ModelState;
    context2.PropertyFilter = predicate;
    context2.ValueProvider = valueProvider;
    ModelBindingContext bindingContext = context2;
    binder.BindModel(base.ControllerContext, bindingContext);
    return this.ModelState.IsValid;
}
  

参数
   - model要更新的模型实例    - prefix在值提供程序中查找值时使用的前缀。


所以 - 您可以尝试使用UpdateModel<T>(T model, string prefix)重载并传递“晚餐”或“晚餐”。作为前缀参数。

答案 3 :(得分:1)

可能更简单的方法如下。如果您正在剪切和粘贴NerDDinner教程的wrox下载代码,您会发现存在一些错误。使用上面的建议,我修改了1-53.txt中的示例以使其工作。改变如下:

 //
  // POST: /Dinners/Edit/2
  [HttpPost]
  public ActionResult Edit(int id, FormCollection formValues)
  {
   // Retrieve existing dinner
   Dinner dinner = dinnerRepository.GetDinner(id);
   DinnerFormViewModel viewModel = new DinnerFormViewModel(dinner);

   if (TryUpdateModel(viewModel))
   {
    // Persist changes back to database
    dinnerRepository.Save();
    // Perform HTTP redirect to details page for the saved Dinner
    return RedirectToAction("Details", new { id = dinner.DinnerID });
   }
   else
   {
    return View(viewModel);
   }
  }

答案 4 :(得分:0)

更简单的方法是使用前缀作为参数名称,就像这样:

public ActionResult Edit(Dinner Dinner, int DinnerID)
{
   ...
}