61 votes

Comment fonctionne la liaison liste-modèle de MVC 4 ?

Si je veux qu'un ensemble d'entrées dans un formulaire soit lié à un fichier List dans MVC 4, je sais que la convention d'appellation suivante pour les noms de domaine input name attributs fonctionneront :

<input name="[0].Id" type="text" />
<input name="[1].Id" type="text" />
<input name="[2].Id" type="text" />

Mais je suis curieux de savoir si le modèle de classeur est indulgent. Par exemple, qu'en est-il de ce qui suit :

<input name="[0].Id" type="text" />
<input name="[3].Id" type="text" />
<input name="[8].Id" type="text" />

Comment le classeur de modèles traiterait-il cette question ? Serait-il lié à un List de longueur 9 avec des nuls ? Ou est-ce qu'il serait toujours lié à un List de longueur 3 ? Ou bien s'étoufferait-il complètement ?

Pourquoi je m'en soucie

Je veux mettre en place un formulaire dynamique dans lequel l'utilisateur peut ajouter des lignes au formulaire, et peut également supprimer des lignes du formulaire. Ainsi, si un utilisateur supprime la ligne 2 sur un total de 8 lignes, je veux savoir si je dois renuméroter toutes les entrées suivantes.

49voto

Erik Funkenbusch Points 53436

Il existe un format de fil spécifique à utiliser avec les collections. Ce sujet est abordé sur le blog de Scott Hanselman ici :

http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

Un autre article du blog de Phil Haack en parle ici :

http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Enfin, un article de blog qui fait exactement ce que vous voulez ici :

http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

17voto

Matthew Points 6447

J'ai suivi l'approche indiquée dans les blogs ci-dessus et j'ai ajouté quelques détails qui pourraient être utiles à certains - en particulier parce que je voulais ajouter dynamiquement un nombre quelconque de lignes mais je ne voulais pas utiliser AJAX pour le faire (je voulais que le formulaire ne soit soumis que dans le message). Je ne voulais pas non plus me soucier du maintien des identifiants séquentiels. Je capturais une liste de dates de début et de fin :

Voir le modèle :

public class WhenViewModel : BaseViewModel {
    public List<DateViewModel> Dates { get; set; }
    //... Other properties
}

Date de début / fin Modèle de vue :

public class DateViewModel {
    public string DateID { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
}

Puis les utiliser dans la page (avec le sélecteur de date) :

<div class="grid-8-12 clear" id="DatesBlock">
@{
    foreach (DateViewModel d in Model.Dates) {
        @:<div class="grid-5-12 left clear">
            @Html.Hidden("Dates.Index", d.DateID)
            @Html.Hidden("Dates[" + d.DateID + "].DateID", d.DateID) //ID again to populate the view model
            @Html.TextBox("Dates[" + d.DateID + "].StartDate", 
                          d.StartDate.Value.ToString("yyyy-MM-dd"))
        @:</div>
        @:<div class="grid-5-12">
            @Html.TextBox("Dates[" + d.DateID + "].EndDate", 
                          d.EndDate.Value.ToString("yyyy-MM-dd"))
        @:</div>

        <script type="text/javascript">
            $('input[name="Dates[@d.DateID].StartDate"]')
               .datepicker({ dateFormat: 'yy-mm-dd'});
            $('input[name="Dates[@d.DateID].EndDate"]')
               .datepicker({dateFormat: 'yy-mm-dd'});
        </script>
     }
}
</div>
<a href="#" onclick="AddDatesRow()">Add Dates</a>

Comme le décrit l'article du blog dont le lien figure dans l'article de @ErikTheVikings ci-dessus, la collection est créée par l'élément caché répété : @Html.Hidden("Dates.Index", d.DateID) pour chaque entrée de la collection sur la page.

Je voulais ajouter arbitrairement des rangs sans utiliser AJAX pour renvoyer les données au serveur, ce que j'ai fait en créant un div caché contenant un modèle d'un "rang" / élément dans la collection :

Ligne "Modèle" cachée :

<div id="RowTemplate" style="display: none">
    <div class="grid-5-12 clear">
        @Html.Hidden("Dates.Index", "REPLACE_ID")
        @Html.Hidden("Dates[REPLACE_ID].DateID", "REPLACE_ID") 
        @Html.TextBox("Dates[REPLACE_ID].StartDate", "")
    </div>
    <div class="grid-5-12">
        @Html.TextBox("Dates[REPLACE_ID].EndDate", "")
    </div>
</div>

J'ai ensuite utilisé jQuery qui clone le modèle, fournit un identifiant aléatoire à utiliser pour une nouvelle rangée et ajoute la rangée clonée maintenant visible à la div qui la contient :

jQuery pour compléter le processus :

<script type="text/javascript">
    function AddDatesRow() {
        var tempIndex = Math.random().toString(36).substr(2, 5);
        var template = $('#RowTemplate');
        var insertRow = template.clone(false);
        insertRow.find('input').each(function(){ //Run replace on each input
            this.id = this.id.replace('REPLACE_ID', tempIndex);
            this.name = this.name.replace('REPLACE_ID', tempIndex);
            this.value = this.value.replace('REPLACE_ID', tempIndex);
        });
        insertRow.show();
        $('#DatesBlock').append(insertRow.contents());

        //Attach datepicker to new elements
        $('input[name="Dates['+tempIndex+'].StartDate"]')
            .datepicker({dateFormat: 'yy-mm-dd' });
        $('input[name="Dates['+tempIndex+'].EndDate"]')
            .datepicker({dateFormat: 'yy-mm-dd' });
    }
</script>

Exemple JSFiddle du résultat : http://jsfiddle.net/mdares/7JZh4/

3voto

Richo Jarlson Points 50

J'ai une liste dynamique qui ressemble à ceci :

<ul id="okvedList" class="unstyled span8 editableList">
<li>
    <input data-val="true" data-val-required="The Guid field is required." id="Okveds_0__Guid" name="Okveds[0].Guid" type="hidden" value="2627d99a-1fcd-438e-8109-5705dd0ac7bb">
    --//--
</li>

Ainsi, lorsque j'ajoute ou supprime une ligne (élément li), je dois réorganiser les éléments.

    this.reorderItems = function () {
        var li = this.el_list.find('li');

        for (var i = 0; i < li.length; i++) {
            var inputs = $(li[i]).find('input');

            $.each(inputs, function () {
                var input = $(this);

                var name = input.attr('name');
                input.attr('name', name.replace(new RegExp("\\[.*\\]", 'gi'), '[' + i + ']'));

                var id = input.attr('id');
                input.attr('id', id.replace(new RegExp('_.*__', 'i'), '_' + i + '__'));
            });
        }
    };

Cette liste est placée dans un simple Html.BeginFrom du côté client et comme une liste dans le paramètre d'action du côté serveur.

1voto

andri Points 839

Je suis également confronté à un problème similaire dans le passé, et j'utilise KnockoutJS pour gérer un tel scénario.

En gros, Knockout envoie la collection dans une chaîne JSON, et je les désérialise dans mon contrôleur.

Pour plus d'informations : http://learn.knockoutjs.com/#/?tutorial=collections

0voto

thomas wu Points 1

J'ai un petit problème, lorsque j'utilise le navigateur Chrome et que je clique sur le bouton retour, et je constate que l'entrée avec type="hidden" lorsque les valeurs sont définies dynamiquement n'est pas traitée correctement par le navigateur Chrome.

nous pouvons peut-être changer

<input type="hidden" name="Detes.Index" value="2016/01/06" />

à

<div style="display: none">
    <input type="text" name="Detes.Index" value="2016/01/06" />
</div>

Formez plus d'infos : Chrome ne met pas en cache les valeurs des champs de formulaire cachés pour les utiliser dans l'historique du navigateur http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X