带有ASP.NET Web API的JSONP


136

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

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

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


1
在观看Channel9上的ScottGu视频并阅读Scott Hanselman的文章之后,我只是在研究新的Web API结构,这是我对此的第一个想法/问题。
Tracker1 2012年

Answers:


132

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

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

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序列化添加了格式化程序。有任何想法吗?
贾斯汀

4
我相信FormatterContext在MVC4 RC版本forums.asp.net/post/5102318.aspx
Diganta Kumar

13
该代码现在是NuGet中WebApiContrib的一部分。无需手动将其拉入。
乔恩·昂斯托特

7
是的,现在才是:“ Install-Package WebApiContrib.Formatting.Jsonp” Doco在这里:nuget.org/packages/WebApiContrib.Formatting.Jsonp
13年

4
这就是我今天使用的nuget下载所要执行的操作:GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
joym8

52

这是用于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);
    }
}

8
太棒了,尽管我相信WriteToStreamAsync现在应该在最终版本中采用HttpContent而不是HttpContentHeaders对象,但是这种更改像个魅力一样工作
2012年

21

您可以像这样使用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完美配合
请在2015年

11

当然,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做到这一点?
jonperl 2012年

也对RC版本感兴趣
Thomas Stock



5

更新

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框架中不起作用。
djbielejeski 2013年

2

这是具有一些改进的更新版本,可与Web API的RTM版本一起使用。

  • 根据请求自己的Accept-Encoding标头选择正确的编码。在new StreamWriter()前面的例子只会使用UTF-8。致电base.WriteToStreamAsync可能使用不同的编码,从而导致输出损坏。
  • 将JSONP请求映射到application/javascript Content-Type标头;前面的示例将输出JSONP,但带有application/json标头。这项工作是在嵌套Mapping类中完成的(参见服务JSONP的最佳内容类型?
  • 放弃a的构造和刷新开销,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属性。传递Funcin Mapping会将属性绑定到lambda中,因此以后可以在中访问它TryMatchMediaType。如果您有更优雅的方式,请发表评论!


2

不幸的是,我没有足够的声誉来发表评论,因此我将发布答案。@Justin提出了与标准JsonFormatter一起运行WebApiContrib.Formatting.Jsonp格式化程序的问题。该问题已在最新版本(实际上是在一段时间之前发布)中得到解决。此外,它应与最新的Web API版本一起使用。


1

约翰·托马斯。上面的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);
        }

1

可以使用已实现的WebApiContrib.Formatting.Jsonp NuGet软件包来代替托管自己的JSONP格式化程序版本(选择适用于.NET Framework的版本)。

将此格式化程序添加到Application_Start

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

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。



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

0

我们可以使用两种方法解决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上引用我的示例存储库。 https://github.com/mahesh353/Ninject.WebAPi/tree/develop

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.