Is it possible to bind such kind of property?
public KeyValuePair Stuff { get; set; }
I\'ve tried to use following co
<%=Html.Text("Stuff.Value", Model.Stuff.Value)%>
Might work?
If you need to bind a Dictionary, such that each value has a texbox to edit it, below is one way to make it work. The really important parts which effect how the name attribute in the HTML is generated is the model expression, which is what ensures the model binding occurs on postback. This example only works for Dictionary.
The linked article explains the HTML syntax that makes the binding work, but it leaves the Razor syntax to accomplish this quite a mystery. Also, the article is quite different in that they are allowing both Keys and Values to be edited, and are using an integer index even though the dictionary’s key is a string, not an integer. So if you are trying to bind a Dictionary, you’ll really need to evaluate first whether you just want Values to be editable, or both keys and values, before you decide on which approach to take, because those scenarios are quite different.
If you ever need to bind to a complex object, i.e. Dictionary then you should just be able to have a textbox for each property with the expression drilling into the property, similar to the article.
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(of course you could add LabelFor to generate labels based on the Key):
@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
}
KeyValuePair<K,V>
is a structure, not a class, so each call to your Stuff
property returns a copy of the original KeyValuePair
. So, when you bind to Model.Stuff.Value
and to Model.Stuff.Key
, you are actually working on two different instances of KeyValuePair<K,V>
, none of which is the one from your model. So when they are updated, it doesn't update the Stuff property in your model... QED
By the way, the Key and Value properties are read-only, so you can't modify them : you have to replace the KeyValuePair
instance
The following workaround should work :
Model :
private KeyValuePair<string, string> _stuff;
public KeyValuePair<string, string> Stuff
{
get { return _stuff; }
set { _stuff = value; }
}
public string StuffKey
{
get { return _stuff.Key; }
set { _stuff = new KeyValuePair<string, string>(value, _stuff.Value); }
}
public string StuffValue
{
get { return _stuff.Value; }
set { _stuff = new KeyValuePair<string, string>(_stuff.Key, value); }
}
View :
<%=Html.Text("Stuff", Model.StuffValue)%>
<%=Html.Hidden("Model.StuffKey", Model.StuffKey)%>
I know that this is a bit older question, but I didn't like any of suggested solution so I give mine. I have rewritten default model binder to handle KeyValuePairs so I can use them as before.
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext);
model = ResolveKeyValuePairs(bindingContext, model);
return model;
}
private object ResolveKeyValuePairs(ModelBindingContext bindingContext, object model)
{
var type = bindingContext.ModelType;
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof (KeyValuePair<,>))
{
var values = bindingContext.ValueProvider as ValueProviderCollection;
if (values != null)
{
var key = values.GetValue(bindingContext.ModelName + ".Key");
var keyValue = Convert.ChangeType(key.AttemptedValue, bindingContext.ModelType.GetGenericArguments()[0]);
var value = values.GetValue(bindingContext.ModelName + ".Value");
var valueValue = Convert.ChangeType(value.AttemptedValue, bindingContext.ModelType.GetGenericArguments()[1]);
return Activator.CreateInstance(bindingContext.ModelType, new[] {keyValue, valueValue});
}
}
}
return model;
}