首页 文章

使用ASP.NET Web API的JSONP

提问于
浏览
132

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

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

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

15 回答

  • 6

    您可以像这样使用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;
    }
    
  • 2

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

    我碰到了这个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);
        }
    })
    

    它似乎工作得很好 .

  • 5

    以下是与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);
        }
    }
    
  • 51

    当然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);
            }
        }
    
  • 1

    Rick Strahl's implementation对我来说最适合RC .

  • 20

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

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

  • 9

    Updated

    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);
            }
        }
    
  • 0

    这是一个带有若干改进的更新版本,它与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<string> 参数的"hackiness",但它是解决它所解决问题的最快方法 - 因为C#只有静态内部类,所以它看不到 CallbackQueryParameter 属性 . 传递 Func 将绑定lambda中的属性,因此 Mapping 稍后将能够在 TryMatchMediaType 中访问它 . 如果你有更优雅的方式,请评论!

  • 128

    johperl,托马斯 . 上面的Peter Moberg给出的答案对于RC版本来说应该是正确的,因为他继承的JsonMediaTypeFormatter已经使用了NewtonSoft Json序列化器,所以他应该使用任何更改 .

    然而,为什么人们仍然在使用参数,当你可以只做以下

    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);
            }
    
  • 0

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

    将此格式化程序添加到 Application_Start

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

    不幸的是,我不会发布一个答案 . @Justin提出了在标准JsonFormatter旁边运行WebApiContrib.Formatting.Jsonp格式化程序的问题 . 该问题已在最新版本中解决(实际上已在前一段时间发布) . 此外,它应该与最新的Web API版本一起使用 .

  • 0

    对于那些使用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垫片 .

    C .

  • 1

    检查一下 . 看看它是否有帮助 .

    JSONP with Web API

  • 0

    如果上下文是 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);
        }
    }
    
  • 11

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

    1)使用Jsonp 2)启用Cors

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

    Jquery代码
    enter image description here

    2)启用Cors -

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

    enter image description here

    有关更多参考,您可以使用以下链接在GitHub上引用我的样本仓库 . https://github.com/mahesh353/Ninject.WebAPi/tree/develop

相关问题