ASP.NET Web API из коробки не включает в себя JSONP Formatter, но его довольно просто создать собственными руками.
JSONP — это одна из возможностей javascript-приложений обойти ограничение на получение данных с сервера отличного от сервера с которого произошла загрузка приложения. JSONP оборачивает данные в формате JSON в функцию, которая выполняется при получении данных с сервера. Представим, что у нас есть ресурс, например,
JSONP довольно простой «протокол». Все что он делает — это оборачивает данные в формате JSON в функцию. Результат приведенного выше запроса выглядит примерно так:
jQuery посылает запрос, получает ответ и «выполняет» его, принимая JSON-данные как параметр.
Чтобы понять, как работает JSONP, приведу следующий пример на чистом javascript:
Аналогично предыдущему примеру с jQuery, используем данную функцию для получения списка альбомов:
Как было отмечено в начале статьи, ASP.NET Web API из коробки не поддерживает JSONP. Однако, очень просто создать собственный JSONP formatter и подключить его к проекту.
Приведенный ниже код основан на примере Christian Weyer. Код был доработан для совместимости с последней Web API RTM.
Отмечу ещё раз, что данный код не будет работать с бета-версией Web API, он работает только с версией RTM.
Также нужно отметить, что при подключении этого JSONP formatter вы фактически заменяете стоковый JSON formatter, потому что он обрабатывает те же самые MIME-типы. Этот код по прежнему использует стоковый JSON formatter, но не инициализирет его, а создает новый экземпляр для каждого JSON или JSONP запроса. Это означает, что если вам будет нужно каким-то образов настроить JSON formatter, делать это нужно будет в этом коде путем переопределения
Подключение JSONP formatter происходит путем добавления его в Formatter collection в секции
Вот и все.
Примечание. Я добавил JSONP formatter перед всеми остальными. Необходимо, чтобы JSON formatter был указан до стокового JSON formatter, иначе он никогда не будет вызван.
Исходный код на GitHub
В целом это очень вольный перевод, поэтому все оплеухи, пожалуйста, в личку. Многие части опущены, так как для передачи сути не играли значимой роли. В любом случае прошу обращаться к оригиналу.
В Web API RTM метод
Я подключал JSON Formatter в файле
ApiConfig.cs
Global.asax.cs
Зачем нам JSONP
JSONP — это одна из возможностей javascript-приложений обойти ограничение на получение данных с сервера отличного от сервера с которого произошла загрузка приложения. JSONP оборачивает данные в формате JSON в функцию, которая выполняется при получении данных с сервера. Представим, что у нас есть ресурс, например,
RemoteDomain/aspnetWebApi/albums, который на GET-запрос отдает нам список альбомов и он умеет отдавать этот список в формате JSONP. При использовании jQuery выглядит это так:function getAlbums() { $.getJSON("http://remotedomain/aspnetWebApi/albums?callback=?", null, function (albums) { alert(albums.length); }); }
Как выглядит JSONP
JSONP довольно простой «протокол». Все что он делает — это оборачивает данные в формате JSON в функцию. Результат приведенного выше запроса выглядит примерно так:
Query17103401925975181569_1333408916499( [{"Id":"34043957","AlbumName":"Dirty Deeds Done Dirt Cheap",…},{…}] )jQuery посылает запрос, получает ответ и «выполняет» его, принимая JSON-данные как параметр.
Как работает JSONP
Чтобы понять, как работает JSONP, приведу следующий пример на чистом javascript:
function jsonp(url, callback) { // создание уникального идентификатора var id = "_" + (new Date()).getTime(); // создание глобального обработчика window[id] = function (result) { // вызов этого обработчика if (callback) callback(result); // зачистка: удаление скрипта и идентификатора var sc = document.getElementById(id); sc.parentNode.removeChild(sc); window[id] = null; } url = url.replace("callback=?", "callback=" + id); // создание тега <script>, который загрузит JSONP-скрипт // и выполнит его, вызвав функцию window[id] var script = document.createElement("script"); script.setAttribute("id", id); script.setAttribute("src", url); script.setAttribute("type", "text/javascript"); document.body.appendChild(script); }
Аналогично предыдущему примеру с jQuery, используем данную функцию для получения списка альбомов:
function getAlbumsManual() { jsonp("http://remotedomain/aspnetWebApi/albums?callback=?", function (albums) { alert(albums.length); }); }
JSONP и ASP.NET Web API
Как было отмечено в начале статьи, ASP.NET Web API из коробки не поддерживает JSONP. Однако, очень просто создать собственный JSONP formatter и подключить его к проекту.
Приведенный ниже код основан на примере Christian Weyer. Код был доработан для совместимости с последней Web API RTM.
using System; using System.IO; using System.Net; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Web; using System.Net.Http; using Newtonsoft.Json.Converters; using System.Web.Http; namespace Westwind.Web.WebApi { /// <summary> /// Handles JsonP requests when requests are fired with text/javascript /// </summary> public class JsonpFormatter : JsonMediaTypeFormatter { public JsonpFormatter() { SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript")); JsonpParameterName = "callback"; } /// <summary> /// Name of the query string parameter to look for /// the jsonp function name /// </summary> public string JsonpParameterName {get; set; } /// <summary> /// Captured name of the Jsonp function that the JSON call /// is wrapped in. Set in GetPerRequestFormatter Instance /// </summary> private string JsonpCallbackFunction; public override bool CanWriteType(Type type) { return true; } /// <summary> /// Override this method to capture the Request object /// </summary> /// <param name="type"></param> /// <param name="request"></param> /// <param name="mediaType"></param> /// <returns></returns> public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, System.Net.Http.HttpRequestMessage request, MediaTypeHeaderValue mediaType) { var formatter = new JsonpFormatter() { JsonpCallbackFunction = GetJsonCallbackFunction(request) }; // this doesn't work unfortunately //formatter.SerializerSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings; // You have to reapply any JSON.NET default serializer Customizations here formatter.SerializerSettings.Converters.Add(new StringEnumConverter()); formatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; return formatter; } public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext) { if (string.IsNullOrEmpty(JsonpCallbackFunction)) return base.WriteToStreamAsync(type, value, stream, content, transportContext); StreamWriter writer = null; // write the pre-amble try { writer = new StreamWriter(stream); writer.Write(JsonpCallbackFunction + "("); writer.Flush(); } catch (Exception ex) { try { if (writer != null) writer.Dispose(); } catch { } var tcs = new TaskCompletionSource<object>(); tcs.SetException(ex); return tcs.Task; } return base.WriteToStreamAsync(type, value, stream, content, transportContext) .ContinueWith( innerTask => { if (innerTask.Status == TaskStatus.RanToCompletion) { writer.Write(")"); writer.Flush(); } },TaskContinuationOptions.ExecuteSynchronously) .ContinueWith( innerTask => { writer.Dispose(); return innerTask; },TaskContinuationOptions.ExecuteSynchronously) .Unwrap(); } /// <summary> /// Retrieves the Jsonp Callback function /// from the query string /// </summary> /// <returns></returns> private string GetJsonCallbackFunction(HttpRequestMessage request) { if (request.Method != HttpMethod.Get) return null; var query = HttpUtility.ParseQueryString(request.RequestUri.Query); var queryVal = query[this.JsonpParameterName]; if (string.IsNullOrEmpty(queryVal)) return null; return queryVal; } } }
Отмечу ещё раз, что данный код не будет работать с бета-версией Web API, он работает только с версией RTM.
Также нужно отметить, что при подключении этого JSONP formatter вы фактически заменяете стоковый JSON formatter, потому что он обрабатывает те же самые MIME-типы. Этот код по прежнему использует стоковый JSON formatter, но не инициализирет его, а создает новый экземпляр для каждого JSON или JSONP запроса. Это означает, что если вам будет нужно каким-то образов настроить JSON formatter, делать это нужно будет в этом коде путем переопределения
GetPerRequestFormatterInstance().Подключение JSONP formatter
Подключение JSONP formatter происходит путем добавления его в Formatter collection в секции
Application_Start() файла Global.asax.csprotected void Application_Start(object sender, EventArgs e) { // ваш код GlobalConfiguration .Configuration .Formatters .Insert(0, new Westwind.Web.WebApi.JsonpFormatter()); }
Вот и все.
Примечание. Я добавил JSONP formatter перед всеми остальными. Необходимо, чтобы JSON formatter был указан до стокового JSON formatter, иначе он никогда не будет вызван.
Исходный код на GitHub
От переводчика
В целом это очень вольный перевод, поэтому все оплеухи, пожалуйста, в личку. Многие части опущены, так как для передачи сути не играли значимой роли. В любом случае прошу обращаться к оригиналу.
В Web API RTM метод
WriteToStreamAsync() отличается от такового в Web API RC одним параметром: в первом HttpContent, во втором — HttpContentHeader.Я подключал JSON Formatter в файле
ApiConfig.cs:ApiConfig.cs
public static void RegisterApiConfigure(HttpConfiguration config) { // Remove the JSON formatter //config.Formatters.Remove(config.Formatters.JsonFormatter); // Remove the XML formatter config.Formatters.Remove(config.Formatters.XmlFormatter); // Indenting //config.Formatters.JsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; // Add a custom JsopFormatter config.Formatters.Insert(0, new JsonpFormatter()); }
Global.asax.cs
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ApiConfig.RegisterApiConfigure(GlobalConfiguration.Configuration); }
