2010-10-17 5 views
8

Sto tentando di associare un elenco che fa parte di un modello di vista più grande senza ricorrere a un modello di raccoglitore personalizzato. Quando utilizzo un modello di editor per creare l'elenco di input, i nomi generati non sono nel formato corretto affinché il binder predefinito funzioni.ASP.NET MVC Modello Binding IList in un modello di editor

Invece di Articoli [3]. Come mi aspetterei che sia Articoli. [3] .Id. Se creo la lista senza un modello di editor funziona come previsto.

Sto facendo qualcosa ovviamente sbagliato o è solo una stranezza di Html.Hidden e Html.TextBox?

public class ItemWrapper 
{ 
    [UIHint("ItemList")] 
    public IList<Item> Items { get; set; } 
} 

public class Item 
{ 
    public Guid Id { get; set; } 
    public string Name { get; set; } 
    public int Value { get; set; } 
} 

Index.aspx

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 

    <h2>Index</h2> 

    <% using(Html.BeginForm()) 
    {%> 
    <%:Html.EditorFor(m => m.Items) %> 
    <%}%> 
</asp:Content> 

ItemList.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IList<Mvc2Test.Models.Item>>" %> 

<h4>Asset Class Allocation</h4> 
<% if(Model.Count > 0) { %> 
<table> 
    <tbody> 
    <% for(int i = 0; i < Model.Count; i++) 
    {%> 
     <tr> 
     <td><%: Model[i].Name%></td> 
     <td> 
      <%: Html.HiddenFor(m => m[i].Id) %> 
      <%: Html.TextBoxFor(m => m[i].Value) %> 
     </td> 
     </tr> 
    <%}%> 
    </tbody> 
</table> 
<% 
}%> 

uscita

<tr> 
    <td>Item 4</td> 
    <td> 
    <input id="Items__3__Id" name="Items.[3].Id" type="hidden" value="f52a1f57-fca8-4bc5-a746-ee0cef4e05c2" /> 
    <input id="Items__3__Value" name="Items.[3].Value" type="text" value="40" /> 
    </td> 
</tr> 

Modifica (metodo d'azione)

public ActionResult Test() 
{ 
    return View(
    new ItemWrapper 
    { 
     Items = new List<Item> 
     { 
     { new Item { Id = Guid.NewGuid(), Name = "Item 1", Value = 10 } }, 
     { new Item { Id = Guid.NewGuid(), Name = "Item 2", Value = 20 } }, 
     { new Item { Id = Guid.NewGuid(), Name = "Item 3", Value = 30 } }, 
     { new Item { Id = Guid.NewGuid(), Name = "Item 4", Value = 40 } } 
     } 
    }); 
} 

Modifica # 2

HttpPost azione

[HttpPost] 
public ActionResult Test(ItemWrapper w) 
{ 
    if(w.Items == null) 
     Response.Write("Items was null"); 
    else 
     Response.Write("Items found " + w.Items.Count.ToString()); 
    return null; 
} 

Index.aspx

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 

<h4>Does Not Work</h4> 
<% using(Html.BeginForm("Test", "Home")) 
{%> 
     <%:Html.EditorFor(m => m.Items) %> 
     <input type="submit" value-"Go" /> 
<%}%> 

<h4>Does Work</h4> 
     <% using(Html.BeginForm("Test", "Home")) 
     {%> 
    <table> 
     <tbody> 
      <% for(int i = 0; i < Model.Items.Count; i++) 
      {%> 
      <tr> 
       <td><%: Model.Items[i].Name%></td> 
       <td> 
        <%: Html.HiddenFor(m => Model.Items[i].Id) %> 
        <%: Html.TextBoxFor(m => Model.Items[i].Value) %> 
       </td> 
      </tr> 
      <%}%> 
     </tbody> 
    </table> 
      <input type="submit" value-"Go" /> 
     <%}%> 

</asp:Content> 

risposta

7

ho capito il tuo problema, e potrei benissimo avere una soluzione troppo :)!

Prima di tutto, lascia che ti spieghi cosa ho imparato ispezionando lo framework's source code (è sempre una buona idea ispezionare il codice sorgente di un progetto opensource per capire meglio come funzionano certe cose).

1-) Quando si utilizza semplice digitato fortemente aiutanti html (vale a dire tutti Html.xxxFor (...) metodi tranne EditorFor e DisplayFor), nella espressione lambda che definiscono proprietà del modello a rendere, il nome dell'elemento HTML generato è uguale a qualsiasi stringa seguente "modello =>", meno ciò che viene prima "=>", vale a dire:

  • la stringa "modello" se il modello è una raccolta
  • o la stringa "modello . "(notare" . "alla fine) altrimenti.

Così, ad esempio questo:

<%: Html.TextBoxFor(m=>m.OneProperty.OneNestedProperty)%> 

genererà questo output HTML:

<input type="text" name="OneProperty.OneNestedProperty" ../> 

E questo:

<%: Html.TextBoxFor(m=>m[0].OneProperty.OneNestedProperty)%> 

genererà questo:

<input type="text" name="[0].OneProperty.OneNestedProperty" ../> 

==> Ciò spiega in parte perché si dispone di questo output di codice html "strano" quando si utilizza EditorFor.

2-) Quando si utilizza complessi fortemente tipizzati helper (EditorFor e DisplayFor), la stessa regola precedente viene applicata all'interno della vista parziale associata (ItemList.ascx nel caso), e in Inoltre, tutti gli elementi html generati saranno con prefisso da ciò che segue "==>", come spiegato in 1-).

Il prefisso qui è "Items", perché hai questo nel tuo vista digitato (Index.aspx):

<%:Html.EditorFor(m => m.Items) %> 

==> Questo spiega completamente l'uscita, e perché di default legante non funziona più con la vostra lista di elementi

La soluzione sarà quella di abbattere tua ItemWrapper parametro nella [HttpPost] metodo, nelle sue proprietà, e quindi utilizzare il Bind Abilità con la prefisso parametro di ciascuna struttura complessa, in questo modo:

[HttpPost] 
    public string Index(string foo,[Bind(Prefix = "Items.")]IList<Item> items) 
    { 
     return "Hello"; 
    } 

(supponendo che ItemWrapper ha anche una semplice proprietà denominata Foo di tipo stringa)

per evitare il conflitto, quando si elencano le proprietà nel metodo post, vi raccomando vivamente di dare un nome parametri secondo EA ch nome della proprietà (non è il caso) come ho fatto io.

Spero che questo possa aiutare!

+0

Quindi è davvero una stranezza nel modo MVC genera i nomi dei campi. La vista parziale non tiene conto del fatto che il modello è una raccolta quando genera il nome del campo. Immagino se gli Articoli.viene creato a livello di vista anziché a livello di vista parziale, quindi potrebbe non esserci un buon modo per risolverlo. Grazie. –

+0

kondotine: sembra un bug di asp.net mvc, qualcuno lo ha ancora segnalato? – Wout

+0

Ok, mi sono riferito: http://aspnet.codeplex.com/workitem/7711, votare per questa correzione! – Wout

-1

Una soluzione più lenta è solo utilizzare jQuery per "riparare" istanze di questo tipo. basta eseguire la seguente funzione dopo che la pagina (o pagina parziale) dei carichi:

function makeHiddenInputBindable() { 
    $('input[type="hidden"]').each(
     function (i) { 
      $(this).attr('name', function() { 
       return this.name.replace(/\.\[/g, "["); 
      }) 
     } 
    ); 
} 
+1

jQuery non deve essere utilizzato come stampella per rielaborare l'HTML danneggiato. Risolvilo dal lato del server. –