Вопрос по asp.net-mvc-3, dictionary, c# – Словарь MVC3 не привязан к модели
У меня есть модель, которая содержит свойство словаря. (это было извлечено из более крупного проекта в этот пример, который, как я подтвердил, все еще имеет ту же проблему)
<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.
чтобы каждое значение имело текстовое поле для его редактирования, ниже приведен один из способов заставить его работать. Действительно важными частями, которые влияют на то, как генерируется атрибут name в HTML, является выражение модели, которое обеспечивает связывание модели при обратной передаче. Этот пример работает только для словаря.
В связанной статье объясняется синтаксис HTML, который делает связывание работающим, но он оставляет синтаксис Razor, чтобы сделать это довольно загадкой. Кроме того, статья отличается тем, что позволяет редактировать ключи и значения и использует целочисленный индекс, хотя ключ словаря является строкой, а не целым числом. Поэтому, если вы пытаетесь связать словарь, вам действительно нужно сначала оценить, хотите ли вы, чтобы значения были редактируемыми, или же ключи и значения, прежде чем вы решите, какой подход использовать, потому что эти сценарии совершенно разные. ,
Если вам когда-либо понадобится привязать к сложному объекту, то есть к словарю, вы просто должны иметь текстовое поле для каждого свойства с выражением, сверлящим в это свойство, аналогично статье.
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
}
олее подробной информации, но пока, чтобы исправить вашу проблему, просто замените ваш взгляд следующим образом:
<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
Values[foo]
уже содержит ключ, поэтому можно подумать, что связыватель модели может выяснить, что значение элемента управления вводом принадлежит ключу.
@Html.EditorFor
, Есть ли способ использовать это и добиться результата в посте Скотта?
Цитата из блога
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)
.Key
а также.Value
в свойстве имени входа.
связыватель модели ожидает, что атрибут имени элемента ввода будет в формате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);
}
}