As part of my work environment we need to support IE8, but would like to move forward with technology, specifically CORS.
I'm having trouble posting complex objects to a cors service in ie8. The object is null. Below are the steps to reproduce. If needed i can upload the project to github.
I've created a new mvc4 project. Added a API Controller. And made the following changes.
To Support preflight complex cors calls (global.asax):
protected void Application_BeginRequest()
{
//This is needed for the preflight message
//https://stackoverflow.com/questions/13624386/handling-cors-preflight-requests-to-asp-net-mvc-actions
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS") { Response.Flush(); }
}
Source: Handling CORS Preflight requests to ASP.NET MVC actions
To Support text/plain (ie8 only sends text/plain with cors)(global.asax):
protected void Application_Start()
{
//This is needed to support text/plain
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
config.Formatters.Remove(config.Formatters.FormUrlEncodedFormatter);
config.Formatters.Remove(config.Formatters.XmlFormatter);
...
}
Credit: Posting text/plain as a complex object in WebAPI with CORS
To Support additional function names other than just verbs (put/post/etc) (WebApiConfig.cs)"
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "APICustom",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
...
}
To support cors (web.config)
<httpProtocol>
<customHeaders>
<!-- cors -->
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
</customHeaders>
</httpProtocol>
API Controller, I called PersonController.cs
public class PersonController : ApiController
{
public List<string> Get()
{
List<string> s = new List<string>();
s.Add("s");
s.Add("t");
s.Add("u");
return s;
}
[Serializable()]
public class BaseReply
{
public bool successful = true;
public string error;
}
[Serializable()]
public class UpdateSomethingReply: BaseReply
{
public UpdateSomethingRequest request;
public List<string> stuff = new List<string>();
}
[Serializable()]
public class UpdateSomethingRequest
{
public int hasInt;
public string hasString;
}
//[FromBody]
[HttpPost]
public UpdateSomethingReply UpdateSomething([FromBody] UpdateSomethingRequest request)
{
string body = Request.Content.ReadAsStringAsync().Result;
UpdateSomethingReply reply = new UpdateSomethingReply();
reply.request = request;
reply.stuff.Add("v");
reply.stuff.Add("w");
reply.stuff.Add("x");
return reply;
}
That is the extent on the changes on the service. So next I create a client. This is also an mvc4 project. Pretty basic stuff here.
To polyfill ie8 with cors (index.cshtml):
<script src="~/Scripts/jQuery.XDomainRequest.js"></script>
Source: https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest
To call the cors service
$(document).ready(function () {
$.when(
$.ajax({
url: urls.person.UpdateSomething,
type: 'post',
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({
hasInt: 1,
hasString: "u"
})
})
)
.fail(function (jqXHR, textStatus, errorThrown) {
})
.done(function (data) {
console.log(JSON.stringify(data));
});
$.when(
$.ajax({
url: urls.person.Get,
dataType: 'json'
})
)
.fail(function (jqXHR, textStatus, errorThrown) {
})
.done(function (data) {
console.log(JSON.stringify(data));
});
$.when(
$.ajax({
url: urls.person.UpdateSomething,
type: 'post',
contentType: "text/plain",
dataType: 'json',
data: JSON.stringify({
hasInt: 1,
hasString: "u"
})
})
)
.fail(function (jqXHR, textStatus, errorThrown) {
})
.done(function (data) {
console.log(JSON.stringify(data));
});
});
As i stated earlier all 3 calls complete in ie8. But the request object in the service is null in ie8 and in firefox it is populated, even when i force the content-type to be text/plain
IE8 Console Output:
{"request":null,"stuff":["v","w","x"],"successful":true,"error":null}
Firefox Console Output:
{"request":{"hasInt":1,"hasString":"u"},"stuff":["v","w","x"],"successful":true,"error":null}
Update 9/25/2013
I can confirm that the body is being sent, but isn't being parsed by web api. If I add the following hack it will return the data as expected. In firefox the body will be empty and the request object is populated. In ie8 the body still contains the contents and the request is null.
[HttpPost]
public UpdateSomethingReply UpdateSomething(UpdateSomethingRequest request)
{
if (request == null && Request.Content.ReadAsStringAsync().Result !="")
{
request = JsonConvert.DeserializeObject<UpdateSomethingRequest>(Request.Content.ReadAsStringAsync().Result);
}
UpdateSomethingReply reply = new UpdateSomethingReply();
reply.request = request;
reply.body=Request.Content.ReadAsStringAsync().Result;
reply.headers = Request.Headers.ToString();
reply.stuff.Add("v");
reply.stuff.Add("w");
reply.stuff.Add("x");
return reply;
}
Until I can find another solution or we can stop supporting IE8 here is the accepted hack. Credit to a coworker for coming up with this.
In App_Start create a class called GenericBinder
Change person controller as follows
IE8 now has the ability to send in complex data.
here's the code I was talking about. Create this as a new class, I created a DelegatingHandlers folder in my WebAPI project (but then again, I also have a filters folder, A model bindings folder...)
I've included TONS of comments that you could easily remove.
The below assumes IE 8/9 will always be sending "JSON" data. If your webAPI implementation allows content negotiation, and you want to include that feature for IE8/9 then you will obviously need to add a few if statements to the below code, but this should be more than enough to get you going. I personally just stated that I only accept JSON from IE 8/9.
My WebAPIConfig file looks as follows:
Then to make sure my POST calls were IE 8 and 9 compliant, in my JS I put the following (though obviously you only need to include this if you are also consuming your own API)
Personally, I'm using JSONP for GETs, and not using PUTS or DELETES whatsoever, so that's sufficient for me. If I were to do this project over again, I would use PUTS and DELETES. To make IE 8 / 9 handle cross domain PUTS and DELETES its apparently common practice to include a new node on the data being sent, or in the header, called some variant of "Type", and use a string "PUT" or "DELETE". I'm not sure where I'd sniff that out though.
Enabling CORS was as easy as putting the following in the Web.Config.
As you can see in the above comment, you can also restrict CORS by originating url (the *) and the type of request (put, post, etc). Totally makes stuff like this completely unnecessary. This guy's blog gives a really good walkthrough.
And that's literally all you need to do to a brand new WebAPI project to make it Support both CORS and IE 8/9.