Привязка модели при отправке нескольких форм модели из строго типизированного представления

StackOverflow https://stackoverflow.com/questions/1064998

Вопрос

У меня возникли проблемы с привязкой формы, в которой отправлено несколько моделей.У меня есть форма жалобы, которая включает в себя информацию о жалобе, а также информацию о жалобах от одного ко многим.Я пытаюсь отправить форму, но при привязке получаю ошибки.ModelState.IsValid всегда возвращает false.

Если я отлаживаю и просматриваю ошибки ModelState, я получаю сообщение:«EntityCollection уже инициализирована.Метод InitializeRelatedCollection следует вызывать только для инициализации новой EntityCollection во время десериализации графа объектов».

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

Я не уверен, что то, что я делаю, невозможно с помощью ModelBinder по умолчанию, или я просто поступаю неправильно.Кажется, я не могу найти каких-либо конкретных примеров или документации по этому поводу.Очень похожую проблему можно найти в stackoverflow. здесь но, похоже, это не касается строго типизированных представлений.

Код контроллера:

    public ActionResult Edit(int id)
    {
        var complaint = (from c in _entities.ComplaintSet.Include("Complainants")
                     where c.Id == id
                     select c).FirstOrDefault();

        return View(complaint);
    }

    //
    // POST: /Home/Edit/5
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(Complaint complaint)
    {
        if (!ModelState.IsValid)
        {
            return View();
        }
        try
        {
            var originalComplaint = (from c in _entities.ComplaintSet.Include("Complainants")
                                     where c.Id == complaint.Id
                                     select c).FirstOrDefault();
            _entities.ApplyPropertyChanges(originalComplaint.EntityKey.EntitySetName, complaint);
            _entities.SaveChanges();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

Код представления (это частичное представление, вызываемое функциями создания/редактирования представлений, которые также строго типизированы с помощью Complaint):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ProStand.Models.Complaint>" %>

<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>

<table cellpadding="0" cellspacing="0" class="table">
    <tr>
        <td>
        <label for="DateReceived">Date Received:</label>
            <%= Html.TextBox("DateReceived") %>
            <%= Html.ValidationMessage("DateReceived", "*") %>    
        </td>
        <td>
            <label for="DateEntered">Date Entered:</label>
            <%= Html.TextBox("DateEntered")%>
            <%= Html.ValidationMessage("DateEntered", "*") %>
        </td>
    </tr>
    <tr>
        <td>
            <label for="Concluded">Concluded:</label>
            <%= Html.CheckBox("Concluded")%>
            <%= Html.ValidationMessage("Concluded", "*") %>
            </td>
        <td>
            <label for="IncidentDate">Incident Date:</label>
            <%= Html.TextBox("IncidentDate")%>
            <%= Html.ValidationMessage("IncidentDate", "*") %></td>
    </tr>
</table>
    <hr />
    <table>
    <% if (Model != null) {
           int i = 0;
       foreach (var complainant in Model.Complainants){ %>
       <%= Html.Hidden("Complainants[" + i + "].Id", complainant.Id)%>
    <tr>
        <td>
            <label for="Surname">Surname:</label>

            <%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>
            <%= Html.ValidationMessage("Surname", "*")%>
        </td>
        <td>
            <label for="GivenName1">GivenName1:</label>
            <%= Html.TextBox("Complainants[" + i + "].GivenName1", complainant.GivenName1)%>
            <%= Html.ValidationMessage("GivenName1", "*")%>
        </td>
    </tr>
    <% i++; %>
    <% }} %>
    <tr>
        <td colspan=2>
            <input type="submit" value="Submit" />
        </td>
    </tr>
</table>
<% } %>
<div>
    <%=Html.ActionLink("Back to List", "Index") %>
</div>
Это было полезно?

Решение

Слепое предположение:

изменять:

<%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>

с:

<%= Html.TextBox("Complaint.Complainants[" + i + "].Surname",  
complainant.Surname)%>

Соответственно - добавить «жалобу». Перед "Заявителями [..."

РЕДАКТИРОВАТЬ:

Это НЕ правильный ответ.Оставил его неудаленным только потому, что это может добавить некоторую ценность, пока не появится правильный ответ.

РЕДАКТИРОВАТЬ2:

Возможно, я ошибаюсь, но мне кажется, что проблема связана с инфраструктурой сущностей (или с тем, как вы ее используете).Я имею в виду - asp.net mvc удается читать значения из запроса, но не может инициализировать коллекцию жалобщиков.

Здесь это написано:

Метод InitializeRelatedCollection(TTargetEntity) инициализирует существующую EntityCollection(TEntity), созданную с помощью конструктора по умолчанию.EntityCollection(TEntity) инициализируется с использованием предоставленной связи и имен целевых ролей.

Метод InitializeRelatedCollection(TTargetEntity) используется только во время десериализации.

Еще немного информации:

Исключение:

  • ИнвалидОператионИсключение

Условия:

  • Когда предоставленная EntityCollection(TEntity) уже инициализирована.
  • Когда менеджер отношений уже прикреплен к ObjectContext.
  • Когда менеджер отношений уже содержит связь с этим именем и целевой ролью.

Почему-то InitializeRelatedCollection запускается дважды.К сожалению, у меня не было ярких идей, почему именно.Возможно, это небольшое расследование поможет кому-то еще, более опытному в работе с EF.:)

РЕДАКТИРОВАТЬ3:
Это не решение этой конкретной проблемы, а скорее обходной путь, правильный способ обработки модельной части mvc.

Создайте модель представления только для целей презентации.Также создайте новую модель домена из чистых POCO (поскольку EF будет поддерживать их только в следующей версии).Использовать АвтоМаппер для сопоставления EFDataContext<=>Model<=>ViewModel.

Это потребует некоторых усилий, но именно так и следует поступить.Этот подход снимает с вашей модели ответственность за представление, очищает вашу модель предметной области (удаляет элементы EF из вашей модели) и решает вашу проблему с привязкой.

Другие советы

public ActionResult Edit([Bind(Exclude = "Complainants")]Complaint model)
{
  TryUpdateModel(model.Complainants, "Complainants");
  if (!ModelState.IsValid)
  {
      // return the pre populated model
      return View(model);
  }

}

Это работает для меня!

Я думаю, что когда создается объект Complaint, его коллекция «Жалобщики» инициализируется (из-за автоматической логики структуры сущностей), а затем связующее устройство модели также пытается создать саму коллекцию, что вызывает ошибку.Но когда мы пытаемся обновить модель вручную, коллекция уже инициализируется, но связующему устройству модели не предлагается инициализировать ее снова.

Чтобы это работало без обходных путей в каждом конкретном случае, вам необходимо создать собственную привязку модели и переопределить метод SetProperty:

public class MyDefaultModelBinder : DefaultModelBinder
{
    protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
    { 
        ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name]; 
        propertyMetadata.Model = value;
        string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);

        // Try to set a value into the property unless we know it will fail (read-only 
        // properties and null values with non-nullable types)
        if (!propertyDescriptor.IsReadOnly) { 
        try {
            if (value == null)
            {
            propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            else
            {
            Type valueType = value.GetType();

            if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityCollection<>))
            {
                IListSource ls = (IListSource)propertyDescriptor.GetValue(bindingContext.Model);
                IList list = ls.GetList();

                foreach (var item in (IEnumerable)value)
                {
                list.Add(item);
                }
            }
            else
            {
                propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            }

        }
        catch (Exception ex) {
            // Only add if we're not already invalid
            if (bindingContext.ModelState.IsValidField(modelStateKey)) { 
            bindingContext.ModelState.AddModelError(modelStateKey, ex); 
            }
        } 
        }
    }
}

Не забудьте зарегистрировать связующее в Global.asax:

ModelBinders.Binders.DefaultBinder = new MyDefaultModelBinder();

Я обошел исключение ModelBinding, выполнив следующие действия:

// Remove the error from ModelState which will have the same name as the collection.
ModelState.Remove("Complaints"/*EntityCollection*/); 
if (ModelState.IsValid) // Still catches other errors.
{
    entities.SaveChanges(); // Your ObjectContext
}

Основным недостатком является то, что исключение все равно генерируется, и это может быть дорогостоящим во время выполнения.Элегантным решением может быть создание оболочки вокруг существующего DefaultBinder и предотвращение повторного создания экземпляра EntityCollection, что уже сделано EF.Затем привязываем эту коллекцию к значениям формы (FormCollection).

Имейте в виду, что если вы привязываете более одной коллекции, вам нужно будет удалить ошибку для каждой коллекции.

В моем эксперименте коллекция успешно сохранилась, а также корневой объект в графе, частью которого была коллекция.

Надеюсь, это поможет кому-то еще.

У меня была такая же проблема!В конце концов вы обнаружите, что фреймворк не может обрабатывать сложные модели.

Я написал небольшой компонент привязки, который может инициализировать сложные привязки к сообщению.

Но по сути то, что вам нужно сделать, это то, что Арнис Л.рассказывает.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top