3

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

我在 ie8 中将复杂对象发布到 cors 服务时遇到问题。对象为空。以下是重现的步骤。如果需要,我可以将项目上传到 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); 

        ...
    }

学分:使用 CORS 在 WebAPI 中将 text/plain 作为复杂对象发布

支持除动词以外的其他函数名称 (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)

<httpProtocol>
   <customHeaders>
     <!-- cors -->
     <add name="Access-Control-Allow-Origin" value="*" />
     <add name="Access-Control-Allow-Headers" value="Content-Type" />
   </customHeaders>
</httpProtocol>

API控制器,我叫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;
    }

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

用 cors (index.cshtml) 填充 ie8:

<script src="~/Scripts/jQuery.XDomainRequest.js"></script>

来源: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 中为空,在 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 中,body 仍然包含内容,并且请求为空。

    [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;
    }
4

2 回答 2

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 和 DELETES,其明显的常见做法是在正在发送的数据或标题中包含一个新节点,称为“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>

正如您在上面的评论中看到的,您还可以通过发起 url(*)和请求类型(put、post 等)来限制 CORS。完全使这样的事情 完全没有必要。 这家伙的博客提供了一个非常好的演练。

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

于 2013-10-01T17:57:16.647 回答
0

在我找到另一个解决方案或者我们可以停止支持 IE8 之前,这是公认的 hack。感谢一位同事提出这个问题。

  1. 去掉 global.asax 对 text/plain 的支持,ie8 发送的 header 全部为空。正如评论中所讨论的,请求正文不会自动解析。内容保留在正文中。通常(比如在 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 现在具有发送复杂数据的能力。

于 2013-09-25T17:01:59.467 回答