Cross-domain JsonP using Asp.net MVC and jQuery

So there are a couple great walkthroughs out there that talk about using jquery jsonp in cross-domain scenarios.  They talk a lot about the requesting and server side, but not so much about how to use the callback. So to clarify, I’ll step through what I did to make this work including providing my implementation for a new ActionResult called JsonPResult for MVC.

Starting out, client-side on the calling page, I setup my script. This function makes a request to the server and expects a jsonp response back. The default “success” handler is optional here and will be invoked along-side the one you specify for jsonp.

$.ajax({
    url: 'http://other-domain/api/GetInformation',
    data: { key: 'some data key, this parameter is optional' },
    type: "GET",
    dataType: "jsonp",
    jsonpCallback: "localJsonpCallback"
});

function localJsonpCallback(json) {
    //do stuff...
}

We need to setup the server side.  I’m using Asp.Net MVC, so I built this new ActionResult for handling Jsonp GET requests. I copied most of the code from the original JsonResult class in System.Web.MVC Ms-Pl code. Before I show you that, you should see it in action. Here I have a controller action that uses JsonpResult:

public JsonpResult GetInformation(string key)
{
    var resp = new Core.Model.CustomObject();
    if (validateKey(key))
    {
        resp.Data = "some custom message";
        resp.Success = true;
    }
    else
        resp.Message = "unauthorized";

    return this.Jsonp(resp); //using extension method
}

This automatically handles all the fancy Jsonp work that goes on behind the scenes.  It serializes the custom object into Json and wraps it with the callback function. So your response appears something like this:

localJsonpCallback({"Data":{"Success":true, Message: "some custom message"}});

I setup an extension method to use controller.Jsonp(object) just as you do the existing controller.Json(object) method already available. This should make it a lot easier to handle. It will inspect the call for the json callback itself so you also don’t have to manually pull it from the request.  It will also work with the two different types of callback requests parameters in use out there like: “jsoncallback” and “callback.”

/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *
 * Content of this class was mostly derived from the original
 * JsonResult class in the System.Web.Mvc 2.0 RTM Assembly. This
 * has beeen slightly extended for use with JSONP calls.
 *
 * This software is subject to the Microsoft Public License (Ms-PL).
 * A copy of the license can be found in the license.htm file included
 * in this distribution.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * ***************************************************************************/

namespace System.Web.Mvc
{
    using System;
    using System.Text;
    using System.Web;
    using System.Web.Mvc.Resources;
    using System.Web.Script.Serialization;

    public class JsonpResult : ActionResult
    {

        public JsonpResult()
        {
        }

        public Encoding ContentEncoding
        {
            get;
            set;
        }

        public string ContentType
        {
            get;
            set;
        }

        public object Data
        {
            get;
            set;
        }

        public string JsonCallback { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            this.JsonCallback = context.HttpContext.Request["jsoncallback"];

            if (string.IsNullOrEmpty(this.JsonCallback))
                this.JsonCallback = context.HttpContext.Request["callback"];

            if (string.IsNullOrEmpty(this.JsonCallback))
                throw new ArgumentNullException("JsonCallback required for JSONP response.");

            HttpResponseBase response = context.HttpContext.Response;

            if (!String.IsNullOrEmpty(ContentType))
            {
                response.ContentType = ContentType;
            }
            else
            {
                response.ContentType = "application/json";
            }
            if (ContentEncoding != null)
            {
                response.ContentEncoding = ContentEncoding;
            }
            if (Data != null)
            {
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                response.Write(string.Format("{0}({1});", this.JsonCallback, serializer.Serialize(Data)));
            }
        }
    }

    //extension methods for the controller to allow jsonp.
    public static class ContollerExtensions
    {
        public static JsonpResult Jsonp(this Controller controller, object data)
        {
            JsonpResult result = new JsonpResult();
            result.Data = data;
            return result;
        }
    }
}

When the response is downloaded from the client, it’s invoked and you handle it just as you normally would with any other getJSON success handler. So hopefully this helps clear up any confusion with using JsonP.  There’s about a thousand suggestions I found on doing cross domain JSON and this was just one of the many. I also wrote a simple JsonWebClient class in .Net that will permit the server-side proxy POST to another domain if anyone is interested. I did that before I learned about CORS (preflight post) or this, JsonP. Comments are welcome!

Sample Code

Additionally, here is a simple, working demo showing how this works. Be sure to start both websites. Then browse the ClientWebsite and click Test JSONP. This will perform a JSONP GET to the second website.

Download the sample

This project is also in the IWS Snippets project.

24 thoughts on “Cross-domain JsonP using Asp.net MVC and jQuery

  1. Nathan,

    when I use JSONP to call my service, the jquery script gets added to the head tag, and the result from the web service gets dumped there as well. It is a correct result but wrapped in XML – as .NET web services always do. I am not using MVC, just plain old asmx, but I call them with the same jquery code as you do. Could you recommend a solution for getting “unwrapped” results?
    Thank you.

  2. @Jimmy
    I haven’t worked with that specifically. In WCF, as part of your operation attributes, you can instruct it to return a “bare” response that’s unwrapped. I’ll have to look up the API on a normal web service to see if it’s similar.

  3. I have started using your code, very helpful thank you! My action is getting invoked twice and all i have is a simple call on the page and nothing else…any thoughts?

  4. @@darylhemeon

    Make sure your $.ajax({ call is somewhere it cannot be invoked more than once. Maybe wrap it with a $(document).ready(function() {...}); or put it in a button click event for testing. It should only make one request. The problem probably lies somewhere client-side. Wish I could help further.

  5. This is fantastic stuff, very clever thinking on the Jsonp passing to MVC. I have managed to get a similar thing working in normal webservices, but will now consider moving to MVC as we are still in early stages of our project.

  6. @Sam Waite Thanks!

    When you really get into Mvc after transitioning from web forms it’s a breath of fresh air. We produce stuff a lot faster now. It also puts you closer to the metal in regards to ajax, html, etc, which I really like.

  7. Hi Nathan, your sample code works functionally but did you notice if you look closely at the response it actually doubles the json data it returns, returning everything twice – here is the actual network output from the sample.

    localJsonpCallback({“Success”:true,”Data”:”you provided key: gfdgfdgfd”,”Message”:null});localJsonpCallback({“Success”:true,”Data”:”you provided key: gfdgfdgfd”,”Message”:null});

    regards

  8. Still had some duplicate code in the example zip today. To keep it more generic, I removed the static class in JsonpResult. The overridden ExecuteResult(ControllerContext context) will still get executed auto-magically when you return a JsonpResult type. In the controller rather than passing the model type to the static function add a new JsonpResult constructor and just return new JsonpResult(resp). The subsequent call to ExecuteResult(…) will fill in the rest. Really good though, thanks for the post Nathan!

  9. Just a reminder that the sample code still contains the unnecessary “result.ExecuteResult(controller.ControllerContext);” code. I just downloaded it yesterday. The GitHub version is good though.

    Great article. I’m using your technique to call an ASP.NET MVC 3 controller action from a static web page on a LAMP server to pull in a record set. Saved me from the dreaded iframe. Thanks a ton.

  10. @Stuart
    Updated! Thanks.

    Yeah I love using MVC as an ajax API host. With MVC4, they introduce a more restful web API. I haven’t taken the time yet; but I’ll soon see what it takes to wire up the MVC4 beta. It still runs on asp.net 4.0 so it should work fine on Mono (one would imagine).

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>