Вопрос по asp.net-mvc-3, dictionary, c# – Словарь MVC3 не привязан к модели

2

У меня есть модель, которая содержит свойство словаря. (это было извлечено из более крупного проекта в этот пример, который, как я подтвердил, все еще имеет ту же проблему)

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

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

контроллер

<code>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'
    }
}
</code>

и вид

<code>@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" />
}
</code>

Это отображает в браузере, как это:

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

Проблема, с которой я сталкиваюсь, состоит в том, что словарь является нулевым в модели после обратной передачи.

Am I doing this right, or is there a better way?

Мне нужно иметь какое-то хранилище значений ключей, так как поля в моей форме являются переменными, поэтому я не могу использовать модель POCO.

@Blast_dan. Интересно. Словарь - это просто коллекция KeyValuePairs, поэтому имеет смысл, что использование такой структуры вызовет проблемы. Я попробую использовать пользовательский класс KeyValuePair, чтобы проверить, работает ли он. Kyle Trauberman
Похоже, у этого парня есть решение для пары ключевых значенийstackoverflow.com/questions/1300642/… Надеюсь, это сработает для словаря Blast_dan
Попробуйте подход EditorTemplate, я проверил его со списком, проверьте, будет ли он работать со словаремienablemuch.com/2012/04/aspnet-mvc-editor-templates.html Michael Buen

Ваш Ответ

4   ответа
1

чтобы каждое значение имело текстовое поле для его редактирования, ниже приведен один из способов заставить его работать. Действительно важными частями, которые влияют на то, как генерируется атрибут name в HTML, является выражение модели, которое обеспечивает связывание модели при обратной передаче. Этот пример работает только для словаря.

В связанной статье объясняется синтаксис 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:

Editors for Values only(конечно, вы можете добавить 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>
}

Editing both Keys/Values:

    @{ 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
    }
2

олее подробной информации, но пока, чтобы исправить вашу проблему, просто замените ваш взгляд следующим образом:

<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

@KyleTrauberman Это не то, как DefaultModelBinder работает в случае коллекций. Когда он пытается создать словарь, ему нужно собрать несколько пар значений ключей. И для этого нужна очень специфическая структура. Вот почему это не работает в соответствии с вашей интуицией
Я попытался написать HTML, как у вас выше, и это работает. Вещь, которая озадачивает меняValues[foo] уже содержит ключ, поэтому можно подумать, что связыватель модели может выяснить, что значение элемента управления вводом принадлежит ключу. Kyle Trauberman
Ну, вы могли бы написать свой собственный Html Helper, чтобы сгенерировать этот Html Helper для вас, но он не будет очень полезен для других сценариев. И нет, вы не можете добиться этого с помощью EditorFor ()
проверьте, будут ли помощники, которых я добавил выше, работать на вас
Я не хочу создавать HTML самостоятельно, я хочу использовать@Html.EditorFor, Есть ли способ использовать это и добиться результата в посте Скотта? Kyle Trauberman
1

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

Цитата из блога

If the signature looks like this:

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

And we are given this in 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)
Это не перезаписывает свойство name. Kyle Trauberman
Проблема в том, что мой сгенерированный html не включает.Key а также.Value в свойстве имени входа. Kyle Trauberman
Или @ Html.EditorFor (model = & gt; model, & quot; TemplateName & quot ;, & quot; foo [0] .Value & quot;) также должен работать.
Это не то, что VS говорит мне:i.imgur.com/R21DE.jpg Kyle Trauberman
Разве вы не можете использовать что-то подобное? @ Html.TextBoxFor (model = & gt; model.Values, new {name = & quot; foo [0] .Value & quot;})
0

связыватель модели ожидает, что атрибут имени элемента ввода будет в форматеProperty[index].Value, гдеindex являетсяint а такжеValue является одним из свойств наKeyValuePair учебный класс.

К несчастью,@Html.EditorFor генерирует это значение в неправильном формате. Я написал расширение 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);
}

Поскольку целочисленный индекс должен следовать этим правилам:

Must start with 0

Must be unbroken (you can't skip from 3 to 5, for example)

Я написал счетчик класса для обработки получения целочисленного индекса для меня:

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

Похожие вопросы