Cors,Web Api,IE8,Post Complex Data

作为我工作环境的一部分,我们需要支持IE8,但希望继续推进技术,特别是CORS。

我在将复杂对象发布到ie8中的cors服务时遇到了麻烦。 该对象为null。 以下是重现的步骤。 如果需要,我可以将项目上传到github。

我创建了一个新的mvc4项目。 添加了API控制器。 并做了以下更改。

支持预检复杂的cors调用(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(); } } 

来源: 处理对ASP.NET MVC操作的CORS预检请求

支持text / plain(ie8只发送带有cors的text / plain)(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: 使用CORS在WebAPI中将text / plain作为复杂对象发布

支持除动词以外的其他function名称(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 } ); ... } 

支持cors(web.config)

        

API控制器,我叫PersonController.cs

  public class PersonController : ApiController { public List Get() { List s = new List(); 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 stuff = new List(); } [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; } 

这就是服务变化的程度。 接下来我创建一个客户端。 这也是一个mvc4项目。 这里非常基本的东西。

要用cors(index.cshtml)填充ie8:

  

资料来源: https : //github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest

致电cors服务

  $(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)); }); }); 

正如我之前所说,所有3个电话都在ie8完成。 但是服务中的请求对象在ie8中为null,在firefox中它被填充,即使我强制内容类型为text / plain

IE8控制台输出:

 {"request":null,"stuff":["v","w","x"],"successful":true,"error":null} 

Firefox控制台输出:

 {"request":{"hasInt":1,"hasString":"u"},"stuff":["v","w","x"],"successful":true,"error":null} 

2013年9月25日更新

我可以确认正在发送正文,但是没有被web api解析。 如果我添加以下hack,它将按预期返回数据。 在firefox中,正文将为空,并填充请求对象。 在ie8中,主体仍包含内容,请求为空。

  [HttpPost] public UpdateSomethingReply UpdateSomething(UpdateSomethingRequest request) { if (request == null && Request.Content.ReadAsStringAsync().Result !="") { request = JsonConvert.DeserializeObject(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; } 

这是我正在谈论的代码。 创建这个作为一个新类,我在我的WebAPI项目中创建了一个DelegatingHandlers文件夹(但是,我还有一个filter文件夹,一个模型绑定文件夹…)

我已经包含了很多你可以轻松删除的评论。

以下假设IE 8/9将始终发送“JSON”数据。 如果您的webAPI实现允许内容协商,并且您想要为IE8 / 9包含该function,那么您显然需要在下面的代码中添加一些if语句,但这应该足以让您前进。 我个人刚刚声明我只接受来自IE 8/9的JSON。

 namespace REDACTED.WebApi.DelegatingHandlers { using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; ///  /// Gives the WebAPI the ability to handle XDomainRequest objects with embedded JSON data. ///  public class XDomainRequestDelegatingHandler : DelegatingHandler { protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // XDomainRequest objects set the Content Type to null, which is an unchangable setting. // Microsoft specification states that XDomainRequest always has a contenttype of text/plain, but the documentation is wrong. // Obviously, this breaks just about every specification, so it turns out the ONLY extensibility // point to handle this is before the request hits the WebAPI framework, as we do here. // To read an apology from the developer that created the XDomainRequest object, see here: // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx // By international specification, a null content type is supposed to result in application/octect-stream (spelling mistake?), // But since this is such an edge case, the WebAPI framework doesn't convert that for us before we hit this point. It is unlikely, // but possible that in a future Web.Api release, we will need to also sniff here for the octect header. if (request.Content.Headers.ContentType == null) { request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } return base.SendAsync(request, cancellationToken); } } } 

我的WebAPIConfig文件如下所示:

  public static void Register(HttpConfiguration config) { // Normal config.Routes statements go here // Deserialize / Model Bind IE 8 and 9 Ajax Requests config.MessageHandlers.Add(new XDomainRequestDelegatingHandler()); } 

然后为了确保我的POST调用符合IE 8和9,在我的JS中我放了以下内容(尽管显然你只需要包含这个,如果你也在使用你自己的API)

 esbPost: function (apiUrl, apiData, fOnSuccess, fOnFailure) { $.support.cors = true; // Not sure that I need this. var testModernAjax = function () { if (window.XMLHttpRequest) { var testRequest = new XMLHttpRequest; // IE 8 / 9 with jQuery can create XMLHttpRequest objects, but only modern // CORS implementing browsers (everything + IE10) include the withCredentials specification. if ('withCredentials' in testRequest) { return true; } return false; } return false; }; var testMsieAjax = function () { if (window.XDomainRequest) { return true; } return false; }; //All browsers, and IE 10 if (testModernAjax()) { $.ajax({ url: apiUrl, type: 'POST', dataType: 'json', data: apiData, success: function (result) { if (fOnSuccess) { fOnSuccess(result); } }, error: function (jqXHR, textStatus, errorThrown) { if (fOnFailure) { fOnFailure(jqXHR, textStatus, errorThrown); } } }); //IE 8 / 9 } else if (testMsieAjax()) { var xdr = new XDomainRequest(); xdr.onload = function () { var parsedResponse = $.parseJSON(xdr.responseText); if (fOnSuccess) { fOnSuccess(parsedResponse); } }; xdr.onerror = function () { if (fOnFailure) { fOnFailure(); } }; xdr.onprogress = function () { }; xdr.open("post", apiUrl); xdr.send(JSON.stringify(apiData)); } else { // IE 7 can only do AJAX calls through a flash/iframe exploit, earlier do not include ajax support. throw new 'This browser is unsupported for this solution.'; } }, 

就个人而言,我正在使用JSONP进行GET,而不是使用PUTS或DELETES,这对我来说已经足够了。 如果我再次做这个项目,我会使用PUTS和DELETES。 要使IE 8/9处理跨域PUTS并删除其显然常见的做法,即在发送的数据或标题中包含一个新节点,称为“Type”的某个变体,并使用字符串“PUT”或“DELETE” ”。 我不知道在哪里我会嗅到它。

启用CORS就像在Web.Config中添加以下内容一样简单。

        

正如您在上面的注释中所看到的,您还可以通过发起url(*)和请求类型(put,post等)来限制CORS。 完全不需要这样的东西 。 这家伙的博客给出了非常好的演练。

这就是你需要对一个全新的WebAPI项目做的所有事情,使它支持CORS和IE 8/9。

直到我能找到另一个解决方案或我们可以停止支持IE8这里是被接受的黑客。 感谢同事想出这个。

  1. 删除对global.asax中text / plain的支持,ie8发送的头都为null。 正如评论中所讨论的,请求正文不会自动解析。 内容保留在体内。 通常(比如在Firefox中)将主体解析为请求对象并替换为空字符串。
  2. 在App_Start中创建一个名为GenericBinder的类

     using System; using System.Collections.Generic; using System.Linq; using System.Web; using Newtonsoft.Json; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; namespace Admin2 { public class GenericBinder : IModelBinder { public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { bindingContext.Model = JsonConvert.DeserializeObject(actionContext.Request.Content.ReadAsStringAsync().Result, bindingContext.ModelType); return true; } } } 
  3. 更改人员控制器如下

     using System.Web.Http.ModelBinding; ... [HttpPost] public UpdateSomethingReply UpdateSomething([ModelBinder(typeof(GenericBinder))] UpdateSomethingRequest request) { UpdateSomethingReply reply = new UpdateSomethingReply(); reply.request = request; reply.stuff.Add("v"); reply.stuff.Add("w"); reply.stuff.Add("x"); return reply; } 

IE8现在能够发送复杂数据。