作为我工作环境的一部分,我们需要支持IE8,但我们希望继续推进技术,特别是CORS。
我无法在ie8中将复杂对象发布到cors服务。该对象为null。以下是重现的步骤。如果需要,我可以将项目上传到github。
我创建了一个新的mvc4项目。添加了API控制器。并做了以下更改。
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(); }
}
来源:Handling CORS Preflight requests to ASP.NET MVC actions
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);
...
}
信用:Posting text/plain as a complex object in WebAPI with CORS
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "APICustom",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
...
}
<httpProtocol>
<customHeaders>
<!-- cors -->
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
</customHeaders>
</httpProtocol>
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;
}
这是服务变更的程度。接下来我创建一个客户端。这也是一个mvc4项目。这里非常基本的东西。
<script src="~/Scripts/jQuery.XDomainRequest.js"></script>
来源:https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest
$(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}
我可以确认正在发送正文,但是没有被web api解析。如果我添加以下hack,它将按预期返回数据。在Firefox中,正文将为空,并填充请求对象。在ie8中,主体仍包含内容,请求为空。
[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;
}
答案 0 :(得分:3)
这是我正在谈论的代码。创建这个作为一个新类,我在我的WebAPI项目中创建了一个DelegatingHandlers文件夹(但是,我还有一个过滤器文件夹,一个模型绑定文件夹...)
我收录了很多您可以轻松删除的评论。
以下假设IE 8/9将始终发送“JSON”数据。如果您的webAPI实现允许内容协商,并且您想要为IE8 / 9包含该功能,那么您显然需要在下面的代码中添加一些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;
/// <summary>
/// Gives the WebAPI the ability to handle XDomainRequest objects with embedded JSON data.
/// </summary>
public class XDomainRequestDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> 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中一样简单。
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<!--<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />-->
</customHeaders>
</httpProtocol>
正如您在上面的评论中所看到的,您还可以通过发起网址(*)和请求类型(put,post等)来限制CORS。完全不需要stuff like this 完全。 This guy's blog gives a really good walkthrough.
这就是你需要对一个全新的WebAPI项目做的所有事情,使它支持CORS和IE 8/9。
答案 1 :(得分:0)
直到我能找到另一个解决方案或者我们可以停止支持IE8这里是接受的黑客攻击。感谢同事想出这个。
在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;
}
}
}
如下更改人员控制器
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现在能够发送复杂的数据。