📜 ⬆️ ⬇️

JSONP Formatter for ASP.NET Web API

The ASP.NET Web API out of the box does not include the JSONP Formatter, but it is fairly easy to create with your own hands.

Why do we need JSONP


JSONP is one of the possibilities of javascript applications to bypass the restriction on receiving data from a server other than the server from which the application was loaded. JSONP wraps the data in JSON format into a function that is executed when the data is received from the server. Imagine that we have a resource, for example, RemoteDomain/aspnetWebApi/albums RemoteDomain/aspnetWebApi/albums , which on a GET request gives us a list of albums and he knows how to give this list in JSONP format. When using jQuery, it looks like this:

 function getAlbums() { $.getJSON("http://remotedomain/aspnetWebApi/albums?callback=?", null, function (albums) { alert(albums.length); }); } 

What does JSONP look like


JSONP is a fairly simple "protocol." All it does is wrap the data in JSON format into a function. The result of the above query looks like this:

Query17103401925975181569_1333408916499( [{"Id":"34043957","AlbumName":"Dirty Deeds Done Dirt Cheap",…},{…}] )

jQuery sends the request, receives the response and "executes" it, taking the JSON data as a parameter.
')

How does JSONP work


To understand how JSONP works, I’ll give the following example in pure 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); } 

Similar to the previous jQuery example, we use this function to get a list of albums:

 function getAlbumsManual() { jsonp("http://remotedomain/aspnetWebApi/albums?callback=?", function (albums) { alert(albums.length); }); } 

JSONP and ASP.NET Web API


As noted at the beginning of the article, the ASP.NET Web API out of the box does not support JSONP. However, it’s very easy to create your own JSONP formatter and connect it to the project.

The code below is based on the example of Christian Weyer. The code has been updated to be compatible with the latest RTM Web API.

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

I will note once again that this code will not work with the beta version of the Web API, it works only with the RTM version.

It should also be noted that when you connect this JSONP formatter, you actually replace the stock JSON formatter, because it handles the same MIME types. This code still uses the stock JSON formatter, but does not initialize it, but creates a new instance for each JSON or JSONP request. This means that if you need to set up some images of the JSON formatter, you will need to do this in this code by overriding GetPerRequestFormatterInstance() .

JSONP connection formatter


A JSONP formatter is connected by adding it to the Formatter collection in the Application_Start() section of the Global.asax.cs file.

 protected void Application_Start(object sender, EventArgs e) { //   GlobalConfiguration .Configuration .Formatters .Insert(0, new Westwind.Web.WebApi.JsonpFormatter()); } 

That's all.

Note. I added a JSONP formatter before everyone else. It is necessary that the JSON formatter be specified before the stock JSON formatter, otherwise it will never be called.

GitHub source code

From translator


In general, this is a very free translation, so all the slaps, please, in a personal. Many parts are omitted, since they did not play a significant role for the transfer of the essence. In any case, please refer to the original.

In the RTM Web API, the WriteToStreamAsync() method differs from that in the Web API RC by one parameter: in the first HttpContent , in the second - HttpContentHeader .

I connected the JSON Formatter in the ApiConfig.cs file:

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

Source: https://habr.com/ru/post/150495/


All Articles