ASP.NET MVC: ¿Cómo mantener TextBox Estado cuando su modelo de vista es una colección / Lista / IEnumerable

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

Pregunta

Estoy utilizando ASP.NET MVC 2 Beta. Puedo crear un asistente como el flujo de trabajo utilizando la técnica de Steven Sanderson (en su libro Pro ASP.NET MVC marco), excepto el uso de sesiones en lugar de campos de formulario ocultos para preservar los datos a través de solicitudes. Puedo ir y venir entre las páginas y mantener los valores en un cuadro de texto sin ningún problema cuando mi modelo no es una colección. Un ejemplo podría ser un modelo de persona simple:

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

Pero soy incapaz de conseguir que esto funcione cuando paso alrededor de un IEnumerable. En mi punto de vista que estoy tratando de ejecutar a través del modelo y de generar un cuadro de texto para el nombre y correo electrónico para cada persona en la lista. Puedo generar la forma fina y puedo enviar el formulario con mis valores y vaya al Paso 2. Pero cuando hago clic en el botón Atrás en el Paso 2 me lleva de nuevo a Paso 1 con una forma vacía. Ninguno de los campos que antes me pobladas están ahí. Tiene que haber algo que me falta. ¿Puede alguien ayudarme?

Este es mi punto de vista:

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

Y aquí está mi controlador:

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);
    }
}
¿Fue útil?

Solución

Por lo que yo puedo decir, esto no es compatible con ASP.NET MVC 2 Beta, ni se apoya en ASP.NET MVC 2 RC. Cavé a través del código fuente MVC y se ve como modelos Diccionarios son compatibles pero no que son IEnumerable <> (o que contienen objetos anidados IEnumerable) y es como herederos IList <>.

El problema está en la clase ViewDataDictionary. En particular, el método getPropertyValue sólo proporciona una manera de recuperar los valores de propiedad de las propiedades del diccionario (llamando GetIndexedPropertyValue) o propiedades simples utilizando el método PropertyDescriptor.GetValue para sacar el valor.

Para solucionar este problema, he creado un método GetCollectionPropertyValue que se encarga de modelos que son colecciones (e incluso los modelos que contienen colecciones anidadas). Estoy pegando el código aquí como referencia. Nota: No hace ninguna afirmación acerca de la elegancia - de hecho todo el análisis de cadenas es bastante feo, pero parece estar funcionando. Aquí está el método:

// 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;
        }

Y aquí está el método getPropertyValue modificado que llama el nuevo método:

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

Una vez más, esto es en el archivo ViewDataDictionary.cs en ASP.NET MVC 2 RC. ¿Debería crear un nuevo tema para realizar un seguimiento de esto en el sitio CodePlex MVC?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top