web-dev-qa-db-fra.com

Comment fonctionne le MVC 4 List Model Binding?

Si je veux qu'un ensemble d'entrées dans un formulaire soit lié à un List dans MVC 4, je sais que la convention de dénomination suivante pour les attributs inputname fonctionnera:

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

Mais je suis curieux de savoir comment pardonner le classeur de modèle est. Par exemple, qu'en est-il des éléments suivants:

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

Comment le classeur modèle s'en chargerait-il? Lierait-il à un List de longueur 9 avec des valeurs nulles? Ou serait-il toujours lié à un List de longueur 3? Ou cela suffirait-il?

Pourquoi je m'en soucie

Je souhaite implémenter 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, je veux savoir si je devrai renuméroter toutes les entrées suivantes.

58
Eric

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

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

Une autre entrée de blog de Phil Haack en parle ici:

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

Enfin, une entrée 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/

49
Erik Funkenbusch

J'ai suivi cette approche liée aux blogs ci-dessus et ajouté quelques détails qui pourraient être utiles à certains - en particulier parce que je voulais ajouter dynamiquement un nombre quelconque de lignes mais que je ne voulais pas utiliser AJAX à faire donc (je voulais que le formulaire ne soit envoyé que par la poste) .Je ne voulais pas non plus m'inquiéter de la gestion 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
}

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

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 datepicker):

<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 décrit dans l'article de blog lié à l'article @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 lignes sans utiliser AJAX pour poster des données sur le serveur, ce que j'ai fait en créant un div masqué contenant un modèle d'une "ligne"/élément de la collection:

Ligne cachée "Modèle":

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

Puis jQuery utilisé qui clone le modèle, fournit un identifiant aléatoire à utiliser pour une nouvelle ligne et ajoute la ligne clonée désormais visible au div contenant ci-dessus:

jQuery pour terminer 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/

16
Matthew

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>

alors quand j'ajoute ou supprime la 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 placée dans Html.BeginFrom simple à partir de clientside et comme paramètre List in action sur serverside

3
Evgeny Popov

J'ai également été 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 ai désérialisées dans mon contrôleur.

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

1
andri

J'ai eu peu de problème, quand j'utilise Chrome Navigateur et cliquez sur le bouton Retour, et je trouve l'entrée avec type = "caché" lorsque les valeurs définies de manière dynamique ne sont pas gérées correctement par Chrome Navigateur.

peut-être pouvons-nous 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>

Form plus d'infos: Chrome ne met pas en cache les valeurs de champ de formulaire masquées à utiliser dans l'historique du navigateurhttp://haacked.com/archive/2008/10/23/model-binding-to -a-list.aspx /

0
thomas wu