MVC3字典没有绑定到模型

时间:2012-05-11 17:23:56

标签: c# asp.net-mvc-3 dictionary

我有一个包含字典属性的模型。 (这已从一个较大的项目中提取到这个例子中,我已经确认它仍有同样的问题)

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" />

我遇到的问题是回发后模型上的字典为空。

  • 我这样做是对,还是有更好的方法?

我需要有某种键值存储,因为我表单上的字段是可变的,所以我不能使用POCO模型。

4 个答案:

答案 0 :(得分: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

时,才能通过索引访问键和值

答案 1 :(得分:1)

Scott hanselman展示了如何将ModelBinding转换为Dictionary

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)

答案 2 :(得分:1)

如果你需要绑定一个Dictionary,这样每个值都有一个texbox来编辑它,下面是一种让它工作的方法。影响HTML中name属性生成方式的非常重要的部分是模型表达式,它确保在回发时发生模型绑定。此示例仅适用于Dictionary。

链接的文章解释了使绑定工作的HTML语法,但是它留下了Razor语法来实现这一点非常神秘。此外,文章的不同之处在于它们允许编辑键和值,并使用整数索引,即使字典的键是字符串,而不是整数。因此,如果您尝试绑定字典,在确定采用哪种方法之前,首先需要先评估是否只需要值可编辑,或者键和值都是可编辑的,因为这些方案完全不同。 / p>

如果您需要绑定到复杂对象,即Dictionary,那么您应该能够为每个属性创建一个文本框,并将表达式钻入属性,类似于文章。

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
    }

答案 3 :(得分:0)

正如@Blast_Dan和@gprasant所提到的,模型绑定器期望输入元素的name属性采用Property[index].Value格式,其中indexint和{{ 1}}是Value类的一个属性。

不幸的是,KeyValuePair以错误的格式生成此值。我写了一个HtmlHelper扩展来将name属性转换为正确的格式:

@Html.EditorFor

因为整数索引必须遵循以下规则:

  1. 必须以0

  2. 开头
  3. 必须不间断(例如,你不能跳过3到5)

  4. 我写了一个计数器类来处理我的整数索引:

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