web-dev-qa-db-fra.com

MVC publie une liste d'objets complexes

J'ai un FeedbackViewModel qui contient une liste de questions:

public class FeedbackViewModel
{
    public List<QuestionViewModel> Questions { get; set; }
}

Ce QuestionViewModel est un objet qui peut être hérité de 5 types de questions différents

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

Un exemple de l'un des types de question hérités:

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

Dans la variable HttpGet de l'action Index du contrôleur, je récupère les questions de la base de données et ajoute le type de question correct dans la liste des questions de la variable FeedbackViewModel.

@using (Html.BeginForm())
{
    //foreach (var item in Model.Questions)
    for (int i = 0; i < Model.Questions.Count; i++)
    {
        <div class="form-group">
            @Html.DisplayFor(modelItem => Model.Questions[i].QuestionText, new { @class = "control-label col-md-4" })
            <div class="col-md-6">
                @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)
                }
            </div>
        </div>
        <br />
    }

    <br />
    <button type="submit">Submit</button>
}

enter image description here


Maintenant, je ne peux tout simplement pas le faire pour afficher la liste des questions dans le modèle. Est-il même possible de publier une liste de différents types d'objets?


Edit: Voici la liste des données dans le post que j'ai découvert en utilisant Fiddler:

enter image description here

15
Carel

Après de nombreuses recherches, j'ai trouvé deux solutions:

La première consiste à écrire du HTML comportant des identifiants et noms codés en dur. La deuxième consiste à convertir votre ICollection/IEnumerable en un tableau ou une liste (c’est-à-dire IList quelque chose avec un 'index') et d’avoir un objet Array dans votre BindingModel dans votre contrôleur POST Action.

Merci à l'article de blog 2008 de Phil Haack (@haacked) http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ , Qui est toujours d'actualité. sur le fonctionnement actuel de ModelBinder par défaut pour MVC . (NB: les liens dans l'article de Phil permettant de voir des exemples de méthodes de projet et d'extension sont rompus)

Extrait HTML qui m'a inspiré:

<form method="post" action="/Home/Create">
    <input type="hidden" name="products.Index" value="cold" />
    <input type="text" name="products[cold].Name" value="Beer" />
    <input type="text" name="products[cold].Price" value="7.32" />

    <input type="hidden" name="products.Index" value="123" />
    <input type="text" name="products[123].Name" value="Chips" />
    <input type="text" name="products[123].Price" value="2.23" />

    <input type="submit" />
</form>

Le tableau de messages ressemble un peu à:

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

Modèle:

public class CreditorViewModel
{
    public CreditorViewModel()
    {
        this.Claims = new HashSet<CreditorClaimViewModel>();
    }
    [Key]
    public int CreditorId { get; set; }
    public string Comments { get; set; }
    public ICollection<CreditorClaimViewModel> 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; }
}

Contrôleur GET:

public async Task<ActionResult> Edit(int id)
    {
        var testmodel = new CreditorViewModel
        {
            CreditorId = 1,
            Comments = "test",
            Claims = new HashSet<CreditorClaimViewModel>{
                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)

<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().CreditorClaimType)
        </th>
        <th>
            @Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().ClaimedTotalAmount)
        </th>
    </tr>        
<!--Option One-->
@foreach (var item in Model.Claims)
{
    var fieldPrefix = string.Format("{0}[{1}].", "Claims", item.CreditorClaimId);
    <tr>
        <td>
            @Html.DisplayFor(m => item.CreditorClaimType)
        </td>
        <td>
        @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)
        </td>
    </tr>
    }
</table>    
<!--Option Two-->
@for (var itemCnt = 0; itemCnt < Model.ClaimsArray.Count(); itemCnt++)
{
    <tr>
        <td></td>
        <td>
            @Html.TextBoxFor(m => Model.ClaimsArray[itemCnt].ClaimedTotalAmount)
            @Html.HiddenFor(m => Model.ClaimsArray[itemCnt].CreditorClaimId)
    </td></tr>
}

Le formulaire est traité dans le contrôleur:

Post modèle:

public class CreditorPostViewModel
{
    public int CreditorId { get; set; }
    public string Comments { get; set; }
    public ICollection<CreditorClaimPostViewModel> Claims { get; set; }
    public CreditorClaimPostViewModel[] ClaimsArray  { get; set; }
}

public class CreditorClaimPostViewModel
{
    public int CreditorClaimId { get; set; }
    public Decimal ClaimedTotalAmount { get; set; }
}

Manette:

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

Merci de m'avoir orienté dans la bonne direction avec ce post. J'avais du mal à obtenir la syntaxe correcte pour lier un objet IDictionary<string, bool> non séquentiel. Pas sûr que ce soit correct à 100%, mais ce code Razor a fonctionné pour moi:

<input type="hidden" name="MyDictionary.Index" value="ABC" />
<input type="hidden" name="MyDictionary[ABC].Key" value="ABC" />
@Html.CheckBox(name: "MyDictionary[ABC].Value", isChecked: Model.MyDictionary["ABC"], htmlAttributes: null)

Si vous avez besoin d'une case à cocher, veillez à utiliser Html.CheckBox au lieu d'une case à cocher HTML standard. Le modèle explose si aucune valeur n'est fournie et Html.CheckBox génère un champ masqué pour garantir la présence d'une valeur lorsque la case à cocher n'est pas cochée.

4
Nick Hotalling

Assurez-vous de rendre votre vue dans l'ordre afin que Model.Questions[i] s'affiche dans l'ordre.

Par exemple, Model.Questions[0], Model.Questions[1], Model.Questions[2]. J'ai remarqué que si l'ordre n'est pas correct, le classeur de modèle MVC ne liera que le premier élément.

3
salli

J'utilise ce code peut-être que sa peut aider

<input type="hidden" name="OffersCampaignDale[@(item.ID)].ID" value="@(item.ID)" />

@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" })
0
Yitzhak Weinberg

En utilisant Razor, vous pouvez implémenter la boucle for en utilisant un dictionnaire comme suit sans apporter de modification à votre objet:

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

La collection doit être une liste ou un tableau pour que cela fonctionne. 

0
user942620