September 2007 - Posts

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

Jeff Prosise showed us the power of Asynchronous Asp.net Page in developing truly scalable web application in his great article. But I found a lot people finding hard time implementing it in real life based upon those examples. For example, in the data bind example he uses the BeginExcecuteReader/EndExecuteReader method of the DataReader but in real life we do not use it, instead we return some custom entity collection or the DataReader/DataSet directly from the bussiness logic layer. Omar showed me why this approach will not work and even it will add extra overhead comparing the regular model.In this post, I will show you some real life usage of the asynchronous pages and how easily you can convert your regular pages with it. Let us start with a simple example, you want show all the customers of USA of the Northwind database in a GridView. You will come up something similar like the following:

protected void Page_Load(object sender,
    EventArgs e)
{
    if (!IsPostBack)
    {
        LoadCustomers();
    }
}

private void LoadCustomers()
{
    string SQL = string.Format("SELECT * FROM [Customers] WHERE [Country] = '{0}'", "USA");

    using (IDbConnection cnn = new SqlConnection(_connectionString))
    {
        cnn.Open();

        using (IDbCommand cmd = cnn.CreateCommand())
        {
            cmd.CommandText = SQL;
            cmd.CommandType = CommandType.Text;

            using (IDataReader rdr = cmd.ExecuteReader())
            {
                //Bind it with the GridView
                gvCustomers.DataSource = rdr;
                gvCustomers.DataBind();
            }
        }
    }
}

Now let us convert this regular page with the asynchronous model. First add Async="true" in the page directive. Next, create a delegate with the same signature of LoadCustomers method, we will use this delegate for the async operation. The following shows the complete code:

private delegate void LoadData();
private LoadData _loadCustomers;

protected void Page_Load(object sender,
    EventArgs e)
{
    if (!IsPostBack)
    {
        AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginLoadCustomers),
            new EndEventHandler(EndLoadCustomers));
    }
}

private IAsyncResult BeginLoadCustomers(object sender,
    EventArgs e,
    AsyncCallback cb,
    object state)
{
    _loadCustomers = new LoadData(LoadCustomers);
    return _loadCustomers.BeginInvoke(cb, state);
}

private void EndLoadCustomers(IAsyncResult result)
{
    _loadCustomers.EndInvoke(result);
}

private void LoadCustomers()
{
    string SQL = string.Format("SELECT * FROM [Customers] WHERE [Country] = '{0}'", "USA");

    using (IDbConnection cnn = new SqlConnection(_connectionString))
    {
        cnn.Open();

        using (IDbCommand cmd = cnn.CreateCommand())
        {
            cmd.CommandText = SQL;
            cmd.CommandType = CommandType.Text;

            using (IDataReader rdr = cmd.ExecuteReader())
            {
                //Bind it with the GridView
                gvCustomers.DataSource = rdr;
                gvCustomers.DataBind();
            }
        }
    }
}

What we are doing is very simple, we are wrapping up the actual method with a Begin/End Pair and we are passing the pair to AddOnPreRenderCompleteAsync method. In the Begin we are creating an instance of the delegate and calling the invoke which actually fires a thread from the ThreadPool. We can also use the RegisterAsyncTask method instead of AddOnPreRenderCompleteAsync and there some extra advantage of using it instead of AddOnPreRenderCompleteAsync, among the advantages I think the thread impersonate(It is not possible to access the HttpContext.Current from the other threads) and the parallel execution of multiple async method are the most important. Let us consider that you also want to show the employees in another GridView of the Northwind database along with the customers which country is set to USA, since there is no dependency between these operations we can use two separate thread to for each operation. The following shows the complete code:

private delegate void LoadData();
private LoadData _loadCustomers;
private LoadData _loadEmployees;

protected void Page_Load(object sender,
    EventArgs e)
{
    if (!IsPostBack)
    {
        RegisterAsyncTask(
            new PageAsyncTask(
            new BeginEventHandler(BeginLoadCustomers),
            new EndEventHandler(EndLoadCustomers), null, null, true)
            );

        RegisterAsyncTask(
            new PageAsyncTask(
            new BeginEventHandler(BeginLoadEmployees),
            new EndEventHandler(EndLoadEmployees), null, null, true)
            );
    }
}

private IAsyncResult BeginLoadCustomers(object sender,
    EventArgs e,
    AsyncCallback cb,
    object state)
{
    _loadCustomers = new LoadData(LoadCustomers);
    return _loadCustomers.BeginInvoke(cb, state);
}

private void EndLoadCustomers(IAsyncResult result)
{
    _loadCustomers.EndInvoke(result);
}

private IAsyncResult BeginLoadEmployees(object sender,
    EventArgs e,
    AsyncCallback cb,
    object state)
{
    _loadEmployees = new LoadData(LoadEmployees);
    return _loadEmployees.BeginInvoke(cb, state);
}

private void EndLoadEmployees(IAsyncResult result)
{
    _loadEmployees.EndInvoke(result);
}

private void LoadCustomers()
{
    string SQL = string.Format("SELECT * FROM [Customers] WHERE [Country] = '{0}'", "USA");

    using (IDbConnection cnn = new SqlConnection(_connectionString))
    {
        cnn.Open();

        using (IDbCommand cmd = cnn.CreateCommand())
        {
            cmd.CommandText = SQL;
            cmd.CommandType = CommandType.Text;

            using (IDataReader rdr = cmd.ExecuteReader())
            {
                //Bind it with the GridView
                gvCustomers.DataSource = rdr;
                gvCustomers.DataBind();
            }
        }
    }
}

private void LoadEmployees()
{
    string SQL = string.Format("SELECT * FROM [Employees] WHERE [Country] = '{0}'", "USA");

    using (IDbConnection cnn = new SqlConnection(_connectionString))
    {
        cnn.Open();

        using (IDbCommand cmd = cnn.CreateCommand())
        {
            cmd.CommandText = SQL;
            cmd.CommandType = CommandType.Text;

            using (IDataReader rdr = cmd.ExecuteReader())
            {
                //Bind it with the GridView
                gvEmployees.DataSource = rdr;
                gvEmployees.DataBind();
            }
        }
    }
}

The code is almost same as the previous one except we have added another set of methods for the employees and we are using the RegisterAsyncTask method instead of AddOnPreRenderCompleteAsync in page load event. Here the most important thing in the RegisterAsyncTask method is the last parameter where we are passing true, this ensures that both the method will be executed simultaneously. If we used AddOnPreRenderCompleteAsync method or do not pass true in the last parameter of the RegisterAsyncTask method then it will first execute the customer part, once complete it start the employee part.

In the last and final section, we will see an Master/Detail example of Async Page. We will have a DropDownList which will be populated with all the categories of Northwind database and based upon the selected category we will load the products in a GridView. Since the product loading depends upon the selected category& thus it is not possible to do the parallel execution of both method, instead the product loading needs to wait until the category loads first. The following shows the compete code:

private delegate void LoadData();
private LoadData _loadCategories;
private LoadData _loadProducts;

protected void Page_Load(object sender,
    EventArgs e)
{
    if (!IsPostBack)
    {
        RegisterAsyncTask(
            new PageAsyncTask(
            new BeginEventHandler(BeginLoadCategories),
            new EndEventHandler(EndLoadCategories), null, null)
            );

        RegisterAsyncTask(
            new PageAsyncTask(
            new BeginEventHandler(BeginLoadProducts),
            new EndEventHandler(EndLoadProducts), null, null)
            );
    }
}

protected void Category_Changed(object sender,
    EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(
        new BeginEventHandler(BeginLoadProducts),
        new EndEventHandler(EndLoadProducts), null, null)
        );
}

private IAsyncResult BeginLoadCategories(object sender,
    EventArgs e,
    AsyncCallback cb,
    object state)
{
    _loadCategories = new LoadData(LoadCategories);
    return _loadCategories.BeginInvoke(cb, state);
}

private void EndLoadCategories(IAsyncResult result)
{
    _loadCategories.EndInvoke(result);
}

private IAsyncResult BeginLoadProducts(object sender,
    EventArgs e,
    AsyncCallback cb,
    object state)
{
    _loadProducts = new LoadData(LoadProducts);
    return _loadProducts.BeginInvoke(cb, state);
}

private void EndLoadProducts(IAsyncResult result)
{
    _loadProducts.EndInvoke(result);
}

private void LoadCategories()
{
    const string SQL = "SELECT [CategoryID], [CategoryName] FROM [Categories]";

    using (IDbConnection cnn = new SqlConnection(_connectionString))
    {
        cnn.Open();

        using (IDbCommand cmd = cnn.CreateCommand())
        {
            cmd.CommandText = SQL;
            cmd.CommandType = CommandType.Text;

            using (IDataReader rdr = cmd.ExecuteReader())
            {
                ddlCategory.DataSource = rdr;
                ddlCategory.DataBind();
            }
        }
    }
}

private void LoadProducts()
{
    string SQL = string.Format("SELECT * FROM [Products] WHERE [CategoryID] = {0}",
        ddlCategory.SelectedItem.Value);

    using (IDbConnection cnn = new SqlConnection(_connectionString))
    {
        cnn.Open();

        using (IDbCommand cmd = cnn.CreateCommand())
        {
            cmd.CommandText = SQL;
            cmd.CommandType = CommandType.Text;

            using (IDataReader rdr = cmd.ExecuteReader())
            {
                gvProducts.DataSource = rdr;
                gvProducts.DataBind();
            }
        }
    }
}

As you can see that we are not passing true in the last argument of RegisterAsyncTask method, it ensures that the category will load first and then the products. We are also registering another async task in the selected index change event of the DropDownList.

I hope this will clear your confusion with async pages and you will be able to implement easily it in your projects.

Download: Full Source

kick it on DotNetKicks.com

Today, I found another interesting post in Asp.net Ajax Web Service Forum. How do you ensure that your web service is called from your aspx page. The goal is to protect your web service from unauthorized usage. Certainly, you can clear the protocols section of the web.config but it will not make any impact calling the ajax enabled web service. You will still be able to call the web method with the following code:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("YOUR URL");

request.ContentType = "application/json; charset=utf-8";
request.Method = "POST";

//Assuming the HTTP Post does not require any form fields
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

Checking the http referer in the web method will not also help as it can be easily set too. So what is the way to ensure that it is not called from any other place except the aspx pages. Truly speaking, there is no full proof way you can guarantee but you can add some complexity, so it gets a bit difficult comparing the above. Lets say in the aspx page which is used to call the web service we add the following code:

private void GenerateSecurityTicket()
{
    string cacheKey = User.Identity.Name + ":securityTicket";
    string securityTicket = Guid.NewGuid().ToString();

    Cache[cacheKey] = securityTicket;

    string script = string.Format("SECURITY_TICKET = '{0}';", securityTicket);

    ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "securityKey", script, true);
}

protected void Page_Load(object sender, EventArgs e)
{
    GenerateSecurityTicket();
}

What it does is, every time the page is rendered it creates a new Guid, puts it in the cache and embedded it as a JavaScript global variable. Next, in the web service we add the following code:

[WebMethod]
public string SecureMethod()
{
    EnsureTicket();

    return "This is a valid call.";
}

private void EnsureTicket()
{
    HttpContext context = HttpContext.Current;

    string headerTicket = context.Request.Headers["securityTicket"];

    if (string.IsNullOrEmpty(headerTicket))
    {
        throw new SecurityException("Security ticket must be present.");
    }

    string cacheKey = context.User.Identity.Name + ":securityTicket";
    string cacheTicket = (string)context.Cache[cacheKey];

    if (string.Compare(headerTicket, cacheTicket, false) != 0)
    {
        throw new SecurityException("Security ticket mismatched.");
    }
}

So what we are doing is checking if the http request has any key and if it matches with the key stored in the cache. If the key is not present or does not match with the stored key we are simply raising a security exception.

Next, when we are invoking the web method we have to make sure that the required header is added. But unfortunately there is no way to add the header directly in the WebServiceProxy class. Instead, we have to hook the WebRequestManager invokingRequest event to add the header. This is a special event which is fired for all kinds of ajax operation including the Update Panel partial update. The following shows the code:

function pageLoad(sender, args)
{
    Sys.Net.WebRequestManager.add_invokingRequest(onInvoke);
}

function pageUnload(sender, args)
{
    Sys.Net.WebRequestManager.remove_invokingRequest(onInvoke);
}

function onInvoke(sender, args)
{
    args.get_webRequest().get_headers()['securityTicket'] = SECURITY_TICKET;
}

function invokeSecureMethod()
{
    SecureService.SecureMethod(
                                    function(result)
                                    {
                                        alert(result);
                                    },
                                    function(e)
                                    {
                                        alert(e.get_message());
                                    }
                              );
}

Now anybody wants to call the web method needs to get the key from page, we can even add more complexity by encapsulating the key generation with a JavaScript function rather than stroing it plain variable. Again it is not proper way to ensure unauthorized usage but does any body has a better idea?

Download: Full Source

kick it on DotNetKicks.com

I am extremely sorry to my blog visitors that the source codes as well as the screenshots are chopped off with this half screen layout.  Let us pray that the WebLog support team will add some full page theme asap.

In August Ajax column of DotNetSlackers, I have shown an effective exception logging system. In that column, I concluded saying that a lot of enhancement can be done with it which I finally did and created a project in CodePlex - Asp.net Ajax Exception Logging. Here is the list of enhancements:

  • A new Control is introduced - ExceptionManager which is used to register the listeners. No need to manually register those.
  • Automatic Logging of Unhandled and Update Panel Exceptions not need to hook those events manually. Just turn on the HandleUnhandledException and HandleUpdatePanelException which is by default turned on.
  • Encapsulating listeners in its own server side class.
  • An Enhanced version of web service listener which includes dynamic location of web service path/method name and batch submitting of exceptions.
  • A built-in web service to log exceptions - ExceptionLogService.
  • Introducing provider model in server side, so you can roll your provider to log these exceptions.
  • Built-in provider which logs in Asp.net HealthMonitoring/WebEvents.

Let me give a quick tour.

Registering Listeners

Earlier you have to register the listeners like the following:

function applicationLoad(sender, args)
{
    var mgr = Ajax.Logging.ExceptionManager.getInstance();

    mgr.addListener(new Ajax.Logging.WebServiceListener());
    mgr.addListener(new Ajax.Logging.PanelListener($get('pnlExceptions')));
    mgr.addListener(new Ajax.Logging.SysDebugListener());
    mgr.addListener(new Ajax.Logging.AlertListener());
}

Now you will do it in the server side:

<AjaxLogging:ExceptionManager ID="TheExceptionManager" runat="server">
    <Listeners>
        <AjaxLogging:WebServiceListener ServicePath="~/ExceptionLogService.asmx" ServiceMethod="Log" />
        <AjaxLogging:PanelListener Panel="pnlException" />
        <AjaxLogging:SysDebugListener />
        <AjaxLogging:AlertListener />
    </Listeners>
</AjaxLogging:ExceptionManager>

It will automatically inject the earlier JavaScript codes in the client side.

Handling Unhandled and Update Panel Exception

Previously you have to manually hook those events to log exceptions, now it will automatically log those exceptions, just ensure you did not turned off the HandleUnhandledException and HandleUpdatePanelException. You can also set the exception code from the server side .The Control will automatically inject the following code:

window.onerror = function(message, url, lineNumber)
{
    var e = Error.create(message, {description:message, name:'UnhandledError', lineNumber:lineNumber, url:url});
    Ajax.Logging.ExceptionManager.getInstance().publishException(105000, e);
    return true;
}

Sys.WebForms.PageRequestManager.getInstance().add_endRequest(AjaxLoggingHandleUpdatePanelError)
function AjaxLoggingHandleUpdatePanelError(sender, args)
{
    var e = args.get_error();
    if (e != null)
    {
        Ajax.Logging.ExceptionManager.getInstance().publishException(105001, e);
        args.set_errorHandled(true);
    }
}
Encapsulating Listener in Server Side Class

Now each of the listener is encapsulated in its own server side class. You can also roll your own listener from Inheriting the BaseListener class. For example, the following shows the code of AlertListener:

public class AlertListener : BaseListener
{
    [Browsable(false)]
    public override string CreateScript
    {
        [DebuggerStepThrough()]
        get
        {
            string script = string.Format(System.Globalization.CultureInfo.CurrentCulture, "Ajax.Logging.ExceptionManager.getInstance().addListener(new {0}());", this.GetType().FullName);

            return script;
        }
    }

    [Browsable(false)]
    public override IEnumerable<ScriptReference> ScriptReferences
    {
        [DebuggerStepThrough()]
        get
        {
            return new ScriptReference[] { new ScriptReference(Owner.Page.ClientScript.GetWebResourceUrl(this.GetType(), "Ajax.Logging.Scripts.AlertListener.js")) };
        }
    }
}

You have to override the CreateScript and ScriptReferences property which will contain the listener specific code. The client side rule to create listener remains the same.

Enhanced Web Service Listener

In previous version the exceptions were forwarded to a static web service and the web service was called each time the publishException is called, now it supports dynamic web service url as well as method name and it also queue the exceptions in the client side and submits as batch, the default interval is 10 seconds which you can change from the LogInterval property.

Built-in Web Service

Now it has a pre-built web service which you can use to log exceptions. Just add an asmx file and add the following line:

<%@ WebService Language="c#" Class="Ajax.Logging.ExceptionLogService, Ajax.Logging" %>

And in the web service listener set the path and the method name like the following:

<AjaxLogging:WebServiceListener ServicePath="~/ExceptionLogService.asmx" ServiceMethod="Log"  />
Provider Model

To record the exception in server side it uses the Asp.net Provider Model. To create a new provider you have create a class which inherits from ExceptionLogProvider and overriding the Log method.

Built-in Health Monitoring Provider

Now it comes with a Built-in Provider which uses the Asp.net health monitoring system to log the exceptions. It raises a custom event WebClientExceptionEvent to record the exception. Certainly the required settings for health monitoring needs to be present in the configuration file. The following shows a logged exception in the Event Log.

Event Log

You can download the full source and a working sample from codeplex.

A small note about Asp.net Future

The upcoming release of Asp.net known as Asp.net Future also has the support to log client side exceptions. But unfortunately it does not allow to log any other exceptions except the window.onerror, also it is not as much extensible as the above, no support for listeners, not automatic logging etc. You will find the details in this link.

kick it on DotNetKicks.com

So I got a new Blog. Like any other Asp.net developer, I also had the dream to have a blog in Asp.net and when I saw Joe Stagner calling all Asp.net experts to start blogging with Scott Guthrie I jumped right away to filling up the form. Now few things that are really bothering me:

  • Why it does not have any full screen theme, usually my post contains a fair amount of code and I am afraid that it won't fit in this half screen layout.

  • Could not find a way to import my older posts from my previous blog. Thanks that it supports feedburner at least my feed reader will not be bothered(?)

  • How can I disable the advertisement which shows in an awkward place, at least grant me the privilege to show it in an different place. Certainly, my previous blog has the advertisement but it was shown in much more controlled way so the initial focus of the reader was right on my post rather than the Ad.

My new article has been just published for September Ajax Column in DotNetSlackers.com. In this article, I have answered few common issues which I found often discussed in Asp.net Ajax Web Service Forum. In this column, I have covered:

  • Web Service Method, succeededCallback and failedCallback methods signature.
  • Complex data type interchange which includes Array, Dictionary, Custom Class.
    • GenerateScriptType Attribute
    • ScriptIgnore Attribute
    • Custom JavaScriptConverter class
  • Progress Indicator for long running task.
  • Soap Header/Custom Http Header

Check it out.

kick it on DotNetKicks.com

In this post, I will show you how to compress the Asp.net Ajax Web Service response, To understand the benefits of compression let us start with a simple example, Consider you have an web service which returns a large data like the following:

[WebMethod()]
public string GetLargeData()
{
    using (StreamReader sr = File.OpenText(Server.MapPath("~/DataFile.txt")))
    {
        return sr.ReadToEnd();
    }
}

The web method reads an large text file (around 100KB) and returns it contents. Once we call this method from a page the network activity in the firebug shows like the following:

Plain

Now, lets examine the HttpModule which compress the Ajax Web Service response. The following shows the complete code of this module:

using System;
using System.IO;
using System.IO.Compression;
using System.Globalization;
using System.Web;


public class JsonCompressionModule : IHttpModule
{
    public JsonCompressionModule()
    {
    }

    public void Dispose()
    {
    }

    public void Init(HttpApplication app)
    {
        app.PreRequestHandlerExecute += new EventHandler(Compress);
    }

    private void Compress(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;
        HttpRequest request = app.Request;
        HttpResponse response = app.Response;

        //Ajax Web Service request is always starts with application/json
        if (request.ContentType.ToLower(CultureInfo.InvariantCulture).StartsWith("application/json"))
        {
            //User may be using an older version of IE which does not support compression, so skip those
            if (!((request.Browser.IsBrowser("IE")) && (request.Browser.MajorVersion <= 6)))
            {
                string acceptEncoding = request.Headers["Accept-Encoding"];

                if (!string.IsNullOrEmpty(acceptEncoding))
                {
                    acceptEncoding = acceptEncoding.ToLower(CultureInfo.InvariantCulture);

                    if (acceptEncoding.Contains("gzip"))
                    {
                        response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
                        response.AddHeader("Content-encoding", "gzip");
                    }
                    else if (acceptEncoding.Contains("deflate"))
                    {
                        response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
                        response.AddHeader("Content-encoding", "deflate");
                    }
                }
            }
        }
    }
}

Next, register this module in the web.config like the following:

<httpModules>
    <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="JsonCompressionModule" type="JsonCompressionModule"/>
</httpModules>

This time the network activity shows like the following:

Compressed Json

So by adding this little module, we have saved 74KB. Now consider the impact of this in an highly traffic ajax web application :-). You will find the complete source code in the bottom of this post. If you want to learn more optimization tips check out my previous post Implement Yahoo's YSlow in your Asp.net pages and Combine Multiple JavaScript and CSS Files and Remove Overheads.

Download: Full Source

kick it on DotNetKicks.com

In my previous post, I have shown the data binding capabilities of ListView control. In this post, I will show the Data Editing power of ListView. Like the previous DataGrid, DataList and GridView controls the ListView control has the EditItemTemplate and it also comes up with the long awaited InsertItemTemplate, in the past there was a lot of attempt to allow the users to insert data through Header or Footer Template which is no longer required. In this post I will show you the full data editing capabilities of ListView which includes Insert, Edit and Delete with both LinqDataSource and code. We will follow the previous post example for the data editing.

The first thing you have to do to add the data editing capabilities is defining the Edit item and Insert item templates. Instead of row kind of style editing like the DataGrid and GridView we will follow the form style editing, this is the another powerful features of ListView that you have the full control on how the html is generated. The following shows the screen-shots of both EditItemTemplate and InsertItemTemplate view:

EditItemTemplate

InsertItemTemplate

The following shows the EditItem and InsertItem Template:

<EditItemTemplate>
    <tr>
        <td colspan="9">
            <table>
                <tbody>
                    <tr>
                        <th>
                            Company:
                        </th>
                        <td>
                            <asp:TextBox ID="CompanyNameTextBox" runat="server" Text='<% #Bind("CompanyName")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CompanyValidator" runat="server" ControlToValidate="CompanyNameTextBox" ErrorMessage="<div>Company cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Contact:
                        </th>
                        <td>
                            <asp:TextBox ID="ContactNameTextBox" runat="server" Text='<% #Bind("ContactName")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="ContactValidator" runat="server" ControlToValidate="ContactNameTextBox" ErrorMessage="<div>Contact cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Title:
                        </th>
                        <td>
                            <asp:TextBox ID="ContactTitleTextBox" runat="server" Text='<% #Bind("ContactTitle")%>'></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Address:
                        </th>
                        <td>
                            <asp:TextBox ID="AddressTextBox" runat="server" TextMode="MultiLine" Columns="20" Rows="4" Text='<% #Bind("Address")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="AddressValidator" runat="server" ControlToValidate="AddressTextBox" ErrorMessage="<div>Address cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            City:
                        </th>
                        <td>
                            <asp:TextBox ID="CityTextBox" runat="server" Text='<% #Bind("City")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CityValidator" runat="server" ControlToValidate="CityTextBox" ErrorMessage="<div>City cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Postal Code:
                        </th>
                        <td>
                            <asp:TextBox ID="PostalCodeTextBox" runat="server" Text='<% #Bind("PostalCode")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="PostalCodeValidator" runat="server" ControlToValidate="PostalCodeTextBox" ErrorMessage="<div>Postal code cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Country:
                        </th>
                        <td>
                            <asp:TextBox ID="CountryTextBox" runat="server" Text='<% #Bind("Country")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CountryValidator" runat="server" ControlToValidate="CountryTextBox" ErrorMessage="<div>Country cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Phone:
                        </th>
                        <td>
                            <asp:TextBox