0

ASP.NET MVC 2 Beta を使用しています。スティーブン サンダーソンのテクニック (彼の著書 Pro ASP.NET MVC フレームワーク) を使用してウィザードのようなワークフローを作成できますが、非表示のフォーム フィールドの代わりにセッションを使用してリクエスト間でデータを保持することを除きます。モデルがコレクションではない場合、ページ間を行き来し、TextBox の値を問題なく維持できます。例は単純な Person モデルです。

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

しかし、IEnumerable を渡すと、これを機能させることができません。私の見解では、モデルを実行して、リスト内の各人物の名前と電子メールの TextBox を生成しようとしています。フォームを正常に生成でき、値を含むフォームを送信してステップ 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);
    }
}
4

1 に答える 1

1

私が知る限り、これは ASP.NET MVC 2 Beta でも ASP.NET MVC 2 RC でもサポートされていません。MVC ソース コードを調べたところ、Dictionaries はサポートされているようですが、IEnumerable<> である (またはネストされた IEnumerable オブジェクトを含む) モデルはサポートされておらず、IList<> のような継承者です。

問題は ViewDataDictionary クラスにあります。特に、GetPropertyValue メソッドは、ディクショナリ プロパティから (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
            };
        }

繰り返しますが、これは ASP.NET MVC 2 RC の ViewDataDictionary.cs ファイルにあります。MVC codeplex サイトでこれを追跡するために新しい問題を作成する必要がありますか?

于 2009-12-21T10:29:06.393 に答える