JSONP với API Web ASP.NET


136

Tôi đang làm việc để tạo ra một bộ dịch vụ mới trong ASP.MVC MVC 4 bằng API Web. Cho đến nay, nó thật tuyệt. Tôi đã tạo ra dịch vụ và làm cho nó hoạt động và bây giờ tôi đang cố gắng sử dụng nó bằng JQuery. Tôi có thể lấy lại chuỗi JSON bằng Fiddler và có vẻ ổn, nhưng vì dịch vụ tồn tại trên một trang web riêng biệt, cố gắng gọi nó bằng các lỗi JQuery bằng "Không được phép". Vì vậy, đây rõ ràng là một trường hợp tôi cần sử dụng JSONP.

Tôi biết rằng API Web là mới, nhưng tôi hy vọng ai đó ngoài kia có thể giúp tôi.

Làm cách nào để thực hiện cuộc gọi đến phương thức API Web bằng JSONP?


1
Chỉ nhìn vào cấu trúc API Web mới sau khi xem video ScottGu trên Channel9 và đọc bài viết của Scott Hanselman, và đây là một trong những suy nghĩ / câu hỏi đầu tiên của tôi về điều này.
Tracker1

Câu trả lời:


132

Sau khi hỏi câu hỏi này, cuối cùng tôi đã tìm thấy những gì tôi cần, vì vậy tôi đang trả lời nó.

Tôi đã chạy qua JsonpMediaTypeFormatter này . Thêm nó vào Application_Startglobal.asax của bạn bằng cách làm điều này:

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

và bạn thật tuyệt khi thực hiện cuộc gọi AJAX JQuery giống như thế này:

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

Nó dường như làm việc rất tốt.


Có vẻ như không hoạt động trong trường hợp của tôi, nơi tôi đã có một trình định dạng được thêm vào cho tuần tự hóa Json.Net. Có ý kiến ​​gì không?
Justin

4
Tôi tin rằng FormatterContext đã bị xóa trong MVC4 phiên bản RC forum.asp.net/post/5102318.aspx
Diganta Kumar

13
Mã này hiện là một phần của WebApiContrib trong NuGet. Không cần phải kéo nó bằng tay.
Jon Onstott

7
Vâng, bây giờ chỉ cần: "Install-Package WebApiContrib.Formatting.Jsonp" DOCO là ở đây: nuget.org/packages/WebApiContrib.Formatting.Jsonp
nootn

4
Đây là những gì tôi đã phải sử dụng tải xuống GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
nuget

52

Đây là phiên bản cập nhật của JsonpMediaTypeFormatter để sử dụng với WebAPI RC:

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
Cảm ơn tuyệt vời, mặc dù tôi tin rằng WriteToStreamAsync nên lấy một HTTPContent chứ không phải là một đối tượng HttpContentHeaders trong bản phát hành cuối cùng, nhưng với một thay đổi đó hoạt động như một cơ duyên
Ben

21

Bạn có thể sử dụng ActionFilterAttribution như thế này:

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

Sau đó đưa nó vào hành động của bạn:

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

Hoạt động hoàn hảo với VS2013 U5, MVC5.2 & WebApi 2
Tham khảo ý kiến ​​của Yarla

11

Chắc chắn câu trả lời của Brian là câu trả lời đúng, tuy nhiên nếu bạn đã sử dụng công cụ định dạng Json.Net, cung cấp cho bạn ngày tháng đẹp và nhanh hơn, thì bạn không thể thêm một công cụ định dạng thứ hai cho jsonp, bạn phải kết hợp cả hai. Dù sao thì cũng nên sử dụng nó, vì Scott Hanselman đã nói rằng việc phát hành ASP.NET Web API sẽ sử dụng serializer Json.Net theo mặc định.

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

Làm thế nào chúng ta có thể làm điều này cho API API Web .NET .NET?
jonperl

cũng quan tâm đến phiên bản RC
Thomas Stock



5

Đã cập nhật

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

Cảm ơn bạn, phiên bản khác không hoạt động trong khung .net mới nhất.
djbielejeski

2

Đây là phiên bản cập nhật với một số cải tiến, hoạt động với phiên bản RTM của API Web.

  • Chọn mã hóa chính xác, dựa trên các Accept-Encodingtiêu đề của chính yêu cầu . Các new StreamWriter()ví dụ trước chỉ đơn giản là sử dụng UTF-8. Cuộc gọi đến base.WriteToStreamAsynccó thể sử dụng một mã hóa khác nhau, dẫn đến đầu ra bị hỏng.
  • Bản đồ yêu cầu JSONP đến application/javascript Content-Typetiêu đề; ví dụ trước sẽ xuất JSONP, nhưng với application/jsontiêu đề. Công việc này được thực hiện trong Mappinglớp lồng nhau (xem Loại nội dung tốt nhất để phục vụ JSONP? )
  • Mở đầu quá trình xây dựng và tuôn ra của a StreamWritervà trực tiếp lấy các byte và ghi chúng vào luồng đầu ra.
  • Thay vì chờ đợi một nhiệm vụ, hãy sử dụng ContinueWithcơ chế của Thư viện song song để xâu chuỗi một số nhiệm vụ lại với nhau.

Mã số:

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
}

Tôi nhận thức được "tính hack" của Func<string>tham số trong hàm tạo của lớp bên trong, nhưng đó là cách nhanh nhất để khắc phục vấn đề mà nó giải quyết - vì C # chỉ có các lớp bên trong tĩnh, nên nó không thể nhìn thấy thuộc CallbackQueryParametertính. Truyền vào Functrong liên kết tài sản trong lambda, vì vậy Mappingsẽ có thể truy cập vào sau này trong TryMatchMediaType. Nếu bạn có một cách thanh lịch hơn, xin vui lòng bình luận!


2

Thật không may, tôi không có đủ danh tiếng để bình luận, vì vậy tôi sẽ đăng câu trả lời. @Justin nêu vấn đề về việc chạy trình định dạng WebApiContrib.Formatted.Jsonp cùng với JsonFormatter tiêu chuẩn. Vấn đề đó được giải quyết trong bản phát hành mới nhất (thực sự được phát hành cách đây một thời gian). Ngoài ra, nó sẽ hoạt động với bản phát hành API Web mới nhất.


1

johperl, Thomas. Câu trả lời được đưa ra bởi Peter Moberg ở trên phải chính xác cho phiên bản RC là JsonMediaTypeFormatter mà anh ta thừa hưởng từ việc sử dụng bộ nối tiếp NewtonSoft Json, và vì vậy anh ta nên làm gì với bất kỳ thay đổi nào.

Tuy nhiên, tại sao mọi người vẫn sử dụng các tham số, khi bạn chỉ có thể làm như sau

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

Thay vì lưu trữ JSONP của riêng bạn định dạng phiên bản bạn có thể cài đặt WebApiContrib.Formatting.Jsonp NuGet gói với một đã được thực hiện (chọn phiên bản mà tác phẩm cho .NET của bạn Framework).

Thêm định dạng này vào Application_Start:

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

0

Đối với những người bạn đang sử dụng HTTPSelfhostServer, phần mã này sẽ thất bại trên HttpContext.C hiện tại, vì nó không tồn tại trên máy chủ tự lưu trữ.

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

Tuy nhiên, bạn có thể chặn "bối cảnh" tự lưu trữ thông qua ghi đè này.

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 sẽ cung cấp cho bạn "GET", "POST", v.v. và GetQueryNameValuePairs có thể truy xuất tham số? Callback. Do đó, mã sửa đổi của tôi trông giống như:

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

Hi vọng điều này giúp được cho một vài bạn. Bằng cách này, bạn không nhất thiết cần một shim httpContext.

C.



0

Nếu bối cảnh là Web Api, cảm ơn và đề cập đến 010227leocâu trả lời, bạn phải xem xét WebContext.Currentgiá trị sẽ có null.

Vì vậy, tôi đã cập nhật mã của mình vào đây:

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

Chúng tôi có thể giải quyết vấn đề CORS (chia sẻ tài nguyên nguồn gốc) bằng hai cách,

1) Sử dụng Jsonp 2) Kích hoạt Cors

1) Sử dụng Jsonp- để sử dụng Jsonp, chúng ta cần cài đặt gói nuget WebApiContrib.Formatted.Jsonp và cần thêm JsonpFormmater trong ảnh chụp màn hình tham chiếu WebApiConfig.cs,nhập mô tả hình ảnh ở đây

Mã Jquery nhập mô tả hình ảnh ở đây

2) Kích hoạt Cors -

để kích hoạt các cors, chúng ta cần thêm gói nuget Microsoft.AspNet.WebApi.Cors và cần kích hoạt các cors trong WebApiConfig.cs

nhập mô tả hình ảnh ở đây

Để tham khảo thêm, bạn có thể giới thiệu repo mẫu của tôi trên GitHub bằng liên kết sau. https://github.com/mahesh353/Ninject.WebAPi/tree/develop

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.