ASP.NET MVC: Comment maintenir l'état TextBox lorsque votre ViewModel est une collection / Liste / IEnumerable

StackOverflow https://stackoverflow.com/questions/1878087

Question

J'utilise ASP.NET MVC 2 Beta. Je peux créer un assistant comme flux de travail en utilisant la technique de Steven Sanderson (dans son livre Pro Framework ASP.NET MVC), sauf en utilisant session au lieu de champs de formulaire cachés pour préserver les données sur les demandes. Je peux aller et venir entre les pages et maintenir les valeurs dans une zone de texte sans aucun problème quand mon modèle n'est pas une collection. Un exemple serait un modèle simple de personne:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

Mais je ne peux pas obtenir ce travail quand je passe autour d'un IEnumerable. À mon avis, je suis en train de courir à travers le modèle et générer une zone de texte pour nom et votre email pour chaque personne dans la liste. Je peux générer la grande forme et je peux soumettre le formulaire avec mes valeurs et aller à l'étape 2. Mais quand je clique sur le bouton Retour à l'étape 2, il me ramène à l'étape 1 avec une forme vide. Aucun des champs que je l'ai déjà peuplé sont là. Il doit y avoir quelque chose que je suis absent. Quelqu'un peut-il me aider?

Voici mon point de vue:

<% using (Html.BeginForm()) { %>
<% int index = 0;
   foreach (var person in Model) { %>
       <fieldset>
            <%= Html.Hidden("persons.index", index.ToString())%>
            <div>Name: <%= Html.TextBox("persons[" + index.ToString() + "].Name")%></div>
            <div>Email: <%= Html.TextBox("persons[" + index.ToString() + "].Email")%></div>
       </fieldset>
       <% index++;
   } %>  
   <p><input type="submit" name="btnNext" value="Next >>" /></p>
<% } %>

Et voici mon contrôleur:

public class PersonListController : Controller
{
    public IEnumerable<Person> persons;

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        persons = (Session["persons"]
            ?? TempData["persons"]
            ?? new List<Person>()) as List<Person>;
        // I've tried this with and without the prefix.
        TryUpdateModel(persons, "persons"); 
    }

    protected override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Session["persons"] = persons;

        if (filterContext.Result is RedirectToRouteResult)
            TempData["persons"] = persons;
    }

    public ActionResult Step1(string btnBack, string btnNext)
    {
        if (btnNext != null)
            return RedirectToAction("Step2");

        // Setup some fake data
        var personsList = new List<Person> 
            { 
                new Person { Name = "Jared", Email = "test@email.com", },
                new Person { Name = "John", Email = "test2@email.com" } 
            };

        // Populate the model with fake data the first time
        // the action method is called only. This is to simulate
        // pulling some data in from a DB.
        if (persons == null || persons.Count() == 0)
            persons = personsList;

        return View(persons);
    }

    // Step2 is just a page that provides a back button to Step1
    public ActionResult Step2(string btnBack, string btnNext)
    {
        if (btnBack != null)
            return RedirectToAction("Step1");

        return View(persons);
    }
}
Était-ce utile?

La solution

Pour autant que je peux dire, ce n'est pas pris en charge dans ASP.NET MVC 2 Beta, il est pris en charge ni dans ASP.NET MVC 2 RC. Je perçai le code source MVC et il semble comme des dictionnaires sont pris en charge mais pas les modèles qui sont IEnumerable <> (ou qui contiennent des objets imbriqués IEnumerable) et il est comme IList héritières <>.

La question est dans la classe ViewDataDictionary. En particulier, la méthode GetPropertyValue fournit seulement un moyen de récupérer les valeurs de propriété de propriétés du dictionnaire (en appelant GetIndexedPropertyValue) ou des propriétés simples en utilisant la méthode PropertyDescriptor.GetValue pour retirer la valeur.

Pour résoudre ce problème, j'ai créé une méthode GetCollectionPropertyValue qui gère les modèles qui sont des collections (et même les modèles qui contiennent des collections imbriquées). Je suis coller le code ici pour référence. Note: Je ne fais aucune réclamation au sujet de l'élégance - en fait toute la chaîne est parsing assez laid, mais il semble fonctionner. Voici la méthode:

// Can be used to pull out values from Models with collections and nested collections.
        // E.g. Persons[0].Phones[3].AreaCode
        private static ViewDataInfo GetCollectionPropertyValue(object indexableObject, string key)
        {
            Type enumerableType = TypeHelpers.ExtractGenericInterface(indexableObject.GetType(), typeof(IEnumerable<>));
            if (enumerableType != null)
            {
                IList listOfModelElements = (IList)indexableObject;

                int firstOpenBracketPosition = key.IndexOf('[');
                int firstCloseBracketPosition = key.IndexOf(']');

                string firstIndexString = key.Substring(firstOpenBracketPosition + 1, firstCloseBracketPosition - firstOpenBracketPosition - 1);
                int firstIndex = 0;
                bool canParse = int.TryParse(firstIndexString, out firstIndex);

                object element = null;
                // if the index was numeric we should be able to grab the element from the list
                if (canParse)
                    element = listOfModelElements[firstIndex];

                if (element != null)
                {
                    int firstDotPosition = key.IndexOf('.');
                    int nextOpenBracketPosition = key.IndexOf('[', firstCloseBracketPosition);

                    PropertyDescriptor descriptor = TypeDescriptor.GetProperties(element).Find(key.Substring(firstDotPosition + 1), true);

                    // If the Model has nested collections, we need to keep digging recursively
                    if (nextOpenBracketPosition >= 0)
                    {
                        string nextObjectName = key.Substring(firstDotPosition+1, nextOpenBracketPosition-firstDotPosition-1);
                        string nextKey = key.Substring(firstDotPosition + 1);

                        PropertyInfo property = element.GetType().GetProperty(nextObjectName);
                        object nestedCollection = property.GetValue(element,null);
                        // Recursively pull out the nested value
                        return GetCollectionPropertyValue(nestedCollection, nextKey);
                    }
                    else
                    {
                        return new ViewDataInfo(() => descriptor.GetValue(element))
                        {
                            Container = indexableObject,
                            PropertyDescriptor = descriptor
                        };
                    }
                }
            }

            return null;
        }

Et voici la méthode modifiée GetPropertyValue qui appelle la nouvelle méthode:

private static ViewDataInfo GetPropertyValue(object container, string propertyName) {
            // This method handles one "segment" of a complex property expression

            // First, we try to evaluate the property based on its indexer
            ViewDataInfo value = GetIndexedPropertyValue(container, propertyName);
            if (value != null) {
                return value;
            }

            // If the indexer didn't return anything useful, continue...

            // If the container is a ViewDataDictionary then treat its Model property
            // as the container instead of the ViewDataDictionary itself.
            ViewDataDictionary vdd = container as ViewDataDictionary;
            if (vdd != null) {
                container = vdd.Model;
            }

            // Second, we try to evaluate the property based on the assumption
            // that it is a collection of some sort (e.g. IList<>, IEnumerable<>)
            value = GetCollectionPropertyValue(container, propertyName);
            if (value != null)
            {
                return value;
            }

            // If the container is null, we're out of options
            if (container == null) {
                return null;
            }

            // Third, we try to use PropertyDescriptors and treat the expression as a property name
            PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true);


            if (descriptor == null) {
                return null;
            }

            return new ViewDataInfo(() => descriptor.GetValue(container)) {
                Container = container,
                PropertyDescriptor = descriptor
            };
        }

Encore une fois, cela est dans le fichier ViewDataDictionary.cs dans ASP.NET MVC 2 RC. Dois-je créer un nouveau numéro pour suivre ce sur le site CodePlex MVC?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top