Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Creating a JSONP Formatter for ASP.NET Web API


:P
On this page:

Out of the box ASP.NET WebAPI does not include a JSONP formatter, but it's actually very easy to create a custom formatter that implements this functionality.

Why do we need JSONP?

JSONP is one way to allow browser based JavaScript client applications to bypass cross-site scripting limitations and serve data from the non-current Web server. AJAX in Web Applications uses the XmlHttp object which by default doesn't allow access to remote domains. There are number of ways around this limitation <script> tag loading and JSONP is one of the easiest and semi-official ways that you can do this.

JSONP works by combining JSON data and wrapping it into a function call that is executed when the JSONP data is returned. If you use a tool like jQUery it's extremely easy to access JSONP content.

Imagine that you have a URL like this:

http://RemoteDomain/aspnetWebApi/albums

which on an HTTP GET serves some data - in this case an array of record albums. This URL is always directly accessible from an AJAX request if the URL is on the same domain as the parent request. However, if that URL lives on a separate server it won't be easily accessible to an AJAX request.

Now, if  the server can serve up JSONP this data can be accessed cross domain from a browser client. Using jQuery it's really easy to retrieve the same data with JSONP:

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

The resulting callback the same as if the call was to a local server when the data is returned. jQuery deserializes the data and feeds it into the method. Here the array is received and I simply echo back the number of items returned. From here your app is ready to use the data as needed.

What does JSONP look like?

JSONP is a pretty simple 'protocol'. All it does is wrap a JSON response with a JavaScript function call. The above result from the JSONP call looks like this:

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

The way JSONP works is that the client (jQuery in this case) sends of the request, receives the response and evals it. The eval basically executes the function and deserializes the JSON inside of the function passing the resulting object as a parameter.

How does JSONP work?

To understand how JSONP works, here's some plain JavaScript code that demonstrates the semantics:

function jsonp(url, callback) {
    // create a unique id
    var id = "_" + (new Date()).getTime();

    // create a global callback handler
    window[id] = function (result) {
        // forward the call to specified handler                                       
        if (callback)
            callback(result);
                    
        // clean up: remove script and id
        var sc = document.getElementById(id);
        sc.parentNode.removeChild(sc);
        window[id] = null;
    }

    url = url.replace("callback=?", "callback=" + id);
                
    // create script tag that loads the 'JSONP script' 
    // and executes it calling window[id] function                
    var script = document.createElement("script");
    script.setAttribute("id", id);
    script.setAttribute("src", url);
    script.setAttribute("type", "text/javascript");
    document.body.appendChild(script);
}

The code creates a script tag that basically loads the JSONP snippet and executed it executes it. The 'code' in this case is a function call, which here is a unique name of the function windows[id] I assigned to handle the callback. This method is fired and the JSON payload is converted to a JavaScript instance when it runs. This generic function then routes final result to the function that was passed in as a parameter which allows you just specify an anonymous function or a function delegate.

To call this from any JavaScript code use the following code:

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

This all works fine using either jQuery or this simple JSONP implementation - as long as the server can serve the data with JSONP.

JSONP and ASP.NET Web API

As mentioned previously, JSONP support is not natively in the box with ASP.NET Web API. But it's pretty easy to create and plug-in a custom formatter that provides this functionality.

The following code is based on Christian Weyer's example but has been updated to work with the latest Web API RTM bits, which changes the implementation a bit due to the way dependent objects are exposed differently in the latest builds.

Here's the code:
(updated 8/17/2012 for RTM)

(updated 8/23/2012 per Brad's and BK's  comments)

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

Note again that this code will not work with the Beta bits of Web API - it works only with RTM bits.

This code is a bit different from Christians original code as the API has changed. The biggest change is that the Read/Write functions no longer receive a global context object that gives access to the Request and Response objects as the older bits did.

Instead you now have to override the GetPerRequestFormatterInstance() method, which receives the Request as a parameter. You can capture the Request there, or use the request to pick up the values you need and store them on the formatter. Note that I also have to create a new instance of the formatter since I'm storing request specific state on the instance (information whether the callback= querystring is present) so I return a new instance of this formatter.

Other than that the code should be straight forward: The code basically writes out the function pre- and post-amble and the defers to the base stream to retrieve the JSON to wrap the function call into. The code uses the Async APIs to write this data out (this will take some getting used to seeing all over the place for me).

Note that when you enable this formatter it effectively replaces the stock JSON formatter (ie. JSON.NET) because it handles the same media types for writing. This code still uses the same JSON.NET formatter, but it won't be initialized since effectively we are creating a new instance for each JSON and JSONP write request.

This means if you plan to configure the JsonFormatter for *all* JSON configuration you have to do it in this formatter's GetPerRequestFormatterInstance() override.

Hooking up the JsonpFormatter

Once you've created a formatter, it has to be added to the request processing sequence by adding it to the formatter collection. Web API is configured via the static GlobalConfiguration object.

protected void Application_Start(object sender, EventArgs e)
{

   // Verb Routing 
   RouteTable.Routes.MapHttpRoute(
        name: "AlbumsVerbs",
        routeTemplate: "albums/{title}",
        defaults: new
        {
            title = RouteParameter.Optional,
            controller = "AlbumApi"                   
        }
    );
GlobalConfiguration .Configuration .Formatters .Insert(0, new Westwind.Web.WebApi.JsonpFormatter()); }
 

That's all it takes.

Note that I added the formatter at the top of the list of formatters, rather than adding it to the end which is required. The JSONP formatter needs to fire before any other JSON formatter since it relies on the JSON formatter to encode the actual JSON data. If you reverse the order the JSONP output never shows up. So, in general when adding new formatters also try to be aware of the order of the formatters as they are added.

Resources

Posted in Web Api  

The Voices of Reason


 

Ryan Riley
April 03, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Care to contribute these adjustments to WebApiContrib [1]?

[1] http://github.com/WebApiContrib/WebAPIContrib

Kris Marinescu
April 04, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Great job Rick as this was exactly what I was looking for.

ms440
April 05, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

I would second Ryan Riley's request (while using your code today). THANK YOU RICK.

Rick Strahl
April 05, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@ms440 - issue is that currently builds are out of whack. THis code only works with the current codeplex source, not the beta and it looks like the WebApiContrib source is still using beta code so this won't work.

Will happily contribute once everything syncs up closer to RTM. Already gave permission to allow use when they're ready for it.

Hoogoo
April 06, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

I see, you are really in to JSON now:-) I read the article about JsonValue, Great work!

Lex
June 14, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

I'll be looking at this in the near future and I'm curious. Has this situation changed with the introduction of JSON.Net to the RC build of ASP.Net Web API?

Rick Strahl
June 14, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Lex - JSON.NET is now baked in, in RC and beyond so you won't need to explicitly hook up JSON.NET unless you need to customize the formatting options (which is actually a valid reason to still do this).

Giancarlo
June 24, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Hi Rick,
I attended your jQuery sessions at DevConnections a few years back and as always you present solutions succinctly, thanks for this article. I have a question/criticism: Why isn't this included behavior/functionality included out of the box? According to the ASP .Net website:
"ASP.NET Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients, including browsers and mobile devices."

However, mobile devices could not consume the service in the case that you're using JSON which is probably what you should be doing for mobile devices (for various reasons). Was this something that Microsoft just completely didn't take into consideration or do you know if navtive JSONP support will come in a future version?
Thanks,
Giancarlo

Rick Strahl
June 24, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Giancarlo - I'm not sure why this isn't in the box since this is a very frequent request that I see. There are a few issues with the implementation that don't make this 100% generic and I think this is where often Microsoft doesn't provide a solution for possibility of too many support issues.

However, it is nice that it's so easy to create this type of functionality with very little effort and plug it in yourself.

Tobias
July 25, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Rick: I don't quite understand - .NET 4.0 does support JSONP out of the box. You just have to set the CrossDomainScriptAccessEnabled property in the WebHttpBinding to true.

See also:
- http://msdn.microsoft.com/en-US/library/system.servicemodel.description.webserviceendpoint.crossdomainscriptaccessenabled.aspx
- http://msdn.microsoft.com/en-us/library/ee834511.aspx
- http://www.gutgames.com/post/Enabling-JSONP-in-WCF.aspx

Rick Strahl
July 25, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

WebAPI and WCF REST are two different stacks and this post refers to Web API which at the time I wrote this anyway did not support JSONP natively. FWIW, I didn't know the crossDomainScriptAccessEnabled key was added in 4.0 WCF REST - nice enhancement although not a lot of documentation. Here's another article that's a bit more detail:

http://www.codeproject.com/Articles/417629/Support-for-JSONP-in-WCF-REST-services

victor
August 17, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Got an error on VS2012 RTM:

Error 1 'Westwind.Web.WebApi.JsonpFormatter.WriteToStreamAsync(System.Type, object, System.IO.Stream, System.Net.Http.Headers.HttpContentHeaders, System.Net.TransportContext)': no suitable method found to override C:\Projects-VS2012\TestFullWebsiteStructure\TestWebAPIWebSite\App_Code\JsonpFormatter.cs 85 30 TestWebAPIWebSite

Rick Strahl
August 17, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Victor - yes, the signature changed again. The code on GitHub should be up to date and work with RTM. I'll fix here as well, thanks for the reminder...

Brad Wilson
August 20, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

I have some suggestions on improving the async performance of your code (with the help of Levi and Damian). We've included both .NET 4 and .NET 4.5 versions, and it turns out to be a good illustration how even this relatively simple code block is dramatically improved by the availability of async/await, as well as how tedious and hard it can be to do the right thing in .NET 4.

https://gist.github.com/3411200

BK
August 23, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Hi,

After adding the JsonpFormatter to my pipeline, the Json.NET Enum to string Converter has stopped working.

I have this line to add the StringEnumConverter:

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter());

But with the JsonpFormatter in the beggining of the pipeline, somehow the enum to string conversion has stopped working.

Any ideas?

Rick Strahl
August 24, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@BK - Hmmm... yes you're right.

JsonpFormatter inherits from the JSON.NET formatter so you can apply the same properties when the formatter is created. This involves overriding GetPerRequestFormatterInstance():

public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, 
               System.Net.Http.HttpRequestMessage request, 
               MediaTypeHeaderValue mediaType)
{       
    var formatter = new JsonpFormatter() 
    {                 
        JsonpCallbackFunction = GetJsonCallbackFunction(request) 
    };
 
    formatter.SerializerSettings.Converters.Add(new StringEnumConverter());
 
    return formatter;
}


And this works. After looking at this I was hoping you could just do:

formatter.SerializerSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;


but that seems to have no effect as the added converters are not showing up for some reason (Converters.Count = 0). I'll have to look at this closer another time, but for now it looks like you have to configure the formatter directly with the desired settings in this method.

Rick Strahl
August 24, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Brad - I updated the code in the post and in the sample. Thanks for pointing this out. I get splitting things up, but is it necessary to have the two separate ContinueWith() blocks on the bottom? What's the benefit of breaking up the write and close operations especially since they're marked as synchronous???

Francis Paulin
September 10, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Hi, and thanks for a great post!

Have you tried using the formatter with a self hosted console application?

I've tried your formatter in a regular MVC 4 project, and it worked immediately. However, I would like to use it in a self hosted console application, and I've had a lot of trouble getting it to work.

I've registered the formatter, and verified that it is added:

var config = new HttpSelfHostConfiguration(serviceUrl);
 
config.Formatters.Insert(0, new JsonpMediaTypeFormatter());


I have verified that it is being called when I make a request with the following code:

$("#TestButton").click(function () {         
    $.ajax({
        url: 'http://localhost:8082/Api/Test',
        type: 'GET',
        dataType: 'jsonp',
        success: function(data) {
            alert(data.TestProperty);
        }
    }); 
});


I've checked in Fiddler, and the response I get is:

HTTP/1.1 504 Fiddler - Receive Failure
Content-Type: text/html; charset=UTF-8
Connection: close
Timestamp: 09:30:51.813
 
[Fiddler] ReadResponse() failed: The server did not return a response for this request.


I'd be really grateful if anybody can shed some light on what is going on!

Thanks,

Francis

Francis Paulin
September 11, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

The solution to the problem above can be found here: http://stackoverflow.com/questions/12347461/return-jsonp-from-self-hosted-web-api-console-app

Francis

Manuel Ortiz
October 05, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Hi, I don't know why but the last code is not working on my project. I followed the instructions step by step and it does not work. It worked with the previous code before the last updates. When I execute the request with jsonp, it returns with no data whatsoever. However, when I execute it with normal json it works perfectly. I am using VS2010 with SP1 and MVC4 installed. I have added all the references listed on your project using NuGet. I don't know what else to do. Have anyone else experienced problems with the new code?

Thanks,
Manuel

mahdi
October 21, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

thanks for your post
I send following json request still it doesn't work and also I added JSON Formatter!!

function PostData() {
jQuery.support.cors = true;
var model = {
UserName: "mahdi",
Password: "123"

};

$.ajax({
url: 'http://localhost:6236/api/values/post',
type: 'POST',
data: JSON.stringify(model),
contentType: "application/json;charset=utf-8",
success: function (data) {
alert("success");
$('#divResult').append(data + "(" + data.status + ")");
},
error: function (data) {
alert(data.status + " error");


}

});
}

Rick Strahl
October 21, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@mahdi - JSONP only works with GET operations - not any other HTTP Verb like POST.

mahdi
October 22, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

I think you are wrong because when I send post request internaly in my project and use this
url: '/api/values/post' instead of url: 'http://localhost:6236/api/values/post',
then it works great

Yannick
October 22, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Rick Why is that?

Rick Strahl
October 22, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Yannick, @mahdi - If you read the code I posted above that shows how JSONP actually works you can see why it doesn't work: The JSONP URL is 'called' from a <script> tag as a src= attribute. You can't post data with that.

@mahdi - you're not talking about JSONP you're just talking about plain HTTP POST operations with $.ajax() which is totally different. I think different ports on the same domain may also be allowed for plain XHR requests. If it does work for you try it with a totally different domain/machine and I think you'll see that regular XHR calls fail.

mahdi
October 22, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

thanks rick, but my code work from diffrent domain by using IE9 !!!
my problem is that it doesn't work with Chrome and Firefox !!?

Rick Strahl
October 23, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@mahdi - you should assume it doesn't work. You can make security changes in IE that allow for cross domain calls, but it's tricky to set up and not a good idea. Other browsers do not support it and for this reason you should just assume it does not work.

Denis
December 07, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

Hi Thanks Rick.. Really nice article for an important issue with the web api.

I am trying to add the JsonpFormatter classs to my code but I am having and error related to override Task WriteToStreamAsync(..) the errro is "C# Not suitable member to override"

Also from this comments(@mahdi - JSONP only works with GET operations - not any other HTTP Verb like POST.) Do you mean with this that the cross domain request suing other http verbs will not work with JSONP?
How can I send information from my other application to my Web API using client side

Thank you

Alexander
December 12, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

How do we use JSONP together with WebApi to insert and write records if JSONP only works with http GET?

Rick Strahl
December 13, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Alexander - Well Cross-domain HTTP access is a security issue that browser vendors decided needs to be surpressed. JSONP is one workaround and it works only with HTTP GET which is better than nothing.

Cross domain HTTP from the browser is simply something that you shouldn't plan on using as a design choice. Apps will typically access local server resources, and if they need resources from remote domains, use server side HTTP calls to aggregate the data.

Desktop apps or mobile apps can easily access cross-domain content so this issue is limited to browsers - but APIs are meant for any type of HTTP client.

In the end this is a browser limitation not a limitation of the server software that hosts an API.

Alexander
December 18, 2012

# re: Creating a JSONP Formatter for ASP.NET Web API

@Rick

The WebAPI will be accessed by both the mobile clients that do not use a browser, as well as a browser-based web site that uses javascript controls to read/write to webapi. The issue is with the web site.

To explain the issue in a little more detail:
I am writing the backend using WebAPI on my server, and the web developer is writing the web site using KendoUI JavaScript code - a grid control that reads and writes to my WebAPI from her machine. The machines are on different domains, so a SOP error results. What is the recommended way to get around this?

I am considering using CORS as a workaround, but IE 8 and 9 have limited support for CORS. I found this blog post that discusses how to allow IE 8 and 9 to use CORS:

http://georgedurzi.com/implementing-cross-browser-cors-support-for-asp-net-web-api/

I have yet to figure out how to make the KendoUI grid use the JavaScript transport function code referenced in the post.

As an alternative, I guess we can bind the grid to write data to some local data store that resides on the machine/server which serves the page, and then have the local server pass off that written data to the WebAPI server server-side using HttpClient or something like that.. that just seems the wrong way to do it and an extra step, instead of having the javascript grid post the data straight to WebAPI.

What are your thoughts on doing the server-side HTTP call this way, or the above blog post?

Thank you

Ashwin
December 06, 2013

# re: Creating a JSONP Formatter for ASP.NET Web API

Really simple and easy to implement solution!! Thanks

John H
March 09, 2017

# re: Creating a JSONP Formatter for ASP.NET Web API

I know its an old thread and thanks for this solution. Just to note the the Dispose() of the StreamWriter() closes the underlying Stream. This can cause problem with other filters where a MemoryStream is passed to WriteToStreamAsync.

.Net 4.5 now has a leaveOpen bool in one of the StreamWriter constructors.

I have removed the Dispose() from the code with no memory or resource issues.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024