【问题标题】:Using ViewModel Pattern with MVC 2 Strongly Typed HTML Helpers使用带有 MVC 2 强类型 HTML 助手的 ViewModel 模式
【发布时间】:2010-01-29 05:53:24
【问题描述】:

我正在使用 ASP.NET MVC2 RC,但不知道如何让 HTML 帮助器 TextBoxforViewModel 模式一起工作。在编辑页面上使用时,在控制器中调用 UpdateModel() 时不会保存数据。我从 NerdDinner 应用程序中获取了以下代码示例。

编辑.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>

晚餐控制器

// 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(dinner) 调用会按预期工作并保存新值。

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

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

谢谢。

【问题讨论】:

标签: asp.net-mvc html-helper


【解决方案1】:

这里的问题是您的 Edit 表单对 DinnerFormViewModel 类型使用强类型帮助器,但您在 Dinner 类型上调用 UpdateModel。

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

但是,这很容易解决。您可以为 UpdateModel 提供一个前缀,表明您没有尝试编辑整个模型,而是尝试编辑模型的属性,在本例中为晚餐。

UpdateModel(dinner, "Dinner");

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

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

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

【讨论】:

  • 没有什么比从项目的 PM 那里得到答案更好的了。谢谢菲尔,效果很好。我使用了您的第一个示例,正如我所希望的那样简单直接。
  • 我遵循相同的示例,但更新模型时出现异常:stackoverflow.com/questions/2377065/…
  • 嗨菲尔,我对创建操作有同样的问题。我正在为创建表单使用自定义视图模型。当我尝试创建一个对象时,它不会从表单中绑定。所以我看了一下来源,发现“Category.Title”而不是“Title”。我该如何解决。谢谢
【解决方案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 输入参数。当您深入研究它的条目数组时,您会发现所有键都以您在视图中引用的属性对象的前缀开头,在您的情况下是编辑视图,如下所示:

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

【讨论】:

    【解决方案3】:

    我不是 100% 确定,但似乎强类型助手创建了 ID/名称“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&lt;T&gt;(T model, string prefix) 重载并传递“Dinner”或“Dinner”。作为前缀参数。

    【讨论】:

    • 谢谢,这似乎可以解决问题。我遇到的问题是,如果您需要跳过一个箍来使用它们,为什么要将强类型助手添加到脚手架。我必须在这里遗漏一些东西。
    • @Brettski 我也遇到了一些困难(stackoverflow.com/questions/2093216/…)。你没有错过任何东西——他们只是不够成熟,仅此而已。至少 - 我是这么看的。
    【解决方案4】:

    也许更简单的说法如下。如果您从 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);
       }
      }
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-18
        • 1970-01-01
        • 1970-01-01
        • 2011-04-30
        • 2011-11-02
        • 1970-01-01
        • 2012-03-10
        • 1970-01-01
        相关资源
        最近更新 更多