How do I pass a Dictionary as a parameter to an ActionResult method from jQuery/Ajax?
At last I figured it out!! Thanks for the suggestions everyone! I finally figured out the best solution is to pass JSON via the Http Post and use a custom ModelBinder to convert the JSON to a Dictionary. One thing I did in my solution is created a JsonDictionary object that inherits from Dictionary so that I can attach the custom ModelBinder to the JsonDictionary type, and it wont cause any conflicts in the future if I use Dictionary as a ActionResult parameter later on for a different purpose than JSON.
Here's the final ActionResult method:
public ActionResult AddItems([Bind(Include="values")] JsonDictionary values){ // do something}
And the jQuery "$.post" call:
$.post("/Controller/AddItems",{ values: Sys.Serialization.JavaScriptSerializer.serialize( { id: 200, "name": "Chris" } )},function(data) { },"json");
Then the JsonDictionaryModelBinder needs to be registered, I added this to the Application_Start method within the Global.asax.cs:
protected void Application_Start(){ ModelBinders.Binders.Add(typeof(JsonDictionary), new JsonDictionaryModelBinder());}
And, finally here's the JsonDictionaryModelBinder object and JsonDictionary object I created:
public class JsonDictionary : Dictionary<string, object>{ public JsonDictionary() { } public void Add(JsonDictionary jsonDictionary) { if (jsonDictionary != null) { foreach (var k in jsonDictionary.Keys) { this.Add(k, jsonDictionary[k]); } } }}public class JsonDictionaryModelBinder : IModelBinder{ #region IModelBinder Members public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext.Model == null) { bindingContext.Model = new JsonDictionary(); } var model = bindingContext.Model as JsonDictionary; if (bindingContext.ModelType == typeof(JsonDictionary)) { // Deserialize each form/querystring item specified in the "includeProperties" // parameter that was passed to the "UpdateModel" method call // Check/Add Form Collection this.addRequestValues( model, controllerContext.RequestContext.HttpContext.Request.Form, controllerContext, bindingContext); // Check/Add QueryString Collection this.addRequestValues( model, controllerContext.RequestContext.HttpContext.Request.QueryString, controllerContext, bindingContext); } return model; } #endregion private void addRequestValues(JsonDictionary model, NameValueCollection nameValueCollection, ControllerContext controllerContext, ModelBindingContext bindingContext) { foreach (string key in nameValueCollection.Keys) { if (bindingContext.PropertyFilter(key)) { var jsonText = nameValueCollection[key]; var newModel = deserializeJson(jsonText); // Add the new JSON key/value pairs to the Model model.Add(newModel); } } } private JsonDictionary deserializeJson(string json) { // Must Reference "System.Web.Extensions" in order to use the JavaScriptSerializer var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); return serializer.Deserialize<JsonDictionary>(json); }}
This is what I tried. Saves a lot of work.Javascript:
var dict = {}; dict["id"] = "200"; dict["FirstName"] = "Chris"; dict["DynamicItem1"] = "Some Value"; dict["DynamicItem2"] = "Some Other Value"; var theObject = {}; theObject.dict = dict; $.post(URL, theObject, function (data, textStatus, XMLHttpRequest) { console.log("success"); }, "json");
Action Method:
public ActionResult MethodName(DictionaryModel obj) { //Action method logic }public class DictionaryModel{ public Dictionary<string, string> dict { get; set; }}
It's possible with custom model binders or filters. Behind the scenes - you will have to do it manually anyway (Request.Form, parse strings, create dictionary tralala), but at least - your controller will be clean and code will be reusable for another actions.