Использование шаблона ViewModel со строго типизированными HTML-помощниками MVC 2 - PullRequest
18 голосов
/ 29 января 2010

Я работаю с 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 ]

19 голосов
/ 31 января 2010

Проблема в том, что ваша форма редактирования использует строго типизированные помощники для типа DinnerFormViewModel, но вы вызываете UpdateModel для типа Dinner.

Когда вы используете строго типизированные помощники для типа, помощники создают поля формы, предполагая, что это тип, который вы публикуете.Когда типы не совпадают, возникает проблема.

Однако это очень легко исправить.Вы можете предоставить префикс для UpdateModel, который указывает, что вы не пытались редактировать всю модель, вы пытались редактировать свойство модели, в данном случае Dinner.

UpdateModel(dinner, "Dinner");

Другой подходэто вызвать UpdateModel для фактической ViewModel.

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

Я думаю, что первый подход намного лучше.

2 голосов
/ 27 ноября 2010

На странице 90 в книге " Wrox Professional ASP.NET MVC 2 " код указан как:

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" })%>
1 голос
/ 23 октября 2010

Может быть, проще сказать это следующим образом. Если вы вырезаете и вставляете код из загрузки wrox для руководства NerDDinner, вы обнаружите, что есть некоторые ошибки. Воспользовавшись предложением сверху, я изменил пример с 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);
   }
  }
1 голос
/ 29 января 2010

Я не уверен на 100%, но похоже, что строго типизированный помощник создает идентификаторы / имена "Dinner.Title" вместо просто "Заголовок" и поэтому - UpdateModel не может связать его.

К сожалению - я сам не использовал UpdateModel метод, поэтому я не знаю решения.

Не могли бы вы добавить HTML, который будет отображаться для обоих подходов?


Игра с рефлектором банкомата.

Вот что я нашел:

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

Параметры
- модель Экземпляр модели для обновления.
- префикс Префикс, используемый при поиске значений в поставщике значений.


Итак - вы можете попытаться использовать UpdateModel<T>(T model, string prefix) перегрузку и пройти «Ужин» или «Ужин». в качестве префиксного аргумента.

0 голосов
/ 14 апреля 2010

Более простой способ - использовать префикс в качестве имени параметра, просто сделайте так:

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