2

辞書プロパティを含むモデルがあります。(これは、より大きなプロジェクトからこの例に抽出されましたが、まだ同じ問題があることを確認しました)

public class TestModel
{
    public IDictionary<string, string> Values { get; set; }

    public TestModel()
    {
        Values = new Dictionary<string, string>();
    }
}

コントローラー

public class TestController : Controller
{
    public ActionResult Index()
    {
        TestModel model = new TestModel();
        model.Values.Add("foo", "bar");
        model.Values.Add("fizz", "buzz");
        model.Values.Add("hello", "world");

        return View(model);
    }

    [HttpPost]
    public ActionResult Index(TestModel model)
    {
        // model.Values is null after post back here.
        return null; // I set a break point here to inspect 'model'
    }
}

とビュー

@using TestMVC.Models
@model TestModel
@using (Html.BeginForm())
{
    @Html.EditorFor(m => m.Values["foo"]);
    <br />
    @Html.EditorFor(m => m.Values["fizz"]);
    <br />
    @Html.EditorFor(m => m.Values["hello"]);
    <br />
    <input type="submit" value="submit" />
}

これは、次のようにブラウザにレンダリングされます。

<input class="text-box single-line" id="Values_foo_" name="Values[foo]" type="text" value="bar" />

私が抱えている問題は、ポストバック後のモデルでディクショナリがnullになることです。

  • 私はこれを正しくやっていますか、それとももっと良い方法がありますか?

フォームのフィールドは可変であるため、ある種のKey-Valueストレージが必要です。そのため、POCOモデルを使用できません。

4

4 に答える 4

2

詳細については、このトピックに関する Scott hanselman のブログ投稿をお読みください。それまでの間、問題を解決するには、ビューを次のように置き換えてください。

<input type="hidden" name="Values[0].Key" value="foo" />
<input type="text" name="Values[0].Value" value="bar" />

すべてのセクションに対して同じことを繰り返します。おそらく次のような for ループに入れます。

@for(i=0;i<Model.Values.Count;i++)
{
    @Html.Hidden("Values[@i].Key", @Model.Values.Keys[@i])
    @Html.TextBox("Values[@i].Value", @Model.Values.Values[@i])
}

OrderedDictionaryを使用する場合にのみ、インデックスを介してキーと値にアクセスできることに注意してください。

于 2012-05-11T18:20:35.547 に答える
1

Scott hanselman が、辞書への ModelBinding の実行方法を示します

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

ブログより引用

署名が次のようになっている場合:

public ActionResult Blah(IDictionary<string, Company> stocks) {
  // ...
}

そして、これは HTML で与えられます:

<input type="text" name="stocks[0].Key" value="MSFT" />
<input type="text" name="stocks[0].Value.CompanyName" value="Microsoft Corporation" />
<input type="text" name="stocks[0].Value.Industry" value="Computer Software" />
<input type="text" name="stocks[1].Key" value="AAPL" />
<input type="text" name="stocks[1].Value.CompanyName" value="Apple, Inc." />
<input type="text" name="stocks[1].Value.Industry" value="Consumer Devices" />

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

@model Dictionary<string, string>

@for (int i = 0; i < 3; i++)
{    
  Html.EditorFor(m => m[i].Value)    
{

次のようなキーでも機能すると思います

Html.EditorFor(m => m.Values["foo"].Value)
于 2012-05-11T17:54:28.687 に答える
1

各値に編集用のテックスボックスがあるように、ディクショナリをバインドする必要がある場合、以下はそれを機能させる 1 つの方法です。HTML の name 属性の生成方法に影響を与える非常に重要な部分はモデル式です。これにより、ポストバック時にモデル バインディングが確実に行われます。この例は Dictionary でのみ機能します。

リンクされた記事では、バインディングを機能させる HTML 構文について説明していますが、Razor 構文でこれを実現することは非常に謎です。また、この記事は、キーと値の両方を編集できるようにしており、辞書のキーが整数ではなく文字列であるにもかかわらず、整数インデックスを使用しているという点でまったく異なります。そのため、ディクショナリをバインドしようとしている場合は、値を編集可能にするか、キーと値の両方を編集可能にするかを最初に評価してから、どちらのアプローチを取るかを決定する必要があります。これらのシナリオはまったく異なるためです。

複雑なオブジェクト、つまりディクショナリにバインドする必要がある場合は、記事と同様に、プロパティにドリルダウンする式を含む各プロパティのテキストボックスを作成できます。

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

 public class SomeVM
    {
        public Dictionary<string, string> Fields { get; set; }
    }

    public class HomeController : Controller
    {
        [HttpGet]
        public ViewResult Edit()
        {
            SomeVM vm = new SomeVM
            {
             Fields = new Dictionary<string, string>() {
                    { "Name1", "Value1"},
                    { "Name2", "Value2"}
                }
            };

            return View(vm);

        }

        [HttpPost]
        public ViewResult Edit(SomeVM vm) //Posted values in vm.Fields
        {
            return View();
        }
    }

CSHTML:

値のみのエディター (もちろん、LabelFor を追加して、キーに基づいてラベルを生成できます):

@model MvcApplication2.Controllers.SomeVM

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>SomeVM</legend>

        @foreach(var kvpair in Model.Fields)
        {
            @Html.EditorFor(m => m.Fields[kvpair.Key])  //html: <input name="Fields[Name1]" …this is how the model binder knows during the post that this textbox value gets stuffed in a dictionary named “Fields”, either a parameter named Fields or a property of a parameter(in this example vm.Fields).
        }

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

キー/値の両方を編集する:

    @{ var fields = Model.Fields.ToList(); }        

    @for (int i = 0; i < fields.Count; ++i) 
    {
        //It is important that the variable is named fields, to match the property name in the Post method's viewmodel.
        @Html.TextBoxFor(m => fields[i].Key)
        @Html.TextBoxFor(m => fields[i].Value)

        //generates using integers, even though the dictionary doesn't use integer keys,
        //it allows model binder to correlate the textbox for the key with the value textbox:            
        //<input name="fields[0].Key" ...
        //<input name="fields[0].Value" ...

        //You could even use javascript to allow user to add additional pairs on the fly, so long as the [0] index is incremented properly
    }
于 2012-12-04T20:29:13.197 に答える
0

@Blast_Dan と @gprasant が述べたように、モデル バインダーは入力要素の name 属性が format であると想定しています。ここProperty[index].Valueで、 はクラスのプロパティの 1 つです。indexintValueKeyValuePair

残念ながら、@Html.EditorForこの値は間違った形式で生成されます。name 属性を正しい形式に変換する HtmlHelper 拡張機能を作成しました。

public static IHtmlString DictionaryEditorFor<TModel, TProperty, TKey, TValue>(this HtmlHelper<TModel> Html, Expression<Func<TModel, TProperty>> expression, IDictionary<TKey, TValue> dictionary, DictionaryIndexRetrievalCounter<TKey, TValue> counter, string templateName, object additionalViewData)
{
    string hiddenKey = Html.HiddenFor(expression).ToHtmlString();
    string editorValue = Html.EditorFor(expression, templateName, additionalViewData).ToHtmlString();
    string expText = ExpressionHelper.GetExpressionText(expression);
    string indexText = expText.Substring(expText.IndexOf('[')).Replace("[", string.Empty).Replace("]", string.Empty);

    KeyValuePair<TKey, TValue> item = dictionary.SingleOrDefault(p => p.Key.ToString() == indexText);
    int index = counter.GetIndex(item.Key);

    string key = hiddenKey.Replace("[" + indexText + "]", "[" + index + "].Key").Replace("value=\"" + item.Value + "\"", "value=\"" + item.Key + "\"");

    string value = editorValue.Replace("[" + indexText + "]", "[" + index + "].Value");

    return new HtmlString(key + value);
}

整数インデックスは次の規則に従う必要があるためです。

  1. 0 から始める必要があります

  2. 途切れていない必要があります (たとえば、3 から 5 にスキップすることはできません)。

整数インデックスの取得を処理するカウンター クラスを作成しました。

public class DictionaryIndexRetrievalCounter<TKey, TValue>
{
    private IDictionary<TKey, TValue> _dictionary;
    private IList<TKey> _retrievedKeys;

    public DictionaryIndexRetrievalCounter(IDictionary<TKey, TValue> dictionary)
    {
        this._dictionary = dictionary;
        this._retrievedKeys = new List<TKey>();
    }

    public int GetIndex(TKey key)
    {
        if (!_retrievedKeys.Contains(key))
        {
            _retrievedKeys.Add(key);
        }

        return _retrievedKeys.IndexOf(key);
    }
}
于 2012-05-11T20:58:14.810 に答える