11

Is it possible to bind such kind of property?

public KeyValuePair<string, string> Stuff { get; set; }

I've tried to use following code in the view, but it does not work:

<%=Html.Text("Stuff", Model.Stuff.Value)%>    
<%=Html.Hidden("Model.Stuff.Key", Model.Stuff.Key)%>
Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
Andrey Sidorenko
  • 180
  • 1
  • 1
  • 11

4 Answers4

8

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)%>
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 1
    Thank you for workaround, it works. It doesn't look pretty, but that's enough for this case. – Andrey Sidorenko Aug 19 '09 at 15:48
  • Seems like it is cleaner to use a class for this. It is frustrating -- more code (as above) or another class. Hrm... Thanks for asking this. – Cymen Jun 02 '11 at 17:07
  • Also [this](http://khalidabuhakmeh.com/submitting-a-dictionary-to-an-asp-net-mvc-action) , post using `Dictionary` can help someone. – Shaiju T Jan 25 '17 at 10:23
1

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
    }
AaronLS
  • 37,329
  • 20
  • 143
  • 202
0
<%=Html.Text("Stuff.Value", Model.Stuff.Value)%>

Might work?

Daniel Elliott
  • 22,647
  • 10
  • 64
  • 82
0

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;
    }
Petr J
  • 182
  • 4
  • 12