jQuery 带有 ASP.NET Web API 的 JSONP

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9421312/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 10:47:16  来源:igfitidea点击:

JSONP with ASP.NET Web API

jqueryjsonpasp.net-mvc-4asp.net-web-api

提问by Brian McCord

I am working on creating a new set of services in ASP.MVC MVC 4 using the Web API. So far, it's great. I have created the service and gotten it to work, and now I am trying to consume it using JQuery. I can get back the JSON string using Fiddler, and it seems to be ok, but because the service exists on a separate site, trying to call it with JQuery errors with the "Not Allowed". So, this is clearly a case where I need to use JSONP.

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

I know that the Web API is new, but I'm hoping someone out there can help me.

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

How do I make a call to a Web API method using JSONP?

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

采纳答案by Brian McCord

After asking this question, I finally found what I needed, so I am answering it.

问完这个问题,我终于找到了我需要的东西,所以我来回答一下。

I ran across this JsonpMediaTypeFormatter. Add it into the Application_Startof your global.asax by doing this:

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

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

and you are good to go with an JQuery AJAX call that looks like this:

您很高兴使用如下所示的 JQuery AJAX 调用:

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

It seems to work very well.

它似乎工作得很好。

回答by Peter Moberg

Here is an updated version of the JsonpMediaTypeFormatter for use with WebAPI RC:

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

回答by 010227leo

You can use an ActionFilterAttribute like this:

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

Then put it on your action:

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

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

回答by Justin

Certainly Brian's answer is the correct one, however if you already are using the Json.Net formatter, which gives you pretty json dates and faster serialization, then you can't just add a second formatter for jsonp, you have to combine the two. It is a good idea to use it anyway, as Scott Hanselman has said that the release of ASP.NET Web API is going to use the Json.Net serializer by default.

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

回答by Paul G

Rick Strahl's implementationworked best for me with RC.

Rick Strahl 的RC实现对我来说效果最好。

回答by user1186065

JSONP only works with Http GET request. There is a CORS support in asp.net web api which works well with all http verbs.

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

Thisarticle may be helpful to you.

这篇文章可能对你有帮助。

回答by ITXGEN

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

回答by atanamir

Here's an updated version with several improvements, which works with the RTM version of Web APIs.

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

  • Selects the correct encoding, based on the request's own Accept-Encodingheaders. The new StreamWriter()in the previous examples would simply use UTF-8. The call to base.WriteToStreamAsyncmay use a different encoding, resulting in corrupted output.
  • Maps JSONP requests to the application/javascriptContent-Typeheader; the previous example would output JSONP, but with the application/jsonheader. This work is done in the nested Mappingclass (cf. Best content type to serve JSONP?)
  • Foregoes the construction and flushing overhead of a StreamWriterand directly gets the bytes and writes them to the output stream.
  • Instead of waiting on a task, use the Task Parallel Library's ContinueWithmechanism to chain several tasks together.
  • 根据请求自己的Accept-Encoding标头选择正确的编码。在new StreamWriter()前面的例子只会使用UTF-8。调用base.WriteToStreamAsync可能会使用不同的编码,从而导致输出损坏。
  • 将 JSONP 请求映射到application/javascriptContent-Type标头;前面的示例将输出 JSONP,但带有application/json标头。这项工作是在嵌套Mapping类中完成的(参见。服务 JSONP 的最佳内容类型?
  • 放弃 a 的构造和刷新开销,StreamWriter直接获取字节并将它们写入输出流。
  • 不是等待任务,而是使用任务并行库的ContinueWith机制将多个任务链接在一起。

Code:

代码:

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
}

I'm aware of the "hackiness" of the Func<string>parameter in the inner class constructor, but it was the fastest way to get around the problem it solves -- since C# only has static inner classes, it can't see the CallbackQueryParameterproperty. Passing the Funcin binds the property in the lambda, so Mappingwill be able to access it later on in TryMatchMediaType. If you have a more elegant way, please comment!

我知道Func<string>内部类构造函数中参数的“hackiness” ,但这是解决它解决的问题的最快方法——因为 C# 只有静态内部类,它看不到CallbackQueryParameter属性。传递Funcin 绑定了 lambda 中的属性,因此Mapping稍后可以在 in 中访问它TryMatchMediaType。如果你有更优雅的方式,请评论!

回答by panesofglass

Unfortunately, I don't have enough reputation to comment, so I'll post an answer. @Justin raised the issue of running the WebApiContrib.Formatting.Jsonpformatter alongside the standard JsonFormatter. That issue is resolved in the latest release (actually released some time ago). Also, it should work with the latest Web API release.

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

回答by Mr. Pumpkin

Instead of hosting your own JSONP formatter version you can install WebApiContrib.Formatting.JsonpNuGet package with already implemented one (choose the version that works for your .NET Framework).

您可以安装WebApiContrib.Formatting.JsonpNuGet 包,而不是托管您自己的 JSONP 格式化程序版本,其中已经实现了一个(选择适用于您的 .NET Framework 的版本)。

Add this formatter into Application_Start:

将此格式化程序添加到Application_Start

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