JSONP dengan ASP.NET Web API

136

Saya sedang mengerjakan membuat satu set layanan baru di ASP.MVC MVC 4 menggunakan API Web. Sejauh ini, itu bagus. Saya telah membuat layanan dan membuatnya berfungsi, dan sekarang saya mencoba mengonsumsinya menggunakan JQuery. Saya bisa mendapatkan kembali string JSON menggunakan Fiddler, dan tampaknya baik-baik saja, tetapi karena layanan tersebut ada di situs terpisah, mencoba memanggilnya dengan kesalahan JQuery dengan "Tidak Diizinkan". Jadi, ini jelas kasus di mana saya perlu menggunakan JSONP.

Saya tahu bahwa API Web masih baru, tetapi saya berharap seseorang di luar sana dapat membantu saya.

Bagaimana cara melakukan panggilan ke metode API Web menggunakan JSONP?

Brian McCord
sumber
1
Baru saja melihat ke dalam struktur API Web baru setelah menonton video ScottGu di Channel9, dan membaca artikel Scott Hanselman, dan ini adalah salah satu pemikiran / pertanyaan pertama saya tentang ini.
Pelacak1

Jawaban:

132

Setelah menanyakan pertanyaan ini, saya akhirnya menemukan apa yang saya butuhkan, jadi saya menjawabnya.

Saya menemukan JsonpMediaTypeFormatter ini . Tambahkan ke dalam Application_Startglobal.asax Anda dengan melakukan ini:

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

dan Anda siap menggunakan panggilan JQuery AJAX yang terlihat seperti ini:

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

Tampaknya bekerja dengan sangat baik.

Brian McCord
sumber
Sepertinya tidak berfungsi dalam kasus saya, di mana saya sudah memiliki pemformat yang ditambahkan untuk serialisasi Json.Net. Ada ide?
Justin
4
Saya percaya FormatterContext dihapus di forum MVC4 RC Versions.asp.net/post/5102318.aspx
Diganta Kumar
13
Kode tersebut sekarang menjadi bagian dari WebApiContrib di NuGet. Tidak perlu menariknya secara manual.
Jon Onstott
7
Ya, sekarang cukup: "Instal-Paket WebApiContrib.Formatting.Jsonp" Doco ada di sini: nuget.org/packages/WebApiContrib.Formatting.Jsonp
nootn
4
Inilah yang harus saya gunakan untuk mengunduh nuget hari ini:GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
joym8
52

Berikut adalah versi terbaru dari JsonpMediaTypeFormatter untuk digunakan dengan 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);
    }
}
Peter Moberg
sumber
8
Terima kasih yang luar biasa, meskipun saya yakin WriteToStreamAsync harus mengambil HttpContent bukan objek HttpContentHeaders sekarang dalam rilis final, tetapi dengan satu perubahan itu bekerja seperti pesona
Ben
21

Anda dapat menggunakan ActionFilterAttribute seperti ini:

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

Kemudian taruh di tindakan Anda:

[JsonCallback]
public IEnumerable<User> User()
{
    return _user;
}
010227leo
sumber
Bekerja sempurna dengan VS2013 U5, MVC5.2 & WebApi 2
Konsultasikan Yarla
11

Tentu jawaban Brian benar, namun jika Anda sudah menggunakan formatter Json.Net, yang memberi Anda tanggal json cantik dan serialisasi lebih cepat, maka Anda tidak bisa menambahkan formatter kedua untuk jsonp, Anda harus menggabungkan keduanya. Itu adalah ide yang baik untuk tetap menggunakannya, seperti yang dikatakan Scott Hanselman bahwa rilis dari ASP.NET Web API akan menggunakan serializer Json.Net secara default.

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);
        }
    }
Justin
sumber
Bagaimana kita bisa melakukan ini untuk ASP .NET Web API RC?
jonperl
juga tertarik dengan versi RC
Thomas Stock
6

JSONP hanya bekerja dengan permintaan Http GET. Ada dukungan CORS di api web asp.net yang bekerja dengan baik dengan semua verba http.

Artikel ini mungkin berguna untuk Anda.

pengguna1186065
sumber
1
Sekarang ada dukungan CORS di Web API. Artikel ini sangat membantu - asp.net/web-api/overview/security/…
Ilia Barahovski
5

Diperbarui

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);
        }
    }
ITXGEN
sumber
Terima kasih, versi lain tidak berfungsi di kerangka kerja .net terbaru.
djbielejeski
2

Berikut adalah versi yang diperbarui dengan beberapa peningkatan, yang berfungsi dengan versi RTM Web API.

  • Memilih encoding yang benar, berdasarkan Accept-Encodingheader permintaan itu sendiri . Pada new StreamWriter()contoh sebelumnya hanya akan menggunakan UTF-8. Panggilan ke base.WriteToStreamAsyncmungkin menggunakan pengkodean yang berbeda, yang mengakibatkan keluaran rusak.
  • Memetakan permintaan JSONP ke application/javascript Content-Typeheader; contoh sebelumnya akan mengeluarkan JSONP, tetapi dengan application/jsonheader. Pekerjaan ini dilakukan di Mappingkelas bersarang (cf. Tipe konten terbaik untuk melayani JSONP? )
  • Mengabaikan konstruksi dan overhead pembilasan dari a StreamWriterdan secara langsung mendapatkan byte dan menulisnya ke aliran keluaran.
  • Alih-alih menunggu tugas, gunakan ContinueWithmekanisme Task Parallel Library untuk merangkai beberapa tugas bersama.

Kode:

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
}

Saya menyadari Func<string>parameter "hackiness" dalam konstruktor kelas dalam, tetapi itu adalah cara tercepat untuk mengatasi masalah yang dipecahkannya - karena C # hanya memiliki kelas dalam statis, ia tidak dapat melihat CallbackQueryParameterproperti. Meneruskan Funcmasuk mengikat properti di lambda, sehingga Mappingakan dapat mengaksesnya nanti di TryMatchMediaType. Jika Anda memiliki cara yang lebih elegan, silakan berkomentar!

atanamir
sumber
2

Sayangnya, saya tidak memiliki reputasi yang cukup untuk berkomentar, jadi saya akan memposting jawaban. @Justin mengangkat masalah menjalankan pemformat WebApiContrib.Formatting.Jsonp bersama dengan JsonFormatter standar. Masalah itu diatasi dalam rilis terbaru (sebenarnya dirilis beberapa waktu lalu). Selain itu, ini harus bekerja dengan rilis API Web terbaru.

panel kaca
sumber
1

johperl, Thomas. Jawaban yang diberikan oleh Peter Moberg di atas harus benar untuk versi RC karena JsonMediaTypeFormatter yang dia warisi dari menggunakan serializer NewtonSoft Json, dan apa yang dia miliki harus bekerja tanpa perubahan apapun.

Namun, mengapa orang masih menggunakan parameter, padahal Anda bisa melakukan hal berikut

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);
        }
stevethread
sumber
1

Alih-alih menghosting versi pemformat JSONP Anda sendiri, Anda dapat menginstal paket WebApiContrib.Formatting.Jsonp NuGet dengan paket yang sudah diimplementasikan (pilih versi yang sesuai untuk .NET Framework Anda).

Tambahkan formatter ini ke Application_Start:

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

Bagi Anda yang menggunakan HttpSelfHostServer, bagian kode ini akan gagal di HttpContext.Current, karena tidak ada di server host mandiri.

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

Namun Anda dapat menghentikan "konteks" host sendiri melalui penggantian ini.

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 akan memberi Anda "GET", "POST", dll. Dan GetQueryNameValuePairs dapat mengambil parameter? Callback. Jadi kode revisi saya terlihat seperti:

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

Semoga ini bisa membantu sebagian dari Anda. Dengan cara ini Anda tidak perlu shim HttpContext.

C.

Anjing hutan
sumber
0

Jika konteksnya adalah Web Api, berterima kasih dan mengacu pada 010227leojawaban, Anda harus mempertimbangkan WebContext.Currentnilai yang akan menjadi null.

Jadi saya memperbarui kodenya menjadi ini:

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);
    }
}
Rikki
sumber
0

Kami dapat menyelesaikan masalah CORS (Cross-origin resource sharing) dengan dua cara,

1) Menggunakan Jsonp 2) Mengaktifkan Cors

1) Menggunakan Jsonp- untuk menggunakan Jsonp kita perlu menginstal paket nuget WebApiContrib.Formatting.Jsonp dan perlu menambahkan JsonpFormmater di screenshot referensi WebApiConfig.cs,masukkan deskripsi gambar di sini

Kode jquery masukkan deskripsi gambar di sini

2) Mengaktifkan Cors -

untuk mengaktifkan cors kita perlu menambahkan paket nuget Microsoft.AspNet.WebApi.Cors dan perlu mengaktifkan cors di WebApiConfig.cs lihat screenshot

masukkan deskripsi gambar di sini

Untuk referensi lebih lanjut, Anda dapat merujuk repo sampel saya di GitHub menggunakan tautan berikut. https://github.com/mahesh353/Ninject.WebAPi/tree/develop

Mendax
sumber