Modelo de ligação em vários modelo de Formulário de Apresentação de fortemente tipado Ver

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

Pergunta

Estou tendo problemas de ligação em um formulário com vários modelos que estão sendo submetidos. Eu tenho um formulário de reclamação que inclui informações denúncia, bem como queixosos um-para-muitos. Estou tentando enviar o formulário, mas eu estou recebendo erros no bind. ModelState.IsValid sempre retorna false.

Se eu debug e ver os erros ModelState, eu recebo um dizendo: "O EntityCollection já foi inicializado. O método InitializeRelatedCollection só deve ser chamado para inicializar uma nova EntityCollection durante a desserialização de um gráfico de objeto".

Além disso, quando a depuração, eu posso ver que a Reclamação Modelo não são preenchidos com os queixosos a partir do envio do formulário, por isso parece que parte está funcionando.

Eu não sei se o que estou fazendo não é possível com o ModelBinder padrão, ou se estou simplesmente não indo pelo caminho certo. Eu não consigo encontrar qualquer exemplos concretos ou documentação sobre este assunto. Um problema muito semelhante pode ser encontrada em stackoverflow aqui mas não parecem lidar com vistas rigidez.

Código Controlador:

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

View Code (Esta é uma vista parcial que é chamada por criar / editar Visualizações, que também são fortemente tipificados com Queixa):

<%@ 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>
Foi útil?

Solução

Cego palpite:

mudança:

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

com:

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

Respectivamente - add "Reclamação". antes "queixosos [..."

Editar :

Esta não é uma resposta certa. Deixou desfeita apenas porque isso poderia acrescentar algum valor até resposta adequada aparece.

EDIT2:

Eu posso estar errado, mas para mim parece que não há problema com a estrutura de entidade (ou - com a maneira de usá-lo). Quero dizer - asp.net MVC consegue ler os valores de solicitação, mas não pode inicializar queixosos coleção.

Aqui está escrito:

O InitializeRelatedCollection (TTargetEntity) método inicializa um EntityCollection existente (TEntity) que foi criado usando o construtor padrão. O EntityCollection (TEntity) é inicializado usando os fornecidos de relacionamento e de destino nomes de função.

O método InitializeRelatedCollection (TTargetEntity) é utilizado durante deserialização única.

Alguns mais informações:

Exceção:

  • InvalidOperationException

Condições:

  • Quando o EntityCollection fornecido (TEntity) já foi inicializado.
  • Quando o gerente de relacionamento já está anexado a um ObjectContext.
  • Quando o gerente de relacionamento já contém uma relação com este nome e função de destino.

Somewhy InitializeRelatedCollection é demitido duas vezes. Infelizmente - Eu não tenho ideias brilhantes por que exatamente. Talvez esta pequena investigação vai ajudar para outra pessoa - mais experiente com EF. :)

EDIT3:
Esta não é uma solução para este problema particular, mais como uma solução alternativa, uma maneira apropriada de lidar com modelo parte da MVC.

Criar um viewmodel apenas para fins de apresentação. Criar um novo modelo de domínio de POCOs puros também (porque EF irá apoiá-los somente na versão seguinte). Use AutoMapper para mapear EFDataContext <=> Modelo <=> ViewModel.

Isso levaria algum esforço, mas é assim que deve ser tratada. Esta apresentação responsabilidade abordagem remove do seu modelo, limpa o seu modelo de domínio (remove material EF do seu modelo) e iria resolver o seu problema com a ligação.

Outras dicas

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

}

Isso funciona para mim!

Eu acho que quando objeto de reclamação é criado, em seguida, sua coleção 'Queixosos é inicializado (por causa da lógica auto estrutura de entidade) e, em seguida, tenta fichário de modelo para criar a coleção em si, bem como, o que provoca o erro. Mas quando tentamos atualizar o modelo manualmente, em seguida, coleção já foi inicializado mas o modelo fichário não é solicitado para inicializar-lo novamente.

Para chegar a este trabalho sem caso a caso Soluções Alternativas que você precisa para criar o seu próprio modelo de ligante e método de substituição 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); 
            }
        } 
        }
    }
}

Não se esqueça de registar o seu aglutinante em Global.asax:

ModelBinders.Binders.DefaultBinder = new MyDefaultModelBinder();

Eu trabalhei em torno exceção ModelBinding fazendo o seguinte:

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

A principal desvantagem é que a exceção ainda é jogado e que pode ser caro em tempo de execução. O em torno do trabalho elegante pode ser para criar um invólucro em torno do DefaultBinder existente e impedir que instanciar o EntityCollection novamente, o que já é feito pela EF. Em seguida, ligação que recolha para os valores do formulário (FormCollection).

Tenha em mente, se você estiver de ligação mais do que uma coleção, você precisará remover o erro para cada coleção.

Na minha experiência, a coleção salvas com sucesso, bem como o objeto raiz no gráfico que a coleção era parte de.

Espero que ajude alguém.

Eu tive o mesmo problema! No final, você vai descobrir que o quadro não pode lidar com modelos complexos.

Eu escrevi um componente obrigatório pouco que pode inicializar as ligações complexas em um post.

Mas, basicamente, o que você tem a fazer é o que Arnis L. é revelador.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top