span.sup { vertical-align:text-top; }

Cutting Edge

Programming AJAX with ASP.NET Partial Rendering

Dino Esposito

Code download available at:CuttingEdge2008_08.exe(470 KB)

Contents

Pages with a Menu
Navigation URLs
Async Postbacks
Menu and Partial Rendering
Give Me a Placeholder
Dynamically Loaded Controls
Which Control Posted Back?
Caching Output for User Controls
It's about Pragmatism

The heart of AJAX is the XMLHttpRequest object. The user experience opportunities that AJAX offers are contingent on the availability of this object on a broad number of browsers over multiple platforms. A lot of things have happened since 2004, when component vendors first began showcasing AJAX applications, but at the core, AJAX would not be possible without the ability to execute an out-of-band call using the XMLHttpRequest object.

As the complexity of AJAX applications has increased, developers have come to realize that plain script-driven, out-of-band calls are not enough to build a new generation of Web applications in a cost-effective way. Subsequently, the demand for a set of more powerful tools is growing. These tools are expected to add AJAX capabilities to pages and applications, while using the same development paradigm as classic ASP.NET. So, to make a long story short, developers need more in order to build AJAX sites than plain calls to the XMLHttp­Request object for fetching data and handcrafted JavaScript functions for manipulating the Document Object Model (DOM).

In this month's column, I'll look at a pragmatic approach for AJAX that leverages the ASP.NET partial rendering engine. As you will see, AJAX involves trade-offs between application performance and developer productivity. Real-world AJAX sites are not built entirely with either partial rendering or manual scripting of the XMLHttpRequest object. They require a powerful mix of techniques that are often well synthesized by custom controls.

Pages with a Menu

AJAX is mostly about minimizing the number of full page reloads. Both partial rendering and manual script-driven calls allow you to fetch server-side data and avoid a full page refresh. For many content types, back-end services and DOM manipulation may be enough. But what if you have to support navigation?

Classic hyperlinks break the AJAX magic, telling the browser to place a request to another URL. As a result, the current page is frozen until the new chunk of HTML is downloaded. When the fresh data arrives, the page is dismissed and the browser's client is then fully redrawn.

Top-level links placed on a Master Page that point users to different areas of the site may really be implemented as traditional hyperlinks. Depending on your users' expectations, a full page reload may be acceptable in this case.

Whenever you build a menu, you make a choice about how to handle user clicks. You may assign each menu item a URL or just post back to the same page. From an AJAX perspective, you choose between dismissing the current page and loading a brand new one, and asynchronously loading some new content from the server.

Take a look at the following code snippet, which shows a fragment of the ASP.NET Menu control:

<asp:Menu runat="server" ID="Menu1">
<asp:MenuItem Text="Products" Value="Products">
    <asp:MenuItem Text="By price" NavigateUrl="..." /> 
    <asp:MenuItem Text="All" Value="Products-All" /> 
</asp:MenuItem>
...
</asp:Menu> 

The first MenuItem child element of the Products menu item features a NavigateUrl property. The property gets or sets the URL to navigate to when the menu item is clicked. The second MenuItem element features a Value property. The Value property is used to store additional data about the menu item, and it is passed to the MenuItem's postback event. Menu items at the same level must each have a unique value for the Value property. When no explicit navigation URL is specified, clicking a menu item results in a classic postback. On the server, you simply handle the MenuItemClick event and process the content of Value property.

Obviously, if you choose an approach using the Value property, you can use AJAX techniques and avoid a full page reload. The overall model of this type of page is a single-page interface.

The single-page interface reduces the burden on the user interface because it decreases the number of reloads and eliminates flickering. However, the single-page interface also means that your application uses less-distinct URLs, which results in diminished support from search engines. The single-page interface also means that the development team writes fewer pages, but they're richer. Such an approach may also reduce parallelism within the team.

Navigation URLs

When the menu takes users to a brand new page, you don't really need a Menu control to implement the solution. In theory, a list of hyperlinks would suffice. There are dozens of DHTML and AJAX menu frameworks available out there, but, at the end of the day, they differ based on the animation, graphics, and predefined skins they offer. Functionally speaking, these menus are plain collections of hyperlinks, and hyperlinks are natively handled by browsers in a non-AJAX way.

Generally, large Web sites that contain hundreds of total pages are likely designed around a few entry pages that then direct users to different subsites. The home page, therefore, just needs links to these subsites. In this case, navigation is the requirement and AJAX would not be necessary. But what if you need to bring content down to the currently displayed page? How would you organize this content to maximize performance of the application and productivity of the team?

Async Postbacks

Last month I demonstrated how to use Windows® Communication Foundation (WCF) services to return either raw data or HTML to the client. Both solutions have pros and cons. Sending raw data optimizes the bandwidth but requires you have some DOM manipulation logic on the client implemented with JavaScript. Sending server-generated HTML increases the quantity of data that is moved around, but it allows you to maintain most of your rendering logic on the server.

Both approaches fall into the category of the so-called "true" AJAX solutions, where the application is clearly designed according to a two-tier model—Web browser and service layer. Any state and logic that involves the user interface is maintained and controlled by the client; no view state or postbacks are needed.

Server-generated HTML is most useful if you have read-only markup to return. It works well if you need to return a static grid of data or a panel with some information on the selected customer or invoice. It's much less enticing if you need to bring up an interactive panel full of controls that fire events and require handlers. In a pure AJAX approach, the displayed markup—whether generated on the client or the server—must contain JavaScript function calls.

Last month I used services to implement the HTML Message and Browser-Side Template patterns. But, as you may have noticed, my examples were not really interactive. It turns out that both approaches may work in some cases, but not in all. My sample services returned stock quotes, but the final grid was not pageable. To support paging, for example, I had to insert hyperlinks pointing to JavaScript functions and then make sure that I downloaded the referenced JavaScript.

Menu and Partial Rendering

Figure 1 shows a sample page with a top-level menu that allows users to navigate in the set of application features. The menu has been created using the ASP.NET Menu control, as shown in Figure 2. As you can see, all menu items avoid specifying the Navigate­Url attribute, which would point each to a physical URL, possibly for a totally different page. Having specified the Value attribute, a postback occurs and the MenuItemClick event is fired to the page whenever the user selects any of the menu items:

void Menu1_MenuItemClick(object sender, MenuEventArgs e)
{
    LoadContent(e.Item.Value);
}

fig01.gif

Figure 1 An ASP.NET AJAX Page with a Top-Level Menu (Click the image for a larger view)

Figure 2 Menu of the Sample Page

<asp:Menu ID="Menu1" runat="server" Orientation="Horizontal" 
        onmenuitemclick="Menu1_MenuItemClick">
    <Items>
        <asp:MenuItem Text="Customers">
            <asp:MenuItem Text="All" 
                 Value="Customers-All" /> 
            <asp:MenuItem Text="By Country" 
                 Value="Customers-by-country" /> 
        </asp:MenuItem>
        <asp:MenuItem Text="Orders">
            <asp:MenuItem Text="1997" 
                  Value="Orders-1997" />
            <asp:MenuItem Text="By Customer" 
                  Value="Orders-by-customer" /> 
        </asp:MenuItem>
        <asp:MenuItem Text="Products">
            <asp:MenuItem Text="All" 
                 Value="Products-All" /> 
        </asp:MenuItem>
    </Items>
</asp:Menu>

The MenuEventArgs class features an Item property that represents the clicked menu item. The Value property on the MenuItem object informs the event handler about the identity of the clicked item. By using a local LoadContent function, the page can dynamically load the required content. But what about the postback?

The ASP.NET Menu control fully supports partial rendering. To swallow the postback and load new content smoothly, all that you have to do is set up a ScriptManager control and insert an Update­Panel control, as shown here:

<asp:UpdatePanel runat="server" ID="UpdatePanel1" 
          UpdateMode="Conditional">
    <ContentTemplate>    
       ...
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Menu1" 
            EventName="MenuItemClick" />
    </Triggers>
</asp:UpdatePanel>

The updateable content is bound to the MenuItemClick of the Menu. Through the UpdateMode property, the panel is also configured for control-specific updates. This means that the updateable area is refreshed only when the user clicks on a menu item.

In most Web applications, you use a menu to point users to different functional areas of the site or the page. Having distinct ASPX pages to implement specific functionalities is helpful for a number of reasons. It keeps your code clean and easier to maintain and test. But more importantly, it allows you to assign the implementation of distinct functionalities to distinct developers on the team. Thus, you can improve the productivity by parallelizing some development tasks.

The single-page interface is a typical AJAX pattern that suggests you have a main page in your application whose user interface is rearranged every time an event is fired. The single-page interface model is great at minimizing postbacks, but it requires some care when it comes to parallelizing development tasks on a team. You should consider it to be a page architecture based on the concept of plug-ins. In other words, the page defines placeholders with a contracted interface where dynamically loaded components could be plugged in quickly and easily.

Give Me a Placeholder

In ASP.NET, the simplest way to achieve this page architecture oriented toward plug-ins involves using the PlaceHolder control and ASCX user controls to define on-demand components. Here's how you define an updateable area:

<asp:UpdatePanel runat="server" ID="UpdatePanel1" 
    UpdateMode="Conditional">
    <ContentTemplate>    
        <asp:PlaceHolder runat="server" ID="PlaceHolder1"
            EnableViewState="false" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Menu1" 
            EventName="MenuItemClick" />
    </Triggers>
</asp:UpdatePanel>

You associate each menu item with an ASCX user control and load it into the placeholder whenever the item is clicked. Figure 3 shows the source code of the aforementioned LoadContent function, which you invoke from the MenuItemClick event handler.

Figure 3 Dynamically Loading Content into the Page

public partial class _Default : System.Web.UI.Page
{
    public string TrackedUserControl 
    {
        get { return ViewState["TrackedUserControl"] as string; }
        set { ViewState["TrackedUserControl"] = value; }
    }

    protected void Menu1_MenuItemClick(object sender, MenuEventArgs e)
    {
        LoadContent(e.Item.Value);
    }

    private void LoadContent(string menuItemName)
    {
        string ucUrl = menuItemName + ".ascx";

        UserControl uc = null;
        try
        {
            uc = this.LoadControl(ucUrl) as UserControl;
        }
        catch(Exception ex)
        {
        }
        if (uc != null)
        {
            Placeholder1.Controls.Add(uc);
            TrackedUserControl = ucUrl;
        }
    }
    ...
}

The LoadControl method on the Page class takes the URL of an ASP.NET user control and loads it into memory as an instance of a class that derives from UserControl. If the control is loaded correctly, the method adds it to the Controls collection of the PlaceHolder. Note that the ASP.NET PlaceHolder control doesn't output any markup, so don't be afraid of cluttering your user interface markup if you use it.

Using ASCX user controls in the context of an AJAX site with dynamic and interactive content doesn't reduce your chance of developing multiple blocks of content at the same time—an ASCX control is a sort of ASP.NET page and can be developed independently from the rest of the site.

Dynamically Loaded Controls

In ASP.NET, dynamically loaded controls require a bit of attention because of their behavior across postbacks. Suppose you create a user control that contains a pageable grid of customers, and then you load the user control into the placeholder (see Figure 4). Everything works just fine until you click one of the bottom hyperlinks to navigate to a new page. When this happens, the page partially refreshes, but the whole content of the user control disappears. Why?

fig04.gif

Figure 4 Interactive Control Dynamically Loaded into Page (Click the image for a larger view)

In ASP.NET, each page request is treated independently from any other previous or successive requests. So a brand new instance of the page class is created to service each incoming request. This new page contains fresh instances of all server controls that are statically referenced in the ASPX source code.

But what about the dynamically added controls? The page doesn't know about them unless the code in the page uses some persistence mechanism to track dynamic controls. The page view state is an excellent storage medium for this type of information. In Figure 3, I added the TrackUserControl property to the page class to remember which user control is currently displayed.

In the Page_Load event, you use the content of the TrackUserControl property to figure out which user control has to be manually reloaded in order to restore exactly the same collection of controls the page contained last time:

void Page_Load(object sender, EventArgs e)
{
    if (!IsPostingFromMenu() && IsPostBack)
        ReloadContent();
}

Note, though, that the postback may have one of two causes—either the user triggered an action from within the displayed user control or the user clicked another menu item. The if statement in Page_Load determines whether the user is posting from the menu. If so, nothing happens because some new content has to be loaded; otherwise, the tracked user control is reloaded so that it can handle the postback event:

void ReloadContent()
{ 
    UserControl uc = null;
    try
    {
        uc = this.LoadControl(TrackedUserControl) as UserControl;
    }
    catch
    {
    }
    if (uc != null)
        Placeholder1.Controls.Add(uc);
}

What about the view state for the dynamically added user control? And what about any data that HTML elements may have posted to it? When the user is served a page, there is no distinction between statically and dynamically added controls. Any input the user enters (for example, text in an input field) is automatically packed into the HTTP request when the form is submitted. On the server, therefore, any information to be consumed by the dynamically loaded control is available. However, you should know that when the page's Init event fires, no such control is yet available. This is because the page's control tree is only populated with statically referenced controls.

It is up to the page author to add any dynamically referenced controls to the tree. The page author, though, may need to read about added controls from a persistent storage medium. If this information is stored in Session or Cache, then you can safely access it from the Init event handler. If that information is stored in the view state bag, then you have to wait for the Load event. In the ASP.NET page lifecycle, in fact, the view state is unpacked and processed between the Init and Load events.

At this stage, any posted data is also examined and mapped to existing controls. But what about the data that is destined for dynamically added controls? Basically, posted data is processed in one of two passes—once before or once after the Page__Load event. Data for which no matching control is found in the first round is cached and processed again after the Load event.

In the Load event, developers should check which controls were dynamically added the previous time and reload them in the page control tree. Nicely enough, the Add method of the Controls collection automatically updates the state of the control with any data found for that control in the view state.

Which Control Posted Back?

A page that loads some of its content from external resources needs to know why a postback is raised. In the sample code you see in Figure 4, the page may post back because the user selected a given element from the menu or because the user triggered a postback while working with another component on the user interface.

In ASP.NET, there's no property anywhere that can tell you which control caused the postback. The ID of the posting control, however, is not difficult to find. If the user posted back by clicking a Submit button, then the ID of the control is listed in the HTTP request. If no button control exists in the body of the HTTP request, then the user posted back by interacting with a Link button or an auto-postback control.

In any case, the ID of the postback source is in the __EVENTTARGET hidden field. In ASP.NET AJAX, when partial rendering is used, things are easier, as the ScriptManager control exposes a custom property—the AsyncPostBackSourceElementID property. The following code, which is an excerpt of the code that produced Figure 4, shows how to determine whether the user clicked a menu item or posted back to the server through another control:

bool IsPostingFromMenu()
{
    ScriptManager sm = ScriptManager.GetCurrent(this);
    string ctlID = sm.AsyncPostBackSourceElementID;
    Control c = this.FindControl(ctlID);
    if (c == null)
        return false;

    return (c.ID == "Menu1");
}

When the user clicks repeatedly on the menu, a given ASCX user control may be loaded again. Loading a user control means downloading the control markup and going through the server lifecycle. However, since continuously reloading a user control could possibly have a negative impact on performance, you can use an ASP.NET feature such as output caching to help boost the overall performance of the page.

Caching Output for User Controls

A user control that provides some content to a site may be expensive to create because it could require one or more database calls and potentially a few million CPU cycles. Why then would you want to regenerate the same user control several times per second, especially if your content is not continuously changing?

A better strategy is to create the user control once, cache its output, and give it a maximum duration. Once the cached snapshot of the user control becomes stale, the first incoming request will be served again in the standard way, running the control's code and caching the generated markup again.

The ASP.NET page output caching feature allows you to cache page and user control responses so that subsequent requests can be satisfied without executing code but rather by simply returning the cached output. To make a user control cacheable, you declare the @OutputCache attribute in the source of the ASCX, as shown in the following code. The code snippet caches the output of the control for one minute:

<% @OutputCache Duration="60" VaryByParam="None" %>

Output caching is a nice feature, but it is far from being a silver bullet. It is simply a way to have the application serve more pages and user controls more quickly. It won't necessarily make the application more efficient or scalable. With output caching, however, you certainly reduce the workload on the server, as pages and resources are cached downstream.

Additionally, output caching is only a viable option for anonymous content. Requests for cached content are served by IIS directly and never make it to the ASP.NET pipeline, where the caller can be authenticated.

Finally, pages and user controls that post back require some extra work. In particular, you need to instruct ASP.NET to save multiple copies of the output that vary by one or more parameters. For this purpose, you use the VaryByParam attribute on the @OutputCache directive.

It's about Pragmatism

While partial rendering is often perceived as the poor man's AJAX and reserved for those who can't exert the energy required to create "true" AJAX applications, it is one of the best options at the moment for developers using ASP.NET. In the end, remember that AJAX involves many trade-offs, and effective AJAX results from the combination of a variety of techniques and patterns.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is currently an architect at IDesign and the author of Programming ASP.NET 3.5 Core References . Based in Italy, Dino is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos.