Nerdworks logo "The nerd shall inherit the earth."

Nerdworks Blogorama

Nerdspeak

Enabling JSONP calls on ASP.NET MVC
Technobabble
10/19/2009 2:15:09 PM

JSONP is the technique of making cross-domain HTTP requests via JavaScript circumventing browser security restictions on requests that return data in the JSON format. By cross-domain, we refer to the practice of making HTTP requests on URLs that refer to resources residing on a domain other than the one from where the page being viewed/executed was loaded from. You'll find a good description of what JSONP is all about here. Briefly, the technique exploits a browser back-door where the SRC attribute on a SCRIPT tag is allowed to be a "foreign" URL and the browser will download the code and evaluate it. But then again, this is perhaps not so much a back-door as a feature that allows you to build mash-ups by composing code that draws on functionality hosted on different servers. If this were not allowed then something like the Content Delivery Networks (CDN) would just not work. In fact this blog that you're reading right now loads the jQuery JavaScript library via the Google CDN.

Here's an example of this in practice - I've loaded up the 5 most "interesting" photos uploaded on Flickr in the last day or so below:

And here's the code that accomplishes this with a little jQuery magic:

//
// Flickr REST url
//
var url = "http://api.flickr.com/services/rest/?";

//
// My Flickr API key
//
var api_key = "<<your flickr api key here>>";

//
// build a flicker url from a photo object
//
function buildPhotoUrl(photo) {
    return "http://farm" + photo.farm +
           ".static.flickr.com/" + photo.server + "/" +
           photo.id + "_" + photo.secret + "_t.jpg";
}

//
// get interesting photos
//
function getInterestingPhotos() {
    //
    // build the URL
    //
    var call = url + "method=flickr.interestingness.getList&api_key=" +
               api_key + "&per_page=5&page=1&format=json&jsoncallback=?";

    //
    // make the ajax call
    //
    $.getJSON(call, function(rsp) {
        if (rsp.stat != "ok") {
            //
            // something went wrong!
            //
            $("#interesting_photos").append(
                "<label style=\"color:red\">Whoops!  It didn't work!" +
                "  This is embarrassing!  Here's what Flickr had to " +
                " say about this - " + rsp.message + "</label>");
        }
        else {
            //
            // build the html
            //
            var html = "";
            $.each(rsp.photos.photo, function() {
                var photo = this;
                html += "<span><img src=\"" + buildPhotoUrl(photo) +
                        "\" title=\"" + photo.title + "\" alt=\"" + photo.title +
                        "\" /></span> ";
            });

            //
            // append this to the div
            //
            $("#interesting_photos").append(html);
        }
    });
}

//
// get the photos
//
$(getInterestingPhotos);

The basic idea is to dynamically add a SCRIPT tag to the DOM where the SRC attribute is pointing to the external URL where the data that we want resides. Upon encountering a new SCRIPT node, the browser immediately starts loading the code and evaluating it (and also freezes pretty much everything else in the browser while it is doing this!). If we can have the external resource render a call to a function that we define (or to a well-known function name) in the script that it produces then we can effectively have a callback routine invoked when the data is received from the foreign domain. The function to be called when the script dynamically loads is usually passed in as a query string parameter in the GET request or can be a function name that is mandated by the 3rd party site. Flickr for example defaults to rendering a call to a function called jsonFlickrApi but allows you to override this by passing a different name via the jsoncallback query string parameter.

jQuery has direct support for JSONP in that if you include a callback=? parameter in the AJAX URL for the getJSON call then it will replace the ? with a dynamically generated global JavaScript function name that it also defines. The word callback can be replaced with anything else as we did above by using jsoncallback. All the grunt work of dynamically adding SCRIPT tags to the DOM is done by jQuery.

Now, imagine that you designed your own little REST based service using ASP.NET MVC and have opted to provide JSON as one of the data output formats and wish to support JSONP requests. This basically means that you simply need to wrap your JSON output in a call to a user-supplied or a standard JavaScript function. So instead of rendering something like this out to the client:

{
    photos : [
        photo : {
            id : 232992,
            secret : "bAC980980c09c08a0ef"
        }
    ],
    page : 1,
    total : 500,
    photosPerPage : 10
}

You want to render something like this:

callback({
    photos : [
        photo : {
            id : 232992,
            secret : "bAC980980c09c08a0ef"
        }
    ],
    page : 1,
    total : 500,
    photosPerPage : 10
});

Where callback is the name of the JavaScript function defined by the client that needs to be invoked when the script is evaluated by the browser. If you wanted your ASP.NET MVC controllers to support this, how would you do it? The first approach I took was to simply define a custom ActionResult class that would produce the correct script. Here's what this looks like:

/// <summary>
/// Renders result as JSON and also wraps the JSON in a call
/// to the callback function specified in "JsonpResult.Callback".
/// </summary>
public class JsonpResult : JsonResult
{
    /// <summary>
    /// Gets or sets the javascript callback function that is
    /// to be invoked in the resulting script output.
    /// </summary>
    /// <value>The callback function name.</value>
    public string Callback { get; set; }

    /// <summary>
    /// Enables processing of the result of an action method by a
    /// custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>.
    /// </summary>
    /// <param name="context">The context within which the
    /// result is executed.</param>
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        HttpResponseBase response = context.HttpContext.Response;
        if (!String.IsNullOrEmpty(ContentType))
            response.ContentType = ContentType;
        else
            response.ContentType = "application/javascript";

        if (ContentEncoding != null)
            response.ContentEncoding = ContentEncoding;

        if (Callback == null || Callback.Length == 0)
            Callback = context.HttpContext.Request.QueryString["callback"];

        if (Data != null)
        {
            // The JavaScriptSerializer type was marked as obsolete
            // prior to .NET Framework 3.5 SP1 
#pragma warning disable 0618
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            string ser = serializer.Serialize(Data);
            response.Write(Callback + "(" + ser + ");");
#pragma warning restore 0618
        }
    }
}

Now you can simply return a JsonpResult (note the p before the "R" in "Result") from your action methods instead of JsonResult and everything should just work. Client's would indicate that this is a JSONP request by appending a query string parameter called callback to the request with the name of a JavaScript function that is to be called. I wasn't however, entirely happy with this situation as this would mean that I'd have to go and change the return type of all my action methods to return a JsonpResult instead of a JsonResult. And also update the code that instantiated JsonResult. I wanted a less disruptive solution if you will.

It is precisely for scenarios such as this that the ASP.NET MVC framework includes extension points in the form of action filters. Action filters are basically hooks that you can define to intercept and enhance request processing in various ways. ASP.NET MVC allows you to define filters that get called before and/or after the action method is invoked and also before and/or after the ExecuteResult method on the ActionResult is invoked. Action filters are defined as .NET custom attribute classes that inherit from ActionFilterAttribute. ActionFilterAttribute defines 4 virtual methods that you can override to hook into the request processing pipeline at the right juncture. The OnActionExecuted method for instance is invoked immediately after the action method has been invoked and gives you a chance to further process the returned result. This would perfectly suit our purpose here where we wish to conditionally supplant the JsonResult object being returned from the action method with a JsonpResult instead. Here's what I came up with:

public class JsonpFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if(filterContext == null)
            throw new ArgumentNullException("filterContext");

        //
        // see if this request included a "callback" querystring parameter
        //
        string callback = filterContext.HttpContext.Request.QueryString["callback"];
        if (callback != null && callback.Length > 0)
        {
            //
            // ensure that the result is a "JsonResult"
            //
            JsonResult result = filterContext.Result as JsonResult;
            if (result == null)
            {
                throw new InvalidOperationException("JsonpFilterAttribute must be applied only " +
                    "on controllers and actions that return a JsonResult object.");
            }

            filterContext.Result = new JsonpResult
            {
                ContentEncoding = result.ContentEncoding,
                ContentType = result.ContentType,
                Data = result.Data,
                Callback = callback
            };
        }
    }
}

As is perhaps self-evident, we simply check if the query string included a parameter called callback and if it did, then we supplant the Result object in filterContext with a new JsonpResult instance. This approach allows us to enable JSONP processing on a controller or an action method by simply tagging on the JsonpFilter attribute. If I wanted all of my methods in the controller to support JSONP then, I can do this:

[JsonpFilter]
public class DoofusController : Controller
{
    //
    // all your action methods here
    //
}

Or if I wanted it to work with only a specific action method then I'd do this:

public class DoofusController : Controller
{
    [JsonpFilter]
    public JsonResult DoTheThing(string data, string moreData)
    {
        return new JsonResult
        {
            Data = FetchSomeData(data, moreData)
        };
    }
}

ASP.NET MVC is kind of cool eh?! :-P

 
lau' 3/9/2010 8:30:05 AM
Thanks a lot!
I was getting confused with all this JSON and JSONP thing and it helped clarify it.
I tested your code and it works great.
 
Ranju V 3/9/2010 8:52:54 AM
Thanks for pointing out the typo lau. I've fixed it now.
 
Yuval Kaplan 4/25/2010 12:13:58 PM
Thanks for making this issue so clear and simple.
It is truly the correct JSONP solution for MVC.
Lots of posts on JSNOP seem to over complicate the issue while you have given a great solution through a very simple example.
Well done!
 
Ranju V 4/25/2010 12:56:10 PM
Glad you liked it Yuval! Thanks for the comment.
 
jim tollan 5/23/2010 7:21:24 AM
Ranju,

thank you for this timely post. i've been looking for a way to leverage mvc functionality from a php site that needs to utilise lookup data from it's sister site. this approach was one that i had imagined i'd like to do but didn't have the technical details on how to implement it.

perfect, thank you...

jim
 
Ranju V 5/23/2010 11:24:35 AM
You're welcome Jim. Thanks for your comment!
 
Phil Derksen 6/8/2010 3:15:55 PM
Great post. Helped a ton trying to do a JSON API cross-domain in an app.

I'm using ASP.NET MVC 2, so I had to add one thing to the JsonpFilterAttribute code to get it to work: JsonRequestBehavior = JsonRequestBehavior.AllowGet (see below)

filterContext.Result = new JsonpResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
ContentEncoding = result.ContentEncoding,
ContentType = result.ContentType,
Data = result.Data,
Callback = callback
};

Thanks!
Phil Derksen

http://philderksen.com
 
Ranju V 6/8/2010 3:23:46 PM
Thanks for the tip on the pixie dust needed to make it work with ASP.NET MVC2 Phil!
 
Luke 6/10/2010 12:22:57 PM
That code is extremely useful. Thanks for sharing!
 
Ranju V 6/10/2010 12:26:00 PM
Thanks for your comment Luke!
 
Dave 12/12/2010 8:56:20 PM
Thanks so much for the post! Very helpful, and it worked like a charm out of the box. Wonder why the MVC team hasn't thought to put something this in the official codebase. Have you approached them?
 
Ranju V 12/13/2010 7:44:03 AM
Thanks for your comment Dave! No I haven't approached the MVC team with this. But it does make sense - maybe I'll give it a shot.
 
Bart Burkhardt 4/12/2011 1:33:25 PM
Great post, one thing though,

JSONP returns javascript so the content-type should be application/javascript

Webkit complains if you use json as return type.

Thanks
 
Ranju V 4/13/2011 6:47:16 AM
You're absolutely right Bart. Have fixed the post now. Thanks!
 
Mike Pelton 6/3/2011 9:22:23 AM
I spent much of my day trying to get this whole JSONP and MVC 3 scenario to work - if I'd seen your post at 9:00am I could have been on the beach by 9:30!! Many thanks, Mike
 
Ranju V 6/3/2011 9:46:47 AM
Hehe.. You're welcome Mike!
 
Chris Marisic 12/2/2011 11:21:40 AM
Just ran head first into the same origin policy today, with your JSONP filter was able to clear that hurdle in minutes!

Thanks alot for your post.
 
Ranju V 12/2/2011 11:28:13 AM
Thanks for your comment Chris. Glad it worked out!
 
DrewW 4/2/2012 4:59:27 AM
Clear, concise, well documented -- nice work... but most of all I'm commenting because you take the time to respond to comments. Thanks!
 
Ranju V 4/2/2012 6:40:12 AM
Thanks for the comment DrewW. For the traffic I get, its always a kick when somebody leaves a comment :).
 
robert 4/30/2012 3:42:36 AM
Verry good, using it for a client as we speak! :)
 
Ranju V 4/30/2012 4:31:48 AM
Thanks Robert!
 
Bart Burkhardt 6/14/2012 6:21:40 AM
I think &jsoncallback=?";

should be &callback=?";
 
Ranju V 6/14/2012 7:09:22 AM
The Flickr API requires that the parameter be called "jsoncallback". The specific name of the query string parameter to be used is specified by the service provider - in this case, Flickr.
 
StuH 6/18/2012 7:46:25 PM
Excellent Post - its been said before - but 20 minutes with this post saved hours of work!
 
Raniu V 6/18/2012 8:30:59 PM
Thanks StuH!
 
Ivan 9/26/2012 10:04:37 AM
Thanks a lot!
 
Ranju V 9/26/2012 10:10:45 AM
Thanks for your comment Ivan!
 
Sataa Daghir 10/18/2012 9:07:29 PM
Many thanks, your post saved my time. thank you again for sharing.
 
Ranju V 10/19/2012 3:30:12 AM
Thanks for leaving a comment Sataa.
 
Chris Towles 1/24/2013 3:49:59 AM
Thank you for this great post. Really saved me after about giving up with Jsonp!!
 
Ranju V 1/24/2013 5:32:54 AM
Thanks for your comment Chris Towles.
 
Wesley 2/13/2013 6:45:49 AM
Thanks. it works smoothly
 
Ranju V 2/13/2013 7:44:15 AM
Thanks Wesley.
 
Damon 2/27/2013 2:25:08 PM
Hey man,

Thanks for this great solution, i've got a question tho.
I'm working on a webservice project with MVC4 and ASP.NET Web API.
For some reason my ActionFilter doesn't work, it just never gets hooked. My code is exactly the same as yours except my controller doesn't extend a regular Controller but an ApiController.

Is there any need to register the filter or anything like that which i might be forgetting about?
 
Fahimeh 5/13/2013 12:14:10 AM
Thanks mate!

It really helped me!
 
Glenn 5/22/2013 6:06:39 PM
I have the same issue as Damon. MVC 4 never seems to hit the OnActionExecuted is never hit and normal JSON is returned
 
Glenn 5/22/2013 10:45:00 PM
Nevermind. I got the answer
With MVC 4 there are two ActionFilterAttributes.
The one I needed was System.Web.Http.Filters.ActionFilterAttribute.
Now I'm hitting a breakpoint.
 
Ranju V 5/23/2013 5:38:58 AM
Ah, yes. Good that its working now.
 
Tom 2/11/2014 10:35:34 AM
Works perfectly.Thanks!
 
G Fisher 2/17/2014 8:41:57 PM
As many others have said - great article and very helpful :-)
 
Ranju V 2/18/2014 2:14:58 AM
Thanks for your comments Tom and Fisher!
 

Please fill this form and click on the "Submit" button to post a comment. All fields except the comment box are optional. You don't have to give me your name and email, but if you do then that might allow me to follow up with you on your comment. Also, I won't publish your email address here or anywhere else.

 
Your Name :
Your Email :
Your Comment :
   

What in your opinion do you get when you multiply the number 5 by the number 2?

Your answer will help me figure out whether you are human or a spam bot. If you're a spam bot I hope your kernel core dumps and your CPU bursts into flames.

   

Please click here to go back to the blog.

blogorama home
about this blog
email the author
where on earth am i?
subscribe to mailing list
feeds Use these links for feed syndication
rss  |  atom
by category
technobabble (64)
philosophical crud (3)
irrelevant stuff (7)
archive
june, 2012 (1)
november, 2011 (2)
october, 2011 (1)
september, 2011 (7)
july, 2011 (3)
june, 2011 (2)
may, 2011 (3)
april, 2011 (1)
march, 2011 (1)
february, 2011 (1)
february, 2010 (1)
october, 2009 (1)
september, 2009 (1)
july, 2009 (5)
march, 2009 (2)
august, 2008 (2)
march, 2008 (1)
january, 2008 (1)
september, 2007 (2)
april, 2007 (1)
february, 2007 (2)
december, 2006 (1)
october, 2006 (1)
september, 2006 (4)
august, 2006 (3)
july, 2006 (4)
june, 2006 (3)
may, 2006 (6)
april, 2006 (2)
recent entries
Iterating over a st...
Playing in-memory a...
Add a "Web Server H...
Some notes on C++11...
Implementing variab...
Debugging existing...
Screen scraping wit...
Building an Instagr...
Building an Instagr...
649580 hits