MVC publica una lista de objetos complejos

Tengo un FeedbackViewModel que contiene una lista de preguntas:

public class FeedbackViewModel { public List Questions { get; set; } } 

Este QuestionViewModel es un objeto que puede ser heredado por 5 tipos diferentes de preguntas

 public class QuestionViewModel { public string QuestionText { get; set; } public string QuestionType { get; set; } } 

Un ejemplo de uno de los tipos de preguntas heredadas:

 public class SingleQuestionViewModel : QuestionViewModel { public string AnswerText { get; set; } } 

En el HttpGet de la acción del Index en el controlador, obtengo las preguntas de la base de datos y agrego el tipo de pregunta correcto en la lista de preguntas en el modelo FeedbackViewModel A continuación, presento este modelo en la vista:

 @using (Html.BeginForm()) { //foreach (var item in Model.Questions) for (int i = 0; i < Model.Questions.Count; i++) { 
@Html.DisplayFor(modelItem => Model.Questions[i].QuestionText, new { @class = "control-label col-md-4" })
@if (Model.Questions[i].QuestionType == "Single") { @Html.EditorFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText) } else if (Model.Questions[i].QuestionType == "Multiple") { @Html.TextAreaFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText) } else if (Model.Questions[i].QuestionType == "SingleSelection") { @Html.RadioButtonForSelectList(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleSelectionQuestionViewModel).SelectedAnswer, (Model.Questions[i] as OpenDataPortal.ViewModels.SingleSelectionQuestionViewModel).SelectionAnswers) } else if (Model.Questions[i].QuestionType == "MultipleSelection") { @Html.CustomCheckBoxList((Model.Questions[i] as OpenDataPortal.ViewModels.MultipleSelectionQuestionViewModel).AvailableAnswers) } else if (Model.Questions[i].QuestionType == "UrlReferrer") { @Html.EditorFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText) }

}
}

enter image description here


Ahora, simplemente no puedo conseguir que publique la lista de preguntas en el modelo. ¿Es posible publicar una lista de diferentes tipos de objetos?


Editar: A continuación se muestra la lista de datos dentro de la publicación que descubrí usando Fiddler:

enter image description here

Después de mucha investigación, he encontrado dos soluciones:

Una es escribir HTML que tiene códigos Id y Names Two es convertir su ICollection / IEnumerable a una matriz o lista (es decir, IList algo con un ‘índice’), y tener un objeto Array en su BindingModel en su Controller POST Action.

Gracias a la publicación de blog de Phil Haack (@haacked) de 2008 http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ que sigue siendo relevante sobre cómo funciona el modelo predeterminado de ModelBinder. para MVC. (NB: los enlaces en el artículo de Phil para muestrear los métodos de extensión y poryecto están rotos)

Fragmento de HTML que me inspiró:

 

Post array se parece un poco a:

 products.Index=cold&products[cold].Name=Beer&products[cold].Price=7.32&products.Index=123&products[123].Name=Chips&products[123].Price=2.23 

Modelo:

 public class CreditorViewModel { public CreditorViewModel() { this.Claims = new HashSet(); } [Key] public int CreditorId { get; set; } public string Comments { get; set; } public ICollection Claims { get; set; } public CreditorClaimViewModel[] ClaimsArray { get { return Claims.ToArray(); } } } public class CreditorClaimViewModel { [Key] public int CreditorClaimId { get; set; } public string CreditorClaimType { get; set; } [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:N2}")] public Decimal ClaimedTotalAmount { get; set; } } 

Controlador GET:

 public async Task Edit(int id) { var testmodel = new CreditorViewModel { CreditorId = 1, Comments = "test", Claims = new HashSet{ new CreditorClaimViewModel{ CreditorClaimId=1, CreditorClaimType="1", ClaimedTotalAmount=0.00M}, new CreditorClaimViewModel{ CreditorClaimId=2, CreditorClaimType="2", ClaimedTotalAmount=0.00M}, } }; return View(model); } 

Edit.cshtml:

 @Html.DisplayNameFor(m => m.Comments) @Html.EditorFor(m => m.Comments)  @foreach (var item in Model.Claims) { var fieldPrefix = string.Format("{0}[{1}].", "Claims", item.CreditorClaimId);  } 
@Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().CreditorClaimType) @Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().ClaimedTotalAmount)
@Html.DisplayFor(m => item.CreditorClaimType) @Html.TextBox(fieldPrefix + "ClaimedTotalAmount", item.ClaimedTotalAmount.ToString("F"), new { @class = "text-box single-line", data_val = "true", data_val_number = "The field ClaimedTotalAmount must be a number.", data_val_required = "The ClaimedTotalAmount field is required." }) @Html.Hidden(name: "Claims.index", value: item.CreditorClaimId, htmlAttributes: null) @Html.Hidden(name: fieldPrefix + "CreditorClaimId", value: item.CreditorClaimId, htmlAttributes: null)
@for (var itemCnt = 0; itemCnt < Model.ClaimsArray.Count(); itemCnt++) { @Html.TextBoxFor(m => Model.ClaimsArray[itemCnt].ClaimedTotalAmount) @Html.HiddenFor(m => Model.ClaimsArray[itemCnt].CreditorClaimId) }

El formulario se procesa en el controlador:

Modelo de publicación:

 public class CreditorPostViewModel { public int CreditorId { get; set; } public string Comments { get; set; } public ICollection Claims { get; set; } public CreditorClaimPostViewModel[] ClaimsArray { get; set; } } public class CreditorClaimPostViewModel { public int CreditorClaimId { get; set; } public Decimal ClaimedTotalAmount { get; set; } } 

Controlador:

 [HttpPost] public ActionResult Edit(int id, CreditorPostViewModel creditorVm) { //... 

Gracias por apuntarme en la dirección correcta con esta publicación. Estaba luchando por obtener la syntax adecuada para enlazar un objeto IDictionary no secuencial. No estoy seguro de que esto sea 100% correcto, pero este código Razor funcionó para mí:

   @Html.CheckBox(name: "MyDictionary[ABC].Value", isChecked: Model.MyDictionary["ABC"], htmlAttributes: null) 

Si necesita una checkbox, asegúrese de usar Html.CheckBox en lugar de una casilla HTML estándar. El modelo explotará si no se proporciona un valor, y Html.CheckBox genera un campo oculto para garantizar que haya un valor cuando la checkbox no esté marcada.

Asegúrese de que está Model.Questions[i] su vista para que Model.Questions[i] en orden.

Por ejemplo, Model.Questions[0], Model.Questions[1], Model.Questions[2] . Noté que si el orden no es correcto, la carpeta de modelo de mvc solo enlazará el primer elemento.

Usando Razor puede implementar el bucle for usando un diccionario de la siguiente manera sin hacer cambios a su objeto:

 @foreach (var x in Model.Questions.Select((value,i)=>new { i, value })) { if (Model.Questions[xi].QuestionType == "Single") { @Html.EditorFor(modelItem => (modelItem.Questions[xi] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText) } ... } 

La colección debe ser una Lista o una Matriz para que esto funcione.

Uso este código, tal vez puede ayudar

  @Html.Raw(Html.EditorFor(modelItem => item.NameDale, new { htmlAttributes = new { @class = "form-control" } }) .ToString().Replace("item.NameDale", "OffersCampaignDale[" + item.ID+ "].NameDale").Replace("item_NameDale", "NameDale-" + item.ID)) @Html.ValidationMessageFor(modelItem => item.NameDale, "", new { @class = "text-danger" })