【问题标题】:JSONP with ASP.NET Web APIJSONP 与 ASP.NET Web API
【发布时间】:2012-03-14 08:18:19
【问题描述】:

我正在使用 Web API 在 ASP.MVC MVC 4 中创建一组新的服务。到目前为止,这很棒。我已经创建了服务并让它工作,现在我正在尝试使用 JQuery 来使用它。我可以使用 Fiddler 取回 JSON 字符串,这似乎没问题,但是由于该服务存在于一个单独的站点上,因此尝试使用带有“不允许”的 JQuery 错误来调用它。所以,这显然是我需要使用 JSONP 的情况。

我知道 Web API 是新的,但我希望有人可以帮助我。

如何使用 JSONP 调用 Web API 方法?

【问题讨论】:

  • 在观看了 Channel9 上的 ScottGu 视频并阅读了 Scott Hanselman 文章后,我正在研究新的 Web API 结构,这是我对此的最初想法/问题之一。

标签: jquery jsonp asp.net-mvc-4 asp.net-web-api


【解决方案1】:

问了这个问题后,我终于找到了我需要的东西,所以我正在回答它。

我遇到了这个JsonpMediaTypeFormatter。通过执行以下操作将其添加到 global.asax 的 Application_Start 中:

var config = GlobalConfiguration.Configuration;
config.Formatters.Insert(0, new JsonpMediaTypeFormatter());

您可以使用如下所示的 JQuery AJAX 调用:

$.ajax({
    url: 'http://myurl.com',
    type: 'GET',
    dataType: 'jsonp',
    success: function (data) {
        alert(data.MyProperty);
    }
})

似乎效果很好。

【讨论】:

  • 在我的情况下似乎不起作用,我已经为 Json.Net 序列化添加了一个格式化程序。有什么想法吗?
  • 我相信 FormatterContext 在 MVC4 RC 版本forums.asp.net/post/5102318.aspx987654322@中被删除了
  • 该代码现在是 NuGet 中 WebApiContrib 的一部分。无需手动拉入。
  • 是的,现在只是:“Install-Package WebApiContrib.Formatting.Jsonp” Doco 在这里:nuget.org/packages/WebApiContrib.Formatting.Jsonp
  • 这是我必须使用今天的 nuget 下载的内容:GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
【解决方案2】:

这里是用于 WebAPI RC 的 JsonpMediaTypeFormatter 的更新版本:

public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
    private string callbackQueryParameter;

    public JsonpMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(DefaultMediaType);
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));

        MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
    }

    public string CallbackQueryParameter
    {
        get { return callbackQueryParameter ?? "callback"; }
        set { callbackQueryParameter = value; }
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
    {
        string callback;

        if (IsJsonpRequest(out callback))
        {
            return Task.Factory.StartNew(() =>
            {
                var writer = new StreamWriter(stream);
                writer.Write(callback + "(");
                writer.Flush();

                base.WriteToStreamAsync(type, value, stream, content, transportContext).Wait();

                writer.Write(")");
                writer.Flush();
            });
        }
        else
        {
            return base.WriteToStreamAsync(type, value, stream, content, transportContext);
        }
    }


    private bool IsJsonpRequest(out string callback)
    {
        callback = null;

        if (HttpContext.Current.Request.HttpMethod != "GET")
            return false;

        callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

        return !string.IsNullOrEmpty(callback);
    }
}

【讨论】:

  • 太棒了,虽然我相信 WriteToStreamAsync 现在应该在最终版本中采用 HttpContent 而不是 HttpContentHeaders 对象,但这样的改变就像一个魅力
【解决方案3】:

您可以像这样使用 ActionFilterAttribute:

public class JsonCallbackAttribute : ActionFilterAttribute
{
    private const string CallbackQueryParameter = "callback";

    public override void OnActionExecuted(HttpActionExecutedContext context)
    {
        var callback = string.Empty;

        if (IsJsonp(out callback))
        {
            var jsonBuilder = new StringBuilder(callback);

            jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);

            context.Response.Content = new StringContent(jsonBuilder.ToString());
        }

        base.OnActionExecuted(context);
    }

    private bool IsJsonp(out string callback)
    {
        callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

        return !string.IsNullOrEmpty(callback);
    }
}

然后把它放在你的行动上:

[JsonCallback]
public IEnumerable<User> User()
{
    return _user;
}

【讨论】:

  • 与 VS2013 U5、MVC5.2 和 WebApi 2 完美配合
【解决方案4】:

当然,Brian 的答案是正确的,但是如果您已经在使用 Json.Net 格式化程序,它可以为您提供漂亮的 json 日期和更快的序列化,那么您不能只为 jsonp 添加第二个格式化程序,您必须结合他们俩。无论如何使用它是个好主意,正如 Scott Hanselman 所说,ASP.NET Web API 的发布将默认使用 Json.Net 序列化程序。

public class JsonNetFormatter : MediaTypeFormatter
    {
        private JsonSerializerSettings _jsonSerializerSettings;
        private string callbackQueryParameter;

        public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
        {
            _jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();

            // Fill out the mediatype and encoding we support
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            Encoding = new UTF8Encoding(false, true);

            //we also support jsonp.
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
            MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", "application/json"));
        }

        public string CallbackQueryParameter
        {
            get { return callbackQueryParameter ?? "jsoncallback"; }
            set { callbackQueryParameter = value; }
        }

        protected override bool CanReadType(Type type)
        {
            if (type == typeof(IKeyValueModel))
                return false;

            return true;
        }

        protected override bool CanWriteType(Type type)
        {
            return true;
        }

        protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders,
            FormatterContext formatterContext)
        {
            // Create a serializer
            JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

            // Create task reading the content
            return Task.Factory.StartNew(() =>
            {
                using (StreamReader streamReader = new StreamReader(stream, Encoding))
                {
                    using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
                    {
                        return serializer.Deserialize(jsonTextReader, type);
                    }
                }
            });
        }

        protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders,
            FormatterContext formatterContext, TransportContext transportContext)
        {
            string callback;
            var isJsonp = IsJsonpRequest(formatterContext.Response.RequestMessage, out callback);

            // Create a serializer
            JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

            // Create task writing the serialized content
            return Task.Factory.StartNew(() =>
            {
                using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false })
                {
                    if (isJsonp)
                    {
                        jsonTextWriter.WriteRaw(callback + "(");
                        jsonTextWriter.Flush();
                    }

                    serializer.Serialize(jsonTextWriter, value);
                    jsonTextWriter.Flush();

                    if (isJsonp)
                    {
                        jsonTextWriter.WriteRaw(")");
                        jsonTextWriter.Flush();
                    }
                }
            });
        }

        private bool IsJsonpRequest(HttpRequestMessage request, out string callback)
        {
            callback = null;

            if (request.Method != HttpMethod.Get)
                return false;

            var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
            callback = query[CallbackQueryParameter];

            return !string.IsNullOrEmpty(callback);
        }
    }

【讨论】:

  • 我们如何为 ASP .NET Web API RC 做到这一点?
  • 也对 RC 版本感兴趣
【解决方案5】:

Rick Strahl's implementation 最适合我使用 RC。

【讨论】:

    【解决方案6】:

    JSONP 仅适用于 Http GET 请求。 asp.net web api 中有一个 CORS 支持,它适用于所有 http 动词。

    This 文章可能对您有所帮助。

    【讨论】:

    【解决方案7】:

    更新

    public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
        {
            private string callbackQueryParameter;
    
            public JsonpMediaTypeFormatter()
            {
                SupportedMediaTypes.Add(DefaultMediaType);
                SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
    
                MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
            }
    
            public string CallbackQueryParameter
            {
                get { return callbackQueryParameter ?? "callback"; }
                set { callbackQueryParameter = value; }
            }
    
            public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
            {
                string callback;
    
                if (IsJsonpRequest(out callback))
                {
                    return Task.Factory.StartNew(() =>
                    {
                        var writer = new StreamWriter(writeStream);
                        writer.Write(callback + "(");
                        writer.Flush();
    
                        base.WriteToStreamAsync(type, value, writeStream, content, transportContext).Wait();
    
                        writer.Write(")");
                        writer.Flush();
                    });
                }
                else
                {
                    return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
                }
            }
    
            private bool IsJsonpRequest(out string callback)
            {
                callback = null;
    
                if (HttpContext.Current.Request.HttpMethod != "GET")
                    return false;
    
                callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
    
                return !string.IsNullOrEmpty(callback);
            }
        }
    

    【讨论】:

    • 谢谢,其他版本在最新的.net框架下无法使用。
    【解决方案8】:

    这是一个包含多项改进的更新版本,适用于 RTM 版本的 Web API。

    • 根据请求自己的Accept-Encoding 标头选择正确的编码。前面示例中的 new StreamWriter() 将简单地使用 UTF-8。对base.WriteToStreamAsync 的调用可能使用不同的编码,从而导致输出损坏。
    • 将 JSONP 请求映射到 application/javascript Content-Type 标头;前面的示例将输出 JSONP,但带有 application/json 标头。这项工作在嵌套的Mapping 类中完成(参见Best content type to serve JSONP?
    • 放弃StreamWriter 的构造和刷新开销,直接获取字节并将它们写入输出流。
    • 不要等待任务,而是使用任务并行库的ContinueWith 机制将多个任务链接在一起。

    代码:

    public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
    {
      private string _callbackQueryParameter;
    
      public JsonpMediaTypeFormatter()
      {
        SupportedMediaTypes.Add(DefaultMediaType);
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/javascript"));
    
        // need a lambda here so that it'll always get the 'live' value of CallbackQueryParameter.
        MediaTypeMappings.Add(new Mapping(() => CallbackQueryParameter, "application/javascript"));
      }
    
      public string CallbackQueryParameter
      {
        get { return _callbackQueryParameter ?? "callback"; }
        set { _callbackQueryParameter = value; }
      }
    
      public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
                                              TransportContext transportContext)
      {
        var callback = GetCallbackName();
    
        if (!String.IsNullOrEmpty(callback))
        {
          // select the correct encoding to use.
          Encoding encoding = SelectCharacterEncoding(content.Headers);
    
          // write the callback and opening paren.
          return Task.Factory.StartNew(() =>
            {
              var bytes = encoding.GetBytes(callback + "(");
              writeStream.Write(bytes, 0, bytes.Length);
            })
          // then we do the actual JSON serialization...
          .ContinueWith(t => base.WriteToStreamAsync(type, value, writeStream, content, transportContext))
    
          // finally, we close the parens.
          .ContinueWith(t =>
            {
              var bytes = encoding.GetBytes(")");
              writeStream.Write(bytes, 0, bytes.Length);
            });
        }
        return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
      }
    
      private string GetCallbackName()
      {
        if (HttpContext.Current.Request.HttpMethod != "GET")
          return null;
        return HttpContext.Current.Request.QueryString[CallbackQueryParameter];
      }
    
      #region Nested type: Mapping
    
      private class Mapping : MediaTypeMapping
      {
        private readonly Func<string> _param; 
    
        public Mapping(Func<string> discriminator, string mediaType)
          : base(mediaType)
        {
          _param = discriminator;
        }
    
        public override double TryMatchMediaType(HttpRequestMessage request)
        {
          if (request.RequestUri.Query.Contains(_param() + "="))
            return 1.0;
          return 0.0;
        }
      }
    
      #endregion
    }
    

    我知道内部类构造函数中 Func&lt;string&gt; 参数的“hackiness”,但这是解决它所解决问题的最快方法——因为 C# 只有静态内部类,所以它可以看不到 CallbackQueryParameter 属性。传入Func 会绑定lambda 中的属性,因此Mapping 稍后将能够在TryMatchMediaType 中访问它。如果你有更优雅的方式,欢迎评论!

    【讨论】:

      【解决方案9】:

      很遗憾,我没有足够的声誉来发表评论,所以我会发布一个答案。 @Justin 提出了在标准 JsonFormatter 旁边运行 WebApiContrib.Formatting.Jsonp 格式化程序的问题。该问题已在最新版本中得到解决(实际上是前一段时间发布的)。此外,它应该适用于最新的 Web API 版本。

      【讨论】:

        【解决方案10】:

        乔珀尔,托马斯。上面 Peter Moberg 给出的答案对于 RC 版本应该是正确的,因为他继承的 JsonMediaTypeFormatter 已经使用了 NewtonSoft Json 序列化程序,因此他所拥有的应该可以在没有任何更改的情况下工作。

        但是,当您可以执行以下操作时,为什么人们仍在使用 out 参数

        public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
                {
                    var isJsonpRequest = IsJsonpRequest();
        
                    if(isJsonpRequest.Item1)
                    {
                        return Task.Factory.StartNew(() =>
                        {
                            var writer = new StreamWriter(stream);
                            writer.Write(isJsonpRequest.Item2 + "(");
                            writer.Flush();
                            base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext).Wait();
                            writer.Write(")");
                            writer.Flush();
                        });
                    }
        
                    return base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext);
                }
        
                private Tuple<bool, string> IsJsonpRequest()
                {
                    if(HttpContext.Current.Request.HttpMethod != "GET")
                        return new Tuple<bool, string>(false, null);
        
                    var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
        
                    return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback);
                }
        

        【讨论】:

          【解决方案11】:

          您可以安装 WebApiContrib.Formatting.Jsonp NuGet 包,而不是托管您自己的 JSONP 格式化程序版本(选择适用于您的 .NET Framework 的版本)。

          将此格式化程序添加到Application_Start

          GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));
          

          【讨论】:

            【解决方案12】:

            对于那些使用 HttpSelfHostServer 的人来说,这部分代码将在 HttpContext.Current 上失败,因为它在自托管服务器上不存在。

            private Tuple<bool, string> IsJsonpRequest()
            {
            if(HttpContext.Current.Request.HttpMethod != "GET")
             return new Tuple<bool, string>(false, null);
             var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
             return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback);
             }
            

            但是,您可以通过此覆盖拦截自身主机“上下文”。

            public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
                    {
                        _method = request.Method;
                        _callbackMethodName =
                            request.GetQueryNameValuePairs()
                                   .Where(x => x.Key == CallbackQueryParameter)
                                   .Select(x => x.Value)
                                   .FirstOrDefault();
            
                        return base.GetPerRequestFormatterInstance(type, request, mediaType);
                    }
            

            request.Method 将为您提供“GET”、“POST”等,并且 GetQueryNameValuePairs 可以检索 ?callback 参数。因此我修改后的代码如下:

            private Tuple<bool, string> IsJsonpRequest()
             {
                 if (_method.Method != "GET")
                 return new Tuple<bool, string>(false, null);
            
                 return new Tuple<bool, string>(!string.IsNullOrEmpty(_callbackMethodName), _callbackMethodName);
            }
            

            希望这对你们中的一些人有所帮助。这样您就不一定需要 HttpContext shim。

            C.

            【讨论】:

              【解决方案13】:

              看看这个。看看有没有帮助。

              JSONP with Web API

              【讨论】:

                【解决方案14】:

                如果上下文是Web Api,感谢并参考010227leo 的回答,您必须考虑WebContext.Current 的值,这将是null

                所以我将他的代码更新为:

                public class JsonCallbackAttribute
                    : ActionFilterAttribute
                {
                    private const string CallbackQueryParameter = "callback";
                
                    public override void OnActionExecuted(HttpActionExecutedContext context)
                    {
                        var callback = context.Request.GetQueryNameValuePairs().Where(item => item.Key == CallbackQueryParameter).Select(item => item.Value).SingleOrDefault();
                
                        if (!string.IsNullOrEmpty(callback))
                        {
                            var jsonBuilder = new StringBuilder(callback);
                
                            jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);
                
                            context.Response.Content = new StringContent(jsonBuilder.ToString());
                        }
                
                        base.OnActionExecuted(context);
                    }
                }
                

                【讨论】:

                  【解决方案15】:

                  我们可以通过两种方式解决 CORS(跨域资源共享)问题,

                  1)使用 Jsonp 2) 启用 Cors

                  1)使用Jsonp- 要使用 Jsonp,我们需要安装 WebApiContrib.Formatting.Jsonp nuget 包 需要在WebApiConfig.cs中添加JsonpFormmater参考截图,

                  jQuery 代码

                  2) 启用 Cors -

                  要启用 cors,我们需要添加 Microsoft.AspNet.WebApi.Cors nuget 包并需要在 WebApiConfig.cs 中启用 cors 参考截图

                  如需更多参考,您可以使用以下链接参考我在 GitHub 上的示例 repo。 https://github.com/mahesh353/Ninject.WebAPi/tree/develop

                  【讨论】:

                    猜你喜欢
                    • 2013-06-24
                    • 2013-11-12
                    • 2011-10-11
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-09-02
                    • 2015-02-21
                    • 2013-10-26
                    • 1970-01-01
                    相关资源
                    最近更新 更多