[jquery] 带有ASP.NET Web API的JSONP



Answers

以下是适用于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);
    }
}
Question

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

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

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




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



您可以安装WebApiContrib.Formatting.Jsonp NuGet包,而不是使用已经实现的JSONP格式化版本(选择适用于您的.NET Framework的版本)。

将此格式化Application_Start添加到Application_Start

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






这里有一个更新后的版本,其中有几项改进,可以与RTM版本的Web API一起使用。

  • 根据请求自己的Accept-Encoding标头选择正确的编码。 前面例子中的new StreamWriter()将简单地使用UTF-8。 对base.WriteToStreamAsync的调用可能使用不同的编码,导致输出损坏。
  • 将JSONP请求映射到application/javascript Content-Type标头; 前面的例子会输出JSONP,但是使用application/json头。 这项工作是在嵌套的Mapping类中完成的(比较最佳内容类型为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访问它。 如果你有更优雅的方式,请评论!




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

1)使用Jsonp 2)启用Cors

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

jquery代码

2)启用Cors -

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

有关更多参考资料,您可以使用以下链接在GitHub上引用我的示例回购。 https://github.com/mahesh353/Ninject.WebAPi/tree/develop




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

本文可能对您有所帮助。




Related