1

When submitting a JSON body to an MVC application I get the following exception during binding:

An item with the same key has already been added.

This happens when a HTTP body has 2+ JSON properties that rely on case sensitivity for uniqueness. E.g.

{ "foo":1, "FOO":2 }

The Content-Type must also be specified as application\json for the exception to occur.

I am not really sure what to do to stop this from happening. I tried a custom IModelBinder but this occurs before IModelBinder.BindModel(...) is even called. This all seems to happen in standard framework code before the request gets passed to me.

Full stack trace:

System.ArgumentException: An item with the same key has already been added.
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
   at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
   at System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
   at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
   at System.Web.Mvc.ControllerBase.get_ValueProvider()
   at System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext,     ParameterDescriptor parameterDescriptor)
   at System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.<BeginInvokeAction>b__19(AsyncCallback asyncCallback, Object asyncState)

How do I prevent this from occuring given that the JSON payload is perfectly valid?

Edit:

I've read a little more and I now believe the problem is in the behaviour of the default IValueProvider rather than the binder. I think if a content type of application\json is specified then the value provider is automatically parsing the JSON properties into a (case insensitive) dictionary for use by the ModelBinder afterwards. This makes more sense given the stack trace I had.

I am still not 100% sure how to accommodate for this. Is writing a custom IValueProvider for this controller the only choice here?

AndySavage
  • 1,729
  • 1
  • 20
  • 34
  • What are you using for deserialization? Likely it has a setting for case (in)sensitivity. – Matt Burland Oct 25 '14 at 00:55
  • Whatever the built in MVC 4 default is. I'm not handling the serialization myself - this is default behaviour passing JSON to an MVC action, and this is framework code that runs before any that I have added (it's in the stack trace, but I should update the question for clarity). – AndySavage Oct 25 '14 at 00:58
  • Check out this question: http://stackoverflow.com/questions/13274625/how-to-set-custom-jsonserializersettings-for-json-net-in-mvc-4-web-api – Matt Burland Oct 25 '14 at 01:02
  • The `DefaultModelBinder` is not case sensitive. What you are doing is creating an object with 2 properties with identical names which is not valid when binding (try creating a model with 2 identical properties - it wont compile). Are you trying to post back a collection? –  Oct 25 '14 at 01:06
  • It's an API endpoint that takes a JSON payload including free form properties. An end user is submitting us JSON where the property names are the same except for case. Unfortunately, we need to support it. – AndySavage Oct 25 '14 at 01:10
  • Is converting that portion of request "manually" in controller an option? Than keep it string and try to parse with JSON.Net in controller. – Alexei Levenkov Oct 25 '14 at 02:21
  • Unfortunately not @AlexeiLevenkov. Even if you have a controller action with a string param, the default MVC ValueProvider will still run before you get that far and raise the exception. I have updated the question a little. – AndySavage Oct 25 '14 at 15:13
  • Andy, did you ever resolve this issue? In my case it's external webhooks hitting an MVC site, causing 500s due to repeated properties with different cases in the json. – football Sep 30 '15 at 17:24
  • @football Very similar case to ours then. I did resolve it though I've no idea if it was the best way. I made a custom `ValueProviderFactory` which essentially did nothing and added that to the `ValueProviderFactories.Factories` collection ahead of the default binder. This meant it could intercept JSON requests before the in built MVC handler. We also made a custom `IModelBinder` for the JSON requests so we could pass the decoded JSON into the controller as an object, but you could just read the body manually if preferred. I should probably expand this into an answer when I have time. – AndySavage Sep 30 '15 at 17:37
  • yep that's pretty much what I've done as well. with the addition that I wanted to do this on a per controller basis, so I have an intermediate factory that checks the controllerContext to determine the correct Json provider to return. – football Sep 30 '15 at 18:42

1 Answers1

2

If the Content-Type is not application\json then the FormValueProvider is used to build the dictionary which ignores case and combines the values as the following shows

[HttpPost]
public ActionResult YourMethod(FormCollection form)
{    
  var fooA= Request.Form["foo"]; // returns "1,2"
  var fooB= Request.Form["FOO"]; // returns "1,2"
  var fooC= Request.Form["FoO"]; // returns "1,2"

If the Content-Type is application\json then the JsonValueProviderFactory is used to build the dictionary which in turn uses JavaScriptSerializer() to deserialize the request but this throws an exception because of the duplicate (case-insensitive) keys. You could possibly write a custom JavaScriptConverter to handle this although I am not sure if that is appropriate, because Value Providers are used in conjunction with Model Binders to build a model(s) and your model cant contain both properties foo and FOO (unless perhaps if your intending to map foo and FOO to other known model properties).

You can read the values from the Request.InputStream

[HttpPost]
public ActionResult YourMethod()
{
  StreamReader reader = new StreamReader(Request.InputStream);
  string body = reader.ReadToEnd(); // returns "foo=1&FOO=2"

and the use body .Split('&') to create an array of key/value pairs

  • Thanks, though I don't think your answer is correct. HTTP headers are indeed case insensitive, but that has nothing to do with the HTTP body (which is where action parameters are parsed). The given JSON is a perfectly valid HTTP body. A body is free form with its structure indicated only by the `Content-Type` header. – AndySavage Oct 25 '14 at 15:03
  • OK, misunderstood. I'll update answer shortly showing how you can at least read the key/values of the JSON. I'm not sure that writing a custom ValueProvider for the controller is going to solve the issue because that's used by a `ModelBinder` to bind to your model, but your model cant have properties `string FOO` and `string foo`. –  Oct 26 '14 at 06:14