ASP.NET MVC:Как поддерживать состояние текстового поля, когда ваша ViewModel является коллекцией / Списком / IEnumerable

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

Вопрос

Я использую ASP.NET Бета-версию MVC 2.Я могу создать рабочий процесс, подобный мастеру, используя технику Стивена Сандерсона (в его книге Pro ASP.NET MVC Framework), за исключением использования сеанса вместо скрытых полей формы для сохранения данных по запросам.Я могу переходить туда и обратно между страницами и сохранять значения в текстовом поле без каких-либо проблем, когда моя модель не является коллекцией.Примером может служить простая модель Человека:

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

Но я не могу заставить это работать, когда я передаю IEnumerable .На мой взгляд, я пытаюсь запустить Модель и сгенерировать текстовое поле для имени и электронной почты для каждого Человека в списке.Я могу сгенерировать форму нормально, и я могу отправить форму со своими значениями и перейти к шагу 2.Но когда я нажимаю кнопку "Назад" на шаге 2, это возвращает меня к шагу 1 с пустой формой.Ни одного из полей, которые я ранее заполнял, там нет.Должно быть, мне чего-то не хватает.Кто-нибудь может мне помочь?

Вот мое мнение:

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

А вот и мой контроллер:

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);
    }
}
Это было полезно?

Решение

Насколько я могу судить, это не поддерживается в ASP.NET MVC 2 Beta, равно как и в ASP.NET MVC 2 RC.Я покопался в исходном коде MVC, и похоже, что словари поддерживаются, но не модели, которые являются IEnumerable<> (или которые содержат вложенные объекты IEnumerable) и их наследники, такие как IList<>.

Проблема заключается в классе ViewDataDictionary.В частности, метод GetPropertyValue предоставляет только способ извлечения значений свойств из словаря properties (путем вызова GetIndexedPropertyValue) или простых свойств с помощью PropertyDescriptor.Метод GetValue для извлечения значения.

Чтобы исправить это, я создал метод GetCollectionPropertyValue, который обрабатывает Модели, являющиеся коллекциями (и даже Модели, содержащие вложенные коллекции).Я вставляю код сюда для справки.Примечание:Я не претендую на элегантность - на самом деле весь синтаксический анализ строк довольно уродлив, но, похоже, он работает.Вот этот метод:

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

И вот модифицированный метод GetPropertyValue, который вызывает новый метод:

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

Опять же, это находится в файле ViewDataDictionary.cs в ASP.NET MVC 2 RC.Должен ли я создать новую проблему, чтобы отслеживать это на сайте MVC codeplex?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top