ASP.NET Web API ile JSONP


136

Web API kullanarak ASP.MVC MVC 4 yeni bir hizmet kümesi oluşturma üzerinde çalışıyorum. Şimdiye kadar, harika. Hizmeti yarattım ve işe aldım ve şimdi JQuery kullanarak tüketmeye çalışıyorum. Fiddler kullanarak JSON dizesini geri alabilirsiniz ve Tamam gibi görünüyor, ancak hizmet ayrı bir sitede var olduğundan, "İzin verilmiyor" ile JQuery hataları ile çağırmaya çalışıyor. Yani, bu açıkça JSONP kullanmam gereken bir durum.

Web API'sinin yeni olduğunu biliyorum, ancak orada birinin bana yardımcı olabileceğini umuyorum.

JSONP kullanarak bir Web API yöntemine nasıl çağrı yapabilirim?


1
Channel9'daki ScottGu videosunu izledikten ve Scott Hanselman makalesini okuduktan sonra yeni Web API yapısına bakıyordum ve bu benim bu konudaki ilk düşüncelerim / sorularımdan biriydi.
Tracker1

Yanıtlar:


132

Bu soruyu sorduktan sonra neye ihtiyacım olduğunu buldum, bu yüzden cevaplıyorum.

Bu JsonpMediaTypeFormatter ile karşılaştım . İçine ekleyin Application_StartBu işlemleri gerçekleştirerek global.asax ait:

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

ve aşağıdaki gibi görünen bir JQuery AJAX çağrısı ile gitmekte fayda var:

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

Çok iyi çalışıyor gibi görünüyor.


Zaten Json.Net serileştirme için bir biçimlendirici eklenmiş durumda benim durumumda çalışmıyor gibi görünüyor. Herhangi bir fikir?
Justin

4
Ben FormatterContext MVC4 RC Sürüm forums.asp.net/post/5102318.aspx
Diganta Kumar

13
Kod artık NuGet'teki WebApiContrib'in bir parçası. Elle çekmeye gerek yok.
Jon Onstott

7
Evet, şimdi sadece: "Install-Package WebApiContrib.Formatting.Jsonp" Doco burada: nuget.org/packages/WebApiContrib.Formatting.Jsonp
nootn

4
Bu bugünün nuget indir kullanarak koymak zorunda olduğunu:GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
joym8

52

WebAPI RC ile kullanım için JsonpMediaTypeFormatter'ın güncellenmiş bir sürümü:

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
Harika teşekkürler, her ne kadar WriteToStreamAsync'in son sürümde bir HttpContentHeaders nesnesi değil bir HttpContent alması gerektiğine inanıyorum, ancak bu değişiklikle bir cazibe gibi çalıştı
Ben

21

Bunun gibi bir ActionFilterAttribute kullanabilirsiniz:

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

Sonra eyleminize koyun:

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

VS2013 U5, MVC5.2 ve WebApi 2 ile mükemmel bir şekilde çalıştı
12'de Yarla'ya danışın

11

Kesinlikle Brian'ın cevabı doğrudur, ancak size oldukça json tarihleri ​​ve daha hızlı serileştirme sağlayan Json.Net formatlayıcı kullanıyorsanız, jsonp için ikinci bir formatlayıcı ekleyemezsiniz, ikisini birleştirmeniz gerekir. Scott Hanselman'ın ASP.NET Web API sürümünün varsayılan olarak Json.Net serileştiricisini kullanacağını söylediği için bunu kullanmak iyi bir fikirdir.

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 için bunu nasıl yapabiliriz?
jonperl

Ayrıca RC versiyonu ile ilgileniyor
Thomas Stock



5

Güncellenmiş

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

Teşekkür ederim, diğer sürüm en son .net çerçevesinde çalışmaz.
djbielejeski

2

Aşağıda, Web API'lerinin RTM sürümüyle çalışan çeşitli iyileştirmeler içeren güncellenmiş bir sürüm bulunmaktadır.

  • İsteğin kendi Accept-Encodingbaşlıklarına göre doğru kodlamayı seçer . new StreamWriter()Önceki örneklerde sadece UTF-8 kullanmak. To çağrısı base.WriteToStreamAsyncfarklı bir kodlama kullanarak sonuçta bozuk çıktı oluşmasına neden olabilir.
  • JSONP isteklerini application/javascript Content-Typebaşlığa eşler ; önceki örnek JSONP çıktısı verir, ancak application/jsonüstbilgiyle birlikte. Bu çalışma iç içe Mappingsınıfta yapılır (bkz. JSONP'ye hizmet verecek en iyi içerik türü? )
  • A'nın yapım ve yıkama yükünü bırakır StreamWriterve doğrudan baytları alır ve bunları çıkış akışına yazar.
  • Bir görevi beklemek yerine, ContinueWithbirkaç görevi birlikte zincirlemek için Görev Paralel Kitaplığı'nın mekanizmasını kullanın.

Kod:

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>İç sınıf yapıcısında parametrenin "hackiness" inin farkındayım , ancak çözdüğü sorunun üstesinden gelmenin en hızlı yoluydu - C # sadece statik iç sınıflara sahip olduğundan, CallbackQueryParameterözelliği göremiyor . İçinden geçerek lambda'daki Funcmülk bağlanır, böylece Mappingdaha sonra içeri girebilirsiniz TryMatchMediaType. Daha zarif bir yolunuz varsa, lütfen yorum yapın!


2

Ne yazık ki, yorum yapmak için yeterli itibarım yok, bu yüzden bir cevap göndereceğim. @ Justin, WebApiContrib.Formatting.Jsonp formatlayıcıyı standart JsonFormatter ile birlikte çalıştırma sorununu gündeme getirdi. Bu sorun en son sürümde çözüldü (aslında bir süre önce yayınlandı). Ayrıca, en son Web API sürümü ile çalışmalıdır.


1

johperl, Thomas. Yukarıda Peter Moberg tarafından verilen cevap, JsonMediaTypeFormatter olarak RC sürümü için doğru olmalı ve daha önce NewtonSoft Json serileştiricisini kullanıyor ve bu yüzden sahip olduğu her türlü değişiklikle çalışması gerekiyor.

Ancak, neden aşağıdakileri yapabildiğinizde, neden insanlar hala parametreleri kullanıyor?

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

Kendi JSONP biçimlendirici sürümünüzü barındırmak yerine, WebApiContrib.Formatting.Jsonp NuGet paketini zaten uygulanmış olan biriyle yükleyebilirsiniz (.NET Framework'ünüz için çalışan sürümü seçin).

Bu biçimlendiriciyi şuraya ekle Application_Start:

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

0

HttpSelfHostServer kullananlarınız için bu kod bölümü HttpContext.Current üzerinde başarısız olacaktır, çünkü kendi kendine sunucuda bulunmaz.

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

Ancak bu geçersiz kılma yoluyla kendi ana bilgisayar "bağlamını" kesebilirsiniz.

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 size "GET", "POST" vb. Bilgileri verir ve GetQueryNameValuePairs? Callback parametresini alabilir. Böylece gözden geçirilmiş kodum şöyle görünür:

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

Umarım bu bazılarınıza yardımcı olur. Bu şekilde mutlaka bir HttpContext şimine ihtiyacınız yoktur.

C.



0

Bağlam ise Web Api, teşekkür ve 010227leocevabına atıfta bulunuluyorsa WebContext.Current, olacak değeri göz önünde bulundurmalısınız null.

Bu yüzden kodunu şu şekilde güncelledim:

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 (Kaynaklar arası kaynak paylaşımı) sorununu iki yolla çözebiliriz,

1) Jsonp Kullanımı 2) Korseleri Etkinleştirme

1) Jsonp'i kullanarak Jsonp'i kullanmak için WebApiContrib.Formatting.Jsonp nuget paketini kurmamız ve WebApiConfig.cs başvuru ekran görüntülerine JsonpFormmater eklememiz gerekir,resim açıklamasını buraya girin

Jquery kodu resim açıklamasını buraya girin

2) Korunların Etkinleştirilmesi -

Korseleri etkinleştirmek için Microsoft.AspNet.WebApi.Cors nuget paketini eklememiz ve WebApiConfig.cs içindeki korseleri etkinleştirmemiz gerekiyor ekran görüntüsüne bakın

resim açıklamasını buraya girin

Daha fazla referans için, aşağıdaki bağlantıyı kullanarak GitHub'daki örnek repoma başvurabilirsiniz. https://github.com/mahesh353/Ninject.WebAPi/tree/develop

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.