1.1 Building Sophisticated Ajax Applications with ASP.NET Atlas

Web pages were originally built around the document concept, with people viewing documents located on remote servers using web browsers. The first big shift from that paradigm happened when programmers started using server-side code to generate dynamic web pages for their users. That’s how web applications were born.

Web sites are no longer just collections of static documents; they can now accept user input and react to it in different ways. Until recently, most of the innovations in that area were made on the server side. Clients had poor scripting support, and the lack of standards made creating portable code difficult.

We are now facing another revolution, this time on the client side. Most popular browsers already support the XMLHttpRequest object, which allows the client to call the server from a script to update a page without reloading it. This drastically cuts application response time, so developers can now author web applications whose responsiveness rivals that of some desktop applications. Unfortunately, however, building such applications is still a challenge, because developers still need to write a lot of cross-browser JavaScript code (a difficult task).

Microsoft’s answer to the difficulties in client-side programming is the ASP.NET “Atlas” framework, which we’ll refer to simply as Atlas. This framework provides a lot of features that help developers concentrate on the application logic and let them forget about most browser quirks.

At its lowest level, Atlas implements a browser-compatibility layer that tries to unify the document object model (DOM) under different browsers. Atlas also makes it possible to use several OO-like constructs such as classes, interfaces, namespaces, properties, and delegates with JavaScript in ways that will be familiar to .NET developers. An entire client-side component model has been created to allow objects to interact in a well-defined and predictable manner. Now JavaScript experts can develop script components that non-experts can create declaratively with an XML-based syntax, greatly reducing the amount of script to be written.

Microsoft ASP at a Glance

Tool

Microsoft ASP.NET Atlas

Version covered

April 2006 CTP

Home page

http://atlas.asp.net

Power Tools page

http://www.windevpowertools.com/tools/18

Summary

A client- and server-side framework providing components and infrastructure that allow the creation of highly interactive web applications while hiding the complexities of Ajax and JavaScript portability

License type

Commercial, zero-cost

Online resources

Documentation, forums, and weblogs (see the home page for links)

Related tools in this book

Anthem.NET, Ajax.NET Professional

Getting Started

You can download Atlas from its home page via the prominent Download icon on the site’s header.

The server-side part of Atlas requires ASP.NET 2.0 or later to run. The client-side code supports Microsoft Internet Explorer (IE), Gecko-based browsers such as Mozilla Firefox, and Safari. Opera support is planned for a future release.

The installation comes in a Windows Installer .msi package. It reconfigures Internet Information Services (IIS) and adds new project templates to Visual Studio .NET 2005. Running an Atlas-powered web site requires a local or Global Assembly Cache (GAC) reference to the Microsoft.Web.Atlas.dll assembly.

Tip

The Atlas installation needs to register a new file extension with IIS. This may be an issue if you’re working with third-party web hosting services for application deployment. In that case, you’ll need to coordinate Atlas provisioning with your hosting service.

Using Atlas

The Atlas framework comes loaded with features for improving the experience of your users. Three of the most important features simplify the coding of asynchronous partial page updates, provide a means to work with local and third-party web services, and enhance the power of JavaScript as a client scripting language.

Rendering partial page updates

Traditional ASP.NET development involves working with controls that generate web page output. Those controls render HTML and send it to the client. The client browser might react to user input and post data back to the server, in which case the server re-creates the page controls, renders the content anew, and sends it back to the client once again. This is not very efficient, especially if a big portion of the page—a navigation pane, for example—stays the same all the time. With Atlas, you can update only portions of the page and send only the changed HTML over the network.

Here’s an example page that accepts a search query in a text box and displays the search results in an unordered list, generated by a data-bound Repeater control:

<asp:TextBox ID="searchBox" runat="server"></asp:TextBox>
<asp:Button ID="searchButton" Text="Search" runat="server"
            OnClick="searchButton_Click" />
<br />
<asp:Repeater ID="searchResults" runat="server">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <%# Container.DataItem %>
        </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>

Every time the user clicks the Search button, the page regenerates all the HTML and sends it back over the network. You can optimize that behavior by placing your controls inside an Atlas UpdatePanel control, which prevents full-page postbacks caused by any control inside it. In addition, it sends the user input to the server with an Ajax request, limiting server responses to the UpdatePanel’s contents only.

A single instance of the ScriptManager control is required on all Atlas-enabled pages. This control is responsible for including various client script resources on the page. To inform all the UpdatePanel controls on a page that they must do partial instead of full rendering, set the ScriptManager’s EnablePartialRendering property to true, as shown in the following snippet:

<atlas:ScriptManager ID="scriptManager1" runat="server"
                     EnablePartialRendering="true">
</atlas:ScriptManager>
<atlas:UpdatePanel ID="searchPanel" runat="server">
    <ContentTemplate>
        <asp:TextBox ID="searchBox" runat="server"></asp:TextBox>
        <asp:Button ID="searchButton" Text="Search" runat="server"
                    OnClick="searchButton_Click" />
        <br />
        <asp:Repeater ID="searchResults" runat="server">
            <HeaderTemplate>
                <ul>
            </HeaderTemplate>
            <ItemTemplate>
                <li>
                    <%# Container.DataItem %>
                </li>
            </ItemTemplate>
            <FooterTemplate>
                </ul>
            </FooterTemplate>
        </asp:Repeater>
    </ContentTemplate>
</atlas:UpdatePanel>

The search application will now work without postbacks and update the search panel only, using Ajax requests. However, there’s another problem. Some searches might take a long time, and, particularly to users with slower network connections, it might look as though the application has locked up while waiting for the request to complete. To notify users that the application is awaiting a response from the server, you can include one or more UpdateProgress controls on the page:

<atlas:UpdateProgress runat="server">
    <ProgressTemplate>
        Loading...
    </ProgressTemplate>
</atlas:UpdateProgress>

An UpdateProgress control is a template control that displays its contents during a request to the server. Most often, such controls contain an animated GIF image that looks like a progress gauge. The progress area content can be positioned at a custom location via CSS styling.

Optimizing partial page updates

Update panels can be a great way to reduce network traffic. But by default, an update caused by one panel will update all the panels on the page. You can further optimize this behavior by switching a panel’s Mode property to Conditional and configuring some update triggers for the panel. A conditionally updated panel will render and update itself if and only if one of its triggers fires.

For example, consider a page that displays user profile information—say, the user’s name and the last date she logged onto the system—in an update panel. The web page might have a profilePanel control containing a Refresh button that fetches changed information from the server. A commentsPanel control might display additional information about the user. Figure 1-1 shows a sample page containing these controls.

A user profile form containing two Atlas UpdatePanel controls

Figure 1-1. A user profile form containing two Atlas UpdatePanel controls

We don’t want the second panel to be updated when the user refreshes the first panel. The only time we need to update the second panel is when the user clicks a checkbox that toggles the display of additional information. The checkbox has its AutoPostBack feature enabled; our CheckedChanged event handler toggles the visibility of a label control inside the commentsPanel. We set its Mode property to Conditional and add a trigger from the Visual Studio designer by editing the Triggers property from the property grid, as shown in Figure 1-2.

Configuring a trigger for a conditionally rendered UpdatePanel

Figure 1-2. Configuring a trigger for a conditionally rendered UpdatePanel

The trigger will fire when showCommentsCheckBox raises its CheckedChanged event. The final version of our page looks like this:

<atlas:ScriptManager ID="scriptManager1" runat="server"
                     EnablePartialRendering="true">
</atlas:ScriptManager>
<asp:CheckBox ID="showCommentsCheckBox" runat="server"
              Text="Show additional comments"
              AutoPostBack="True"
              OnCheckedChanged="showCommentsCheckBox_CheckedChanged" />

<atlas:UpdatePanel ID="profilePanel" runat="server" Mode="Always">
    <ContentTemplate>
        Name:
        <asp:Label ID="nameLabel" runat="server"></asp:Label>
        <br />
        Last Login:
        <asp:Label ID="loginDateLabel" runat="server"></asp:Label>
        <br />
        <asp:Button ID="refreshButton" Text="Refresh" runat="server"
                    OnClick="refreshButton_Click" />
        <br />

    </ContentTemplate>
</atlas:UpdatePanel>
<atlas:UpdatePanel ID="commentsPanel" runat="server" Mode="Conditional">
    <ContentTemplate>
        <asp:Label ID="commentsLabel" runat="server" Visible="false"
                   Text="Some additional info for this user">
        </asp:Label>
    </ContentTemplate>
    <Triggers>
        <atlas:ControlEventTrigger ControlID="showCommentsCheckBox"
                                   EventName="CheckedChanged" />
    </Triggers>
</atlas:UpdatePanel>

Tip

Controls that can fire triggers, like showCommentsCheckBox, will cause Ajax requests even when they are placed outside of an UpdatePanel control. After all, doing a complete postback defeats the purpose of partial rendering.

Fetching data from web services

Updating portions of the page with UpdatePanel controls is a great way to turn a postback-based site into a full-blown Ajax application. But sometimes, that’s not enough.

Rendering update panels executes the full page lifecycle, so in effect partial rendering reduces only the amount of rendered HTML. All server-side page events (e.g., Init, Load, and PreRender) are still fired, and that overhead can be prohibitive in some situations. In addition, sometimes the raw data—say, an array of number values—is needed on the client side. Rendering that array as HTML and parsing it back with script code is suboptimal and error-prone.

Atlas solves this problem by allowing developers to call web-service methods from JavaScript and get just the data in a very efficient way. All parameters and return values are automatically serialized to the very compact JavaScript Object Notation (JSON) format.

Tip

Atlas achieves this enhanced functionality with web services by including a new HTTP handler for .asmx files. An HTTP handler is an ASP.NET object that handles all of the requests for a certain file type (in this case, any web services setup using the .asmx extension). The custom HTTP handler can then manage the serialization and deserialization of the parameters and return values on their way into and out of the requested web service.

Atlas’s enhanced web-service functionality is one of its major strengths, and it’s something not found in the other Ajax implementations covered in this book.

Suppose we have a web service that returns the air temperature in a given city. Here is a dummy implementation of our web method:

[WebService(Namespace = "http://windevpowertools.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Weather : System.Web.Services.WebService
{
    public Weather( )
    {
    }

    [WebMethod]
    public int GetTemperature(string city)
    {
        return 30;
    }
}

To call the web method from JavaScript, we need to add a ServiceReference element to our ScriptManager control. It includes a script that generates proxy objects, which we can then use to call the service. The actual call is done in a button’s onclick event handler. The code looks like this:

<atlas:ScriptManager ID="scriptManager1" runat="server"
                     EnablePartialRendering="true">
    <Services>
        <atlas:ServiceReference Path="Weather.asmx" />
    </Services>
</atlas:ScriptManager>

<input type="text" id="cityBox" value="" />
<input type="button" value="Fetch temperature" onclick="FetchTemperature( )" />

<script type="text/javascript">
function FetchTemperature( )
{
    var city = $('cityBox').value;
    Weather.GetTemperature(city, WeatherRequestComplete);
}

function WeatherRequestComplete(result)
{
    alert(result);
}
</script>

The $( ) function (shown in the statement var city = $(''cityBox'').value;) is Atlas shorthand for document.getElementById( ), the official W3C API for locating elements in a document. In this example, we get the name of the city that the user has typed in the cityBox text box. Atlas has taken care to create proxies with the same names as our server objects on the client. GetTemperature( ) is our web method, and Weather is the name of the server-side class. Note that we pass an additional parameter to our GetTemperature( ) proxy method. The service request is asynchronous, and the result is not immediately available, so we have to provide a callback function that will be called when the request completes. We can also pass two other callback functions: the server-error and request-timeout handlers.

Tip

The example script shows the Weather.asmx service as part of the current web application because JavaScript allows access only to resources in the browser’s home domain. However, the latest Community Technology Preview (CTP) of Atlas includes a new bridging technology that allows applications to create gateways to external web services, thereby breaking this limitation of traditional Ajax. See the Atlas documentation for more information.

What if we have a web service that accepts user-defined objects as parameters? Atlas will generate JavaScript stub classes and will serialize the data when sending it to and from the server.

Here is a dummy implementation of a mapping service that returns a list of nearby restaurants. Our method takes a MapPoint object as a location parameter and returns a generic list of Restaurant objects:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class MapWebService : System.Web.Services.WebService
{
    public MapWebService( )
    {
    }

    [WebMethod]
    public List<Restaurant> GetRestaurants(MapPoint location)
    {
        List<Restaurant> result = new List<Restaurant>( );
        Restaurant restaurant = new Restaurant( );
        restaurant.Name = "John's Diner";
        result.Add(restaurant);
        restaurant = new Restaurant( );
        restaurant.Name = "Giovanni's Trattoria";
        result.Add(restaurant);
        return result;
    }
}

Calling the method from JavaScript is very similar to the way you would call it from C#:

<script type="text/javascript">
function FindNearByRestaurants(x, y)
{
    var point = new MapPoint( );
    point.X = x;
    point.Y = y;
    MapWebService.GetRestaurants(point, GetRestaurantsComplete);
}

function GetRestaurantsComplete(result)
{
    for (var i = 0; i < result.length; i++)
    {
        var restaurant = result[i];
        alert(restaurant.Name);
    }
}
</script>

Atlas has generated the client-side MapPoint and Restaurant classes and knows how to serialize their properties during the request. The generic list has been turned into a JavaScript array through which we can iterate.

Automatic object serialization saves us a lot of work here, but it may not be available for complex object types. In these cases, developers can extend Atlas with converter classes.

Working with the client-side component model

Atlas isn’t just about Ajax functionality. It also tries to simplify client-side development by providing a set of JavaScript-based services. It extends the language and provides ways to define classes, interfaces, namespaces, properties, and delegates. Developers can create self-describing client-side components using the type descriptors infrastructure, components can define properties that can raise change notifications and participate in data binding, and users can define property bindings that allow them to update a component as a result of another component change without a single line of code.

Components can serve many purposes, but the most common component types are controls and behaviors. Controls are presentation objects that usually wrap document elements and provide user interface building blocks. Behavior objects offer a way to separate actions from presentation logic. They typically extend controls by subscribing to their events and performing some actions.

As an example, we’ll create a Sys.UI.Button control that wraps the saveButton1 input element. We’ll attach a ConfirmButtonBehavior component to the control and initialize it with a ConfirmMessage property of “Are you sure?” From now on, clicking the button will present the user with a confirmation box with the same text. Clicking Cancel will cancel the action and prevent any data submission to the server. Here’s the code:

<input type="submit" value="Save" id="saveButton1" />
<script type="text/javascript">
function pageLoad( )
{
    var button = new Sys.UI.Button($("saveButton1"));
    var confirmBehavior = new AtlasControlToolkit.ConfirmButtonBehavior( );
    confirmBehavior.set_ConfirmText("Are you sure?");

    button.get_behaviors( ).add(confirmBehavior);
    confirmBehavior.initialize( );
}
</script>

Note the Atlas convention of prefixing methods with get_ and set_. This is how properties are implemented, because JavaScript does not natively support properties. “Setting the ConfirmMessage property” actually refers to calling the set_ConfirmText( ) method.

The previous example might look like overkill—all we want is to attach a behavior to our button. In fact, wiring behaviors by hand is required in only the most complex scenarios. Atlas provides another way to create and configure components: they can be defined declaratively with an Atlas script. Atlas scripts are blocks of XML (<script type="text/xml-script">) that contain the component declarations. The syntax of such blocks is very similar to that of ASP.NET control declarations. Using this approach, we can create another button control and attach a ConfirmButtonBehavior to it:

<input type="submit" value="Save2" id="saveButton2" />
<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005"
      xmlns:atlascontroltoolkit="atlascontroltoolkit">
    <components>
        <button id="saveButton2">
            <behaviors>
                <atlascontroltoolkit:confirmButtonBehavior
                        ConfirmText="Are you sure?" />
            </behaviors>
        </button>
    </components>
</page>
</script>

Even the declarative XML script can be daunting to beginners, but fortunately there’s a third option: Atlas web controls. These are ASP.NET server controls that render XML script to the client. Controls that define behaviors for other controls in this way are usually called control extenders. Here is an example of the ConfirmButtonExtender control that extends a standard ASP.NET button:

<asp:Button ID="serverSaveButton" runat="server"
            Text="Server Save Button" />

<atlascontroltoolkit:ConfirmButtonExtender
        ID="confirmButtonExtender1" runat="server">
    <atlascontroltoolkit:ConfirmButtonProperties
            TargetControlID="serverSaveButton"
            ConfirmText="Are you sure?" />
</atlascontroltoolkit:ConfirmButtonExtender>

The ConfirmButtonExtender control is a part of the Atlas Control Toolkit, a set of free Atlas controls that are developed and released separately from the main Atlas distribution. The toolkit contains numerous controls that simplify and automate repetitive UI and data-related tasks. You can download the toolkit installer from the Atlas download page.

Getting Support

The Atlas site hosts a complete set of Quickstart tutorials, as well as very active forums where members of the Microsoft team who wrote Atlas can often be found.

Get Windows Developer Power Tools now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.