Implement JSONP in your Asp.net Application

In today's web 2.0 world everyone is exposing their application by some kind of services. Some of them even goes further and returning the data in JSON format so that we can consume it directly in browser without using any kind of proxy. There are a lot of popular applications including Yahoo's Flickr, Google Ajax Feed API etc using this technique to deliver the data right into the browser. In this post, I will show you, how to create the same which exposes the Northwind database data. If you just heard this JSONP term, I recommend you to read this page before moving forward.

Let us take a quick example of the client side. Let us assume that we want to show the top 10 customers of the Northwind who submitted the most orders. Here is the code which shows the customers in a bullet list:

<div id="divCustomers"></div>
<script type="text/javascript">
    function onCustomerLoaded(result, methodName)
    {
        var html = '<ul>';

        for(var i = 0; i < result.length; i++)
        {
            html += '<li>' + result[i] + '</li>';
        }

        html += '</ul>';

        document.getElementById('divCustomers').innerHTML = html;
    }
</script>
<script type="text/javascript" 
src="Service.ashx?methodName=topCustomers&jsonp=onCustomerLoaded"></script>

Here the most important thing is the script tag at the end. What we are doing is requesting a generic handler, in this case the Service.ashx and passing the methodName as topCustomers and the callback function. The handler in turn returns the customer names in JSON format in the callback function along with the method name, so the callback function gets automatically executed once it is downloaded in the browser. For example, the following will be returned for the above request:

onCustomerLoaded(["Save-a-lot Markets","Ernst Handel","QUICK-Stop","Folk och fä HB","Hungry Owl All-Night
 Grocers","HILARION-Abastos","Berglunds snabbköp","Rattlesnake Canyon Grocery","Bon app\u0027","Frankenversand"
], 'topCustomers');

Let us now see how the server side is constructed. But before that let me share with you the designing ideas. Instead of having multiple generic handlers or a single handler with lots of switch/if statement we will use a single generic handler with Factory design pattern. The Factory will return a concrete class based upon the passed methodName which will be used to solely handle that request. The methodName and its class will be declared in the web.config file, the Factory will be responsible to read the config file and instantiate the concrete handler. On the other hand the generic handler will request the Factory for the concrete handler and perform some pre/post processing. The following shows the code of the ProcessRequest method of the Service.ashx file:

BaseHandler handler = null;

try
{
    handler = HandlerFactory.CreateHandler(context);

    if (handler != null)
    {
        handler.Execute();

        string output = string.Empty;
        string callbackMethodName = context.Request.Params["jsonp"];

        if (!string.IsNullOrEmpty(callbackMethodName))
        {
            string callback = string.Format(CultureInfo.CurrentCulture, "{0}({1}, '{2}');", callbackMethodName, handler.Output, handler.MethodName);
            output += callback;
        }
        else
        {
            output = string.Format(CultureInfo.CurrentCulture, "var {0} = {1};", handler.MethodName, handler.Output);
        }

        context.Response.ContentType = "application/x-javascript";
        context.Response.Write(output);
    }
    else
    {
        context.Response.StatusCode = 404;
    }
}
finally
{
    if (handler != null)
    {
        handler.Dispose();
    }
}

The generic handler requesting the Factory to return the concrete handler, if found it executes then handler. Then it formats the result of that handler depending upon whether the callback function is specified or not. We will shortly see how to use it without specifying the callback function. At last, it sets the response content type and returns the response. If the concrete handler is not found it throws a 404 error.

Now let us see the Factory code, it reads the configuration file and creates the handler with reflection. Here is the code of the CreateHandler method.

string methodName = context.Request.Params["methodName"];

if (!string.IsNullOrEmpty(methodName))
{
    HandlerMapSection settings = (HandlerMapSection)ConfigurationManager.GetSection(HandlerMapSection.SectionName);

    if (settings != null)
    {
        HandlerMap map = settings.Maps[methodName];

        if (map != null)
        {
            BaseHandler handler = (BaseHandler)_currentAssembly.CreateInstance(map.TypeName,
                                    false, BindingFlags.CreateInstance, null, new object[] { context },
                                    System.Globalization.CultureInfo.CurrentCulture, null);

            return handler;
        }
    }
}

return null;

And the web.config file contains the following lines:

<configSections>
  <section name="handlerMapping" type="HandlerMapSection"/>
</configSections>
<handlerMapping>
  <map methodName="getCustomerList" typeName="CustomerListHandler"/>
  <map methodName="topCustomers" typeName="TopCustomerHandler"/>
</handlerMapping>

If we want to add new methods, we will simply create a new class inherited from BaseHandler. By the way I am using the Asp.net Ajax JavaScriptSerializer for serializing the response. But you can use any other if you want.

I have stated in the above that it is also possible to use it in the client side without passing the callback function. The following shows the code:

<div id="divCustomers"></div>
<script type="text/javascript" src="Service.ashx?methodName=topCustomers"></script>
<script type="text/javascript">
    var html = '<ul>';

    for(var i = 0; i < topCustomers.length; i++)
    {
        html += '<li>' + topCustomers[i] + '</li>';
    }

    html += '</ul>';

    document.getElementById('divCustomers').innerHTML = html;
</script>

As we did not pass the callback function it returns the data as a variable of the method name. For example, for the above, the following is returned:

var topCustomers = ["Save-a-lot Markets","Ernst Handel","QUICK-Stop","Folk och fä HB","Hungry Owl All-Night
 Grocers","HILARION-Abastos","Berglunds snabbköp","Rattlesnake Canyon Grocery","Bon app\u0027","Frankenversand"
];

The most important thing working with the JSONP request is the placing of the JSONP script tag. For example, if we place the JSONP script tag after the codes which shows the bullet list we will get an error that the topCustomers is undefined. The same holds true for the first example, if we place the JSONP tag above the codes, we will get an error that onCustomerLoaded is undefined. The reason behind this is when a browser encounters an external script tag it holds its execution until it downloads it.

So far we have seen the examples where the script tag is already declared in the page. But in real life we might have to add the script tag dynamically based upon the user action. The following shows a simple customer list which adds the script dynamically and shows 10 customer at a time in a bullet list:

<div id="divCustomers"></div>
<input id="btnPrevious" type="button" value="<" disabled="disabled" onclick="movePrevious()" />
<input id="btnNext" type="button" value=">" disabled="disabled" onclick="moveNext()" />
<script type="text/javascript">
    var _rowPerPage = 10;
    var _pageNo = 1;
    var _startRowIndex = 0;

    function movePrevious()
    {
        _pageNo -= 1;
        loadCustomers();
    }

    function moveNext()
    {
        _pageNo += 1;
        loadCustomers();
    }

    function loadCustomers()
    {
        _startRowIndex = ((_pageNo - 1) * _rowPerPage);
        var url = 'Service.ashx?methodName=getCustomerList&start=' + _startRowIndex + '&max=' + _rowPerPage + '&jsonp=onLoaded';

        addScript(url);
    }

    function onLoaded(result, methodName)
    {
        var html = '<ul>';

        for(var i = 0; i < result.Rows.length; i++)
        {
            html += '<li>' + result.Rows[i].Company + '</li>';
        }

        html += '</ul>';
        document.getElementById('divCustomers').innerHTML = html;

        document.getElementById('btnPrevious').disabled = (_startRowIndex < _rowPerPage);
        document.getElementById('btnNext').disabled = ((_startRowIndex + _rowPerPage) >= result.Total);
    }

    function addScript(scriptUrl)
    {
        var script = document.createElement('script');

        script.setAttribute('type','text/javascript');
        script.setAttribute('src', scriptUrl);

        var head = document.getElementsByTagName('head')[0];
        head.appendChild(script);
    }

    loadCustomers();
</script>

You will find the complete code in the bottom of the post.

A note about the Asp.net Ajax: As far as I can remember the earlier version of Asp.net Ajax also has this feature but the final version does not allows this as it requires application/json in the request header which is not possible to add by the script tag. Certainly there is some security concerns and you should not expose your sensitive data with this. You will find more details on Scott Guthrie's this blog post. But the recent version Microsoft Astoria also has the JSONP feature.

Download: Full Source

kick it on DotNetKicks.com

5 Comments

Comments have been disabled for this content.