Welcome to the Dart White Paper repository. We have created this section to publish White Papers about our technology, ideas for IT solutions implementing TCP and UDP based communications, tips for using our products, and opinions about current and coming technologies. Along with these articles, readers will have the opportunity to respond with their own ideas and we will publish those comments along with the articles. If you have a comment on any article, please submit it to comments@dart.com.

Building Rich(er) Internet Applications Using ASP.NET

This spring at ASP.NET Connections one attendee told me he “hates the flash” as portions of the typical web page are updated. By that he meant even mildly sophisticated Web Apps provide a crude user experience as modified pages reload and draw. Web Apps should be able to update selected page elements. It seemed to him that there should be a practical, cost-effective way to create Rich Internet Applications (RIAs).

The reload delay annoys us too, so I’m writing this paper to explain how you can use lightweight asynchronous client callbacks (that raise code-behind events without re-rendering the page) to achieve a higher level of rich browser interaction. But first, we should review the existing techniques being used, and why they fail as a general technique for most applications.

Existing RIA Programming Techniques

The obstacle to a better browser experience is the synchronous request/response mechanism that reloads and draws the page when any page element needs to be changed. Several techniques have made their way into the web developer’s toolbox to mitigate this limitation:

  • Frame technology for selectively updating frames, which was hindered by poor cross-browser support
  • Flash applications for advanced interactive graphics, which requires Flash programming expertise and expense
  • Java Applets for advanced client-side logic, which requires Java expertise, downloading of code, and browser support (the Java Virtual Machine must be installed on the client and this is no longer included in Windows by default)
  • DHTML programming for pre-loaded client-side logic, which requires HTML and JavaScript expertise
  • Plug-ins and COM objects that are downloaded and used in the browser for special purposes, but have poor cross-browser support as well as security issues
  • SmartNavigation, introduced in .NET version 1, which requires the use of IE

These techniques, although useful for many applications, may fall short in programming ease, maintainability, reliability, cross-browser support, security, expense, or access to server-side computational power. They are all useful, but not broadly. What the industry needs is easy-to-use tools for creating RIAs.

The web development community has been discussing the use of callbacks to reduce page loads and to provide the user with a richer experience. Many readers have already seen the callback technique in action on the MSDN site, where the TOC is dynamically grown with new nodes as the user drills into the documentation. This technique is also used in Google Gmail and Google Suggest where callbacks are used to dynamically generate UI data on the server and sent back to the browser to update selected elements. A recent article, "Ajax: A New Approach to Web Applications" by Jesse Garrett (www.adaptivepath.com/publications/essays/archives/000385.php), discusses callbacks and suggests it "represents a fundamental shift in what’s possible on the Web" (Ajax stands for Asynchronous JavaScript and XML). Also, Dart Communications recently released a suite of Web Controls, PowerWEB LiveControls for ASP.NET, that uses callbacks to achieve similar effects.

Microsoft has recognized the utility of callbacks and is improving support with new APIs in version 2 of the .NET Framework. It won’t be long until your customers will be asking you about RIAs, so you might want to get started now!

Callback Technology Unveiled

Typical Web Apps use an HTTP request to raise code-behind events where results are computed and used to update the browser with new information. The page is rendered and then consumed by the browser where the elements are drawn. Bandwidth might not be a problem (although it is for many extranets), but the drawing process always introduces a noticeable delay. Like my potential customer said, he "hates the flash".

We need 2 things to get around the flash: a callback that does not cause the page to render, and a way to tell the browser to update text, graphics, or other attribute.

How do I create a callback? You can use the Whidbey Client Script Manager or you can use a client-side object such as XmlHttpRequest. On the server you can use Page.Response.End() in the OnPreRender() event to terminate the request so the page is not rendered.

How do I tell the browser to update an element? One way would be to use XML to format your values, and then interpret that using an installed handler. Another way is to send JavaScript to the browser and execute it. Since you are writing both sides of the "protocol", you have the flexibility to communicate any way you wish.

We’ve created a sample application that uses Whidbey’s Client Script Manager to perform a simple dynamic update on a database table. Figure 1 illustrates the application.

Figure 2 shows the coding involved. This was an 8 hour project for a very experienced JavaScript and ASP.NET developer already familiar with the Client Script Manager. As you can see, most of the work involves getting the browser to display the data.

<%@ Page Language="C#" CompileWith="microsoft.aspx.cs" ClassName="microsoft_aspx" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Microsoft ASP.NET 2.0 Callback Demo</title>

    <link rel="stylesheet" href="StyleSheet.css" />

</head>

<body>

    <form id="form1" runat="server">

        <div align="center">

            <h3>

                WeatherWatch Demo (ASP.NET 2.0 Client Callback)</h3>

            <p>

                Welcome to the WeatherWatch demo, written using Microsoft ASP.NET 2.0 Client Callbacks.

                Simply enter a zipcode to search for a city, then add it to the streaming weather

                grid.</p>

            &nbsp;&nbsp;&nbsp;

            <asp:TextBox ID="txtZip" CssClass="textbox" Runat="server"></asp:TextBox>

            <asp:Button CssClass="button" ID="btnLookup" Runat="server" Text="Lookup Zipcode"

                OnClientClick="doBtnLookup(); return false;" /><br />

            <asp:Label ID="lblResults" Font-Bold="true" Runat="server"></asp:Label>&nbsp;&nbsp;&nbsp;

            <asp:LinkButton Style="visibility: hidden" ID="btnAdd" Runat="server" Text="Add this city to streaming grid?"

                OnClientClick="doBtnAdd(); return false;">

            </asp:LinkButton>

            <br />

            <br />

            <asp:DataGrid ID="grdCities" Runat="server" AutoGenerateColumns="false">

                <HeaderStyle ForeColor="White" Font-Bold="True" BackColor="SteelBlue"></HeaderStyle>

                <Columns>

                    <asp:BoundColumn DataField="City" HeaderText="City name" ItemStyle-Width="200px"></asp:BoundColumn>

                    <asp:BoundColumn DataFormatString="{0:F2} &deg;F" HeaderText="Temperature" DataField="Temp"

                        ItemStyle-Width="100px" ItemStyle-HorizontalAlign="Center">

                    </asp:BoundColumn>

                    <asp:BoundColumn DataFormatString="{0:F2} in" HeaderText="Pressure" DataField="Pressure"

                        ItemStyle-Width="100px" ItemStyle-HorizontalAlign="center">

                    </asp:BoundColumn>

                    <asp:BoundColumn DataFormatString="{0:F2} %" HeaderText="Humidity" DataField="Humidity"

                        ItemStyle-Width="100px" ItemStyle-HorizontalAlign="Center">

                    </asp:BoundColumn>

                    <asp:BoundColumn DataField="Pic"></asp:BoundColumn>

                </Columns>

                <ItemStyle BackColor="LightSkyBlue"></ItemStyle>

            </asp:DataGrid>

        </div>

        <script language="javascript">

    var timerInterval = 1000;

   

    function clientCallbackError(result, context){

      alert("Error!!! " + result);

    }

   

    function handleCallback(result, context){

        eval(context + "(result)");

    }

   

    function doBtnLookup(){

        var zipCode = document.getElementById("txtZip").value;

        doCallback('BtnLookup_Click&' + zipCode, 'handleBtnLookup');

    }

   

    function handleBtnLookup(result){

       document.getElementById("lblResults").innerHTML = result;

       document.getElementById("btnAdd").style.visibility = "visible";

    }

   

    function doBtnAdd(){

        var city = document.getElementById("lblResults").innerHTML;

        doCallback('BtnAdd_Click&' + city, 'handleBtnAdd');

    }

   

    function handleBtnAdd(result){

        alert("btnadd " + result);

    }

   

    function doTmrUpdate(){

        doCallback('TmrUpdate_Tick', 'handleTmrUpdate');

        window.setTimeout(doTmrUpdate, timerInterval);

    }

   

    function handleTmrUpdate(result){

        /* "result" contains a serialized response (rows delimited by "@" and cells delimited by "|").

         * We need to deserialize and update existing values. If there are any values left over, that

         * means a new row was added on the server, so we have to do the same on the client.

         */

         // Get the table instance.

         var table = document.getElementById("grdCities");       

         // Deserialize the rows.

         var rows = result.split('@');

         for(var i=0; i<table.rows.length-1; i++){

            // Deserialize the cells

            var cells = rows[i].split('|');

            for(var j=0; j<cells.length; j++){

                // Update the table

                // the "+1" is because we do not need to update the header in the tick.

                table.rows[i+1].cells[j].innerHTML = cells[j];

            }

         }       

         // Check for new rows

         if(table.rows.length - 1 < rows.length){

            // There is a new row. Add it.

            var newRow = table.insertRow();

            newRow.style.backgroundColor = 'LightSkyBlue';

            var newCellDataArr = rows[rows.length-1].split('|');

            for(var i=0; i<newCellDataArr.length; i++){            

                var newCell = newRow.insertCell();

                newCell.innerHTML = newCellDataArr[i];

            }

         }

    }

   

    function startTimer(){

        window.setTimeout(doTmrUpdate, timerInterval);

    }

    </script>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

using System.Reflection;

 

public partial class microsoft_aspx:ICallbackEventHandler

{

    private ZipcodeInfo zipInfo = null;

    private WeatherInfo weatherInfo = null;

 

    void Page_Load(object sender, EventArgs e)

    {

        // Recreate data objects on each request

        weatherInfo = new WeatherInfo(this.Session);

        zipInfo = new ZipcodeInfo();

 

        if (!Page.IsPostBack)

        {

            // Reinitialize the session.

            Session.Clear();

            // Add some startup cities.

            weatherInfo.WeatherEntries.Add(new Weather(this.Session, "New York"));

            weatherInfo.WeatherEntries.Add(new Weather(this.Session, "Boston"));

            weatherInfo.WeatherEntries.Add(new Weather(this.Session, "Los Angeles"));

            DoBind();

            // Create callback calls and callback functions.

            string clientCallback = Page.GetCallbackEventReference(this,

                                                                         "arg",

                                                                         "handleCallback",

                                                                         "context",

                                                                         "clientCallbackError");

            String btnLookupFunction = "function doCallback(arg, context){ " + clientCallback + "; }";

            Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "btnlookup", btnLookupFunction, true);

            // Start the "timer"

            Page.ClientScript.RegisterStartupScript(this.GetType(), "timerStart", "startTimer()", true);

        }

    }

 

    private void DoBind()

    {

        // Rebind the grid (changes will automatically be reflected on the client)

        grdCities.DataSource = weatherInfo.GetData();

        grdCities.DataBind();

    }

 

    public string RaiseCallbackEvent(string eventArg)

    {

        // First split args

        string[] parts = eventArg.Split('&');

        // First part is the server-side function to call, second part is the arg to pass

        // to the function...call using Reflection

        object[] args = null;

        if (parts.Length > 1)

        {

            args = new object[1];

            args[0] = parts[1];

       }

        return (string)this.GetType().GetMethod(parts[0]).Invoke(this, args);

    }

 

    public string BtnLookup_Click(string eventArg)

    {

        // Lookup button clicked. Resolve zipcode to city name

        return zipInfo.GetCity(eventArg);

    }

 

    public string BtnAdd_Click(string eventArg)

    {

        // Add button clicked. Add the current city to the streaming grid.

        weatherInfo.WeatherEntries.Add(new Weather(Session, eventArg));

        DataView newData = weatherInfo.GetData();

        return SerializeTableData(newData);

    }

 

    public string TmrUpdate_Tick()

    {

        // Timer tick. Recalc the current conditions and get the new data

        weatherInfo.Recalculate();

        DataView newData = weatherInfo.GetData();

        return SerializeTableData(newData);

    }

 

    private string SerializeTableData(DataView tableData)

    {

        // Serialize the data for our response. We have to dynamically rebuild the table contents each click.

        // "@" --> Seperates rows

        // "|" --> Seperates cells

        string response = "";

 

        foreach (DataRow row in tableData.Table.Rows)

        {

            for (int i = 0; i < row.ItemArray.Length; i++)

            {

                string val = "";

                // Get value...formatting appropriately

                switch (i)

                {

                    case 0:

                        val = row.ItemArray[i].ToString();

                        break;

                    case 1:

                        val = string.Format("{0:F2} &deg;F", row.ItemArray[i]);

                        break;

                    case 2:

                        val = string.Format("{0:F2} in", row.ItemArray[i]);

                        break;

                    case 3:

                        val = string.Format("{0:F2} %", row.ItemArray[i]);

                        break;

                    case 4:

                        val = row.ItemArray[i].ToString();

                        break;

                }

                // Delimit the cell.

                response += val + "|";

            }

            // parse off the last "|"

            if (response.Length > 1) response = response.Substring(0, response.Length - 1);

            // delimit the row.

            response += "@";

        }

        //parse off the last "&" and return data

        if (response.Length > 1) response = response.Substring(0, response.Length - 1);

        return response;

    }

}

Figure 2. WeatherWatch code using Whidbey Client Callback Manager.

Ajax and PowerWEB LiveControls

Ajax, the synergistic combination of callbacks with DHTML, describes the general technique of combining callbacks with JavaScript. LiveControls, however, differentiates itself from other Ajax implementations in the following ways:

  • Other Ajax implementations require the developer to author JavaScript that calls into the Ajax scripting library. LiveControls dynamically generates JavaScript on the developer’s behalf.
  • Other Ajax implementations require an engine to be downloaded, whereas LiveControls is light-weight and only downloads a few required handlers.
  • Other Ajax implementations are “client-centric” with most of the code preloaded on the client, whereas LiveControls is “server-centric” with code downloaded on demand. Because of this, LiveControls provides a superior debugging and maintenance environment for the developer.

The Client Script Manager, of course, simply provides a callback mechanism where a string is returned to the client for interpretation. Ajax layers on additional functionality that the developer needs for writing RIAs.

Figure 3 shows how LiveControls was used to re-write the same WeatherWatch application shown in figure 2. This was a 45 minute project for the same experienced JavaScript and ASP.NET developer. As you can see, no JavaScript is present because LiveControls render that at run-time.

<%@ Page Language="C#" CompileWith="powerweb.aspx.cs" ClassName="PowerWEB_aspx" %>

<%@ Register TagPrefix="cc1" Namespace="Dart.PowerWEB.LiveControls" Assembly="Dart.PowerWEB.LiveControls" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>PowerWEB LiveControls for ASP.NET Demo</title>

    <link rel="stylesheet" href="StyleSheet.css" />

</head>

<body>

    <form id="form1" runat="server">

    <div align="center">

        <h3>WeatherWatch Demo (PowerWEB LiveControls)</h3>

        <p>Welcome to the WeatherWatch demo, written using PowerWEB LiveControls. Simply enter a zipcode to search for a city, then add it to the streaming weather grid.</p>

        &nbsp;&nbsp;&nbsp;

        <cc1:LiveTextBox DataPersistenceType=Session ID="txtZip" CssClass="textbox" Runat="server" ScriptsDirectory="scripts" AutoCompleteType="Disabled"></cc1:LiveTextBox>

        <cc1:LiveButton DataPersistenceType=Session CssClass="button" ID="btnLookup" Runat="server" ScriptsDirectory="scripts" OnClick="btnLookup_Click" Text="Lookup Zipcode" /><br />

        <cc1:LiveLabel DataPersistenceType=Session ID="lblResults" Font-Bold="true" Runat="server" ScriptsDirectory="scripts"></cc1:LiveLabel>&nbsp;&nbsp;&nbsp;<cc1:LiveLinkButton ID="btnAdd" Runat="server" Visible="false" Text="Add this city to streaming grid?" OnClick="btnAdd_Click"></cc1:LiveLinkButton>

        <br /><br />

        <cc1:LiveDataGrid DataPersistenceType=Session ID="grdCities" Runat="server" AutoGenerateColumns="false">

            <HeaderStyle ForeColor="White" Font-Bold="True" BackColor="SteelBlue"></HeaderStyle>

            <Columns>

                <cc1:LiveBoundColumn DataField="City" HeaderText="City name" ItemStyle-Width="200px"></cc1:LiveBoundColumn>

                <cc1:LiveBoundColumn DataFormatString="{0:F2} &deg;F" HeaderText="Temperature" DataField="Temp" ItemStyle-Width="100px" ItemStyle-HorizontalAlign="Center"></cc1:LiveBoundColumn>

                <cc1:LiveBoundColumn DataFormatString="{0:F2} in" HeaderText="Pressure" DataField="Pressure" ItemStyle-Width="100px" ItemStyle-HorizontalAlign="center"></cc1:LiveBoundColumn>

                <cc1:LiveBoundColumn DataFormatString="{0:F2} %" HeaderText="Humidity" DataField="Humidity" ItemStyle-Width="100px" ItemStyle-HorizontalAlign="Center"></cc1:LiveBoundColumn>

                <cc1:LiveBoundColumn DataField="Pic"></cc1:LiveBoundColumn>

            </Columns>

            <ItemStyle BackColor="LightSkyBlue"></ItemStyle>

        </cc1:LiveDataGrid>

        <cc1:LiveTimer DataPersistenceType=Session ID="tmrUpdate" Runat="server" Interval="3000" OnTick="tmrUpdate_Tick"></cc1:LiveTimer>

    </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

 

public partial class PowerWEB_aspx

{

    private ZipcodeInfo zipInfo = null;

    private WeatherInfo weatherInfo = null;

 

    void Page_Load(object sender, EventArgs e)

    {

        // Recreate data objects on each request

        weatherInfo = new WeatherInfo(this.Session);

        zipInfo = new ZipcodeInfo();

        if (!Page.IsPostBack)

        {

            // Reinitialize the session.

            Session.Clear();

            // Add some startup cities.

            weatherInfo.WeatherEntries.Add(new Weather(this.Session, "New York"));

            weatherInfo.WeatherEntries.Add(new Weather(this.Session, "Boston"));

            weatherInfo.WeatherEntries.Add(new Weather(this.Session, "Los Angeles"));

            DoBind();

            // Start the client-side timer

            tmrUpdate.Start();

        }

    }

 

    void btnLookup_Click(object sender, EventArgs e)

    {

        // Lookup button clicked. Resolve zipcode to city name

        string city = zipInfo.GetCity(txtZip.Text);

        lblResults.Text = city;

        btnAdd.Visible = true;

    }

 

    private void DoBind()

    {

        // Rebind the grid (changes will automatically be reflected on the client)

        grdCities.DataSource = weatherInfo.GetData();

        grdCities.DataBind();

    }

 

    void tmrUpdate_Tick(object sender, EventArgs e)

    {

        // Timer tick. Recalc the current conditions and rebind the data

        weatherInfo.Recalculate();

        DoBind();

    }

 

    void btnAdd_Click(object sender, EventArgs e)

    {

        // Add button clicked. Add the current city to the streaming grid.

        weatherInfo.WeatherEntries.Add(new Weather(Session, lblResults.Text));

        DoBind();

    }

}

Figure 3. WeatherWatch code using LiveControls.

Summary

You can use basic callbacks to achieve the following benefits:

  • Rich Internet Application behavior. You can minimize page loads in favor of updating page elements and approach a WinForms look and feel.
  • Superior performance. Not only is client-server communications minimized for the benefit of low-bandwidth users, but client drawing delays are almost eliminated.
  • Tighter database integration. The greatest benefits will likely be seen in database manipulation and display. By reacting immediately to user interaction or timers, tables and computed values can be seamlessly updated with dynamic server-generated data.

In addition, if you use a Rich Internet Application development tool like PowerWEB LiveControls for .NET, you realize additional life-cycle cost benefits:

  • Single-source logic, debugging and maintenance. All logic is in code-behind, instead of being separated into JavaScript and code-behind elements. You always operate on the server-side document model, so development and debugging support is superior. Development and maintenance costs are minimized.
  • Additional code-behind events. Windows-like mouse, key, and click events are raised in code-behind and are easily utilized using the robust Visual Studio development environment.
  • Cross-browser support. IE6 and Mozilla (Netscape, FireFox) are supported by the Client Script Manager, but LiveControls gracefully degrades to using IFrame for IE5 (PC/Mac), Opera, Galeon, and Konquerer/Safari, and further degrades to postback operation for non-supported browsers.
  • ViewState is automatically preserved across callbacks and postbacks. LiveControls manages ViewState across all callbacks and postbacks to the server, whereas Client Script Manager does not persist changes in state made during a callback.
  • Programming efficiency is increased. As demonstrated by our WeatherWatch developer, LiveControls provided a ten-fold increase in productivity when compared to the Microsoft Client Script Manager.

Conclusion

The use of callbacks provides new opportunities for developers and users alike. As we’ve seen, sites like Microsoft and Google are already building RIAs using callbacks, and Ajax technologies are already being used to deliver real-world apps incorporating callback technology.

As users and developers discover and utilize these techniques, we are anticipating greater demand for products that help developers take advantage of the capabilities discussed here. I think we’ll soon be seeing a new ASP.NET component category called "Rich Internet Application Tools".

About the author...

Michael Baldwin founded Dart Communications in 1994, which became a leading component supplier of PowerTCP Internet protocol products for Windows. For the last 2 years Dart has been developing new products for its PowerWEB ASP.NET product line. Mike can be reached at baldwin@dart.com. Company information is available at www.dart.com.