For 3 days we rode an emotional roller coaster, hopefully trying new solutions only to end in failure. I don’t see how Edison survived 2,000 attempts at creating the light bulb. By the end of day 2 I was thinking of inflicting bodily harm on somebody, and by the middle of day 3 I was ready for that person to be me. If I’ve painted a depressing picture for you, then as a writer I have done my job: I think this is the first time that a software problem has actually depressed me.
However, as evidenced by the fact that I have yet to do myself in, we did finally manage to put together a solution. I just hope it’s a long time before something so frustrating crosses our paths again.
CORS, AJAX, and Chrome Brian already wrote about the issue in detail, so I’m not going to rehash the specifics. You should go read his post first. Here are the highlights (my version):
- Accessing resources across domains (also known as CORS, or “Cross-Origin Resource Sharing”) requires the remote domain to grant permission to the requesting domain. This makes perfect sense.
- The server must be configured to acknowledge said permission. You can do this through the Response Header or server configuration. This also makes sense.
- Google Chrome ignores these settings. This sucks. Hard.
The problem of POST data Getting past the dreaded origin issue was a major relief, but it led to another problem sending POST data to the Web API controller actions. These actions have object parameters, which should automatically map the incoming data. What we found is that depending on how you prepare the data for the POST submission, sometimes this will work and sometimes it won’t. If you send JSON, the mapping should occur automatically:
This will result in the object being correctly initialized with the JSON data.
Another common approach is to serialize the data first. In the following example, we’re using KnockoutJS’s ko.toJSON method:
If you POST the data in this manner, jQuery will create a Form object, serialize the form, and submit it. When you do this, the parameter object in the Action method will be initialized to default values.
This means you will need to first extract the serialized string from the Request and re-serialize it as JSON:
Next you will need to deserialize the JSON into your object:
This is a little on the ugly side, so naturally we wrote a GenericDeserializer<T> class that takes an HttpContext instance and returns the deserialized object:
Using the GenericDeserializer, here is the completed Action to deserialize POST data:
How to tell if the data was serialized The problem with the preceding approach is that the controller doesn’t inherently know whether the data was serialized or not. If you call the GenericDeserializer when there is no data in the HttpContext to deserialize, it will throw an exception. Rather than relying on try/catch I would prefer to only call the GenericDeserializer if necessary. To that end, I dug around in the Request object and discovered a useful little tidbit that we can use to solve this problem. The solution is in the Request.Params.AllKeys property. When the data is passed as regular JSON, the sent parameters will be the first ones in this list. If, however, the data was serialized, the first item in the AllKeys list will be null. I wrapped this up in an Extension Method to make reuse more palatable:
Now I can decide when to try and deserialize based on this Extension Method. If the data does not need to be deserialized, it will fallback on the default parameter object.
Putting it all together The final solution Action code includes the correct method attributes (see Brian’s post referenced previously), a call to determine if the data is serialized, and if needed call GenericDeserializer:
While there are several places this could be streamlined, like using the Request object instead of HttpContext, I can now implement a consistent way to handle POST data in Web API Action methods without the client implementation being aware of specifically how to send the data.