September 2009

Volume 24 Number 09

Cutting Edge - Exploring ASP.NET 4.0—Web Forms and Beyond

By Dino Esposito | September 2009

ASP.NET is a stable and mature platform for building rich and powerful Web applications, so it's hard to imagine a new set of compelling features being added to it. But last fall, with the release of Service Pack 1 for ASP.NET 3.5, Microsoft refined the platform's built-in AJAX support and enhanced its productivity by shipping Dynamic Data controls, a new framework of components specifically designed to address the needs of data-driven and data-entry applications.

In parallel, Microsoft developed a brand-new, alternative programming model called ASP.NET MVC. Unlike the classic Web Forms model, ASP.NET MVC helps developers create Web applications in accordance with a widely recognized design pattern: the Model View Controller.
Today, the overall ASP.NET platform is made up of a few distinct components: Web Forms, ASP.NET MVC, Dynamic Data controls and ASP.NET AJAX. The upcoming ASP.NET 4.0 platform has the same foundation as the latest 3.5 SP1 version, but it provides further refinement in the areas of Web Forms, Dynamic Data controls and, last but not least, ASP.NET AJAX.

In this article, I'll take a look at what's new and improved in the Web Forms model. In future columns, I'll address the Dynamic Data control platform as a whole and explore in-depth the developments in the ASP.NET AJAX environment.

ASP.NET Web Forms 4.0 at a Glance

The key words to describe what's new in the overall ASP.NET 4.0 platform are "more control." ASP.NET 4.0 is neither a revolutionary change nor a refactoring of its existing architecture. It consists, instead, of a good number of small-scale changes that together provide developers with much more control of certain frequently used features of the existing framework.

For example, ASP.NET 4.0 Web Forms give developers more control over viewstate management, generation of IDs in the context of data-bound controls, and HTML generated by some template-based controls. In addition, you'll find new families of pluggable components for features that weren't supporting the provider 2009 model in earlier versions of ASP.NET and a finer control over the linking of external script files through the ScriptManager control. Let's start with viewstate management.

More Control Over the Viewstate

I say nothing new by stating that the viewstate has been one of the most controversial features of ASP.NET since the advent of the platform. Too many developers are still convinced that the viewstate is a waste of bandwidth and an unacceptable burden for each and every ASP.NET page. Nearly the same set of developers eagerly welcomed ASP.NET MVC because of its complete absence of viewstate. Recently, while teaching an ASP.NET MVC class, I discussed a master/detail scenario in which the user could select a customer from a list to see more details. I populated the list during the loading of the page, as expected. Next, while handling the selection-changed event, I showed how to fill in the customer's details. However, to have the list available for another selection, I also had to explicitly repopulate it.

Students promptly noted the extra work required to fill in the list at every server action. Couldn't this be automatically filled as in Web Forms? Well, in ASP.NET Web Forms you don't need to refill data-bound controls over a postback just because of the viewstate.

In short, the viewstate is not there only to reduce your bandwidth. The viewstate is functional to the Web Forms model, as it caches some of the content for the controls in the page. Next, the ASP.NET infrastructure takes care of reading that information from the viewstate to restore the last known good state for each control within the page.

As widely known, but also largely overlooked, the viewstate is an optional feature. The viewstate support is turned on for each page by default, but developers have a Boolean property available to change the default setting and do without it. The property is named EnableViewState and is defined on the System.Web.UI.Control class. It should be noted that the System.Web.UI.Page class inherits from the Control class. As far as the viewstate is concerned, an individual control and the page are one and the same.

For any ASP.NET page, turning off the viewstate couldn't be easier. You set EnableViewState to false either declaratively or programmatically during the page's life cycle, as follows:

void Page_Load(object sender, EventArgs e)
{

Figure 1 The ViewStateMode Enumeration

Value Description
Inherit Inherits the value of ViewStateMode property from the parent control.
Enabled Enables viewstate for this control even if the parent control has viewstate disabled.
Disabled Disables view state for this control even if the parent control has viewstate enabled.

 

msdn magazine
2
Cutting Edge
// Disable viewstate for the page
// and ALL of its child controls
this.EnableViewState = false;
...
}

The EnableViewState property indicates whether the control is allowed to cache its own UI-related state. Be reminded that the viewstate setting in ASP.NET has a hierarchical nature, which means that if the viewstate is enabled on the parent control, it cannot be disabled on any of its child controls. The following code has no effect on the behavior of the text box if the viewstate is enabled at the page or container level:

protected void Page_Load(object sender, EventArgs e)
{
TextBox1.EnableViewState = false;
}

The IsViewStateEnabled property -- a protected property indeed -- reports about the current state of the viewstate for a control. But what does all of this mean to developers?

If the viewstate is enabled on the page (which is the default setting), you have no means to keep the state of individual controls off the storage. To gain some control over it in ASP.NET 3.5, you need to disable the viewstate at the page level and then re-enable it where needed, but also keep in mind the hierarchical nature of it. Any container control that has the viewstate enabled will inevitably push its setting down to the list of its children. This fact leads to somewhat of a paradox: it is possible for the same control to have the property IsViewStateEnabled set to true and the property
EnableViewState set to false!

The viewstate is a fundamental piece of the ASP.NET Web Forms architecture, and dropping it entirely from the platform in the name of a performance gain is arguably not the best option. Years of experience has proved that a more sustainable option is having the viewstate disabled by default on the page. Even better than that is the change coming up with ASP.NET 4.0: enabling viewstate control over individual controls.
In ASP.NET 4.0, the System.Web.UI.Control class exposes a new property named ViewStateMode:

public virtual ViewStateMode ViewStateMode { get; set; }

The property takes its feasible values from the ViewStateMode enumerated type, shown in Figure 1.

To preserve compatibility, the default value of the property is Inherit. However, this property gives developers the possibility of controlling the viewstate setting for individual controls, regardless of the viewstate option of any parent page or containers.

Only a few of the controls in ASP.NET pages really need viewstate. For example, all text boxes you use to simply gather text don't need viewstate at all. If Text is the only property you use on the control, then the value of the Text property would be preserved by cross-page postbacks because of posted values. Any value stored in the viewstate, in fact, will be regularly overwritten by the posted value. In this case, the viewstate is really unnecessary. But there's more to notice. Any side properties that are set to a given non-default value on creation, but remain unchanged during postbacks (such as ReadOnly, BackColor and so on), have no need to go to the viewstate. For example, a Button control that retains the same caption all the time doesn't need the viewstate at all. Up until ASP.NET 4.0, turning off the viewstate for individual controls was problematic. Things change in the next version. This is the key takeaway of the ViewStateMode property.

More Control Over Auto-Generated IDs

In ASP.NET pages, using the same ID for two server controls isn't allowed. If this happens, the page won't compile -- period. In HTML, though, it is possible for two or more elements to share the same ID. In this case, when you make a search for an element via document.getElementById, you'll simply get an array of DOM elements. What about nested ASP.NET controls?

Most data-bound, template-based controls generate their output by repeating an HTML template for every data-bound item. This means that any child control defined with a unique ID in the template is being repeated multiple times. The original ID can't just be unique. For this reason, since the beginning, the ASP.NET team defined an algorithm to ensure that every HTML element emitted by an ASP.NET control could be given a unique ID. The ID resulted from the concatenation of the control ID with the ID of the naming container. Furthermore, in case of repeated controls (i.e., templates), a numeric index was added for disambiguation. For years, nobody really cared about the readability of the auto-generated ID, and strings like the following became fairly common:

ctl00$ContentPlaceHolder1$GridView11$TextBox1

Looking at this, the first issue that may come to mind is the potential length of the string, which, repeated for several elements, makes the download larger. More important, such an approach is problematic from a client script perspective. Predicting the ID of a given control you want to script from the client is difficult, and leads straight to hard-to-read code. In the first place, you need to know the detailed name being generated for a given HTML element that is buried in the folds of a grid or any other naming container controls. Second, this name is subject to change as you rename one of the server controls along the hierarchy. A frequently used trick is the following:

var btn = document.getElementById("<% =Button1.ClientID %>");

The trick consists in using ASP.NET code blocks to insert in the HTML source code being generated for the real client ID of a given control. When master pages or template-based controls are used, the trick is a real lifesaver, because in this case, the naming container of a plain control ends up being quite a complex hierarchy of controls that developers often leave unnamed. The actual name of the control, therefore, is subject to a few auto-generated IDs.

In addition to using code blocks, ASP.NET 4.0 supports another option: more control over the algorithm that generates the client ID of a server control.

The System.Web.UI.Control class now features a brand new property named ClientIDMode. The property can assume a few predefined values as detailed in Figure 2.

Admittedly, the ID-generation algorithm, especially when master pages are involved, is not easy to understand. It is guaranteed to generate unique IDs, but ends up with strings that are really hard to predict. A more predictable option has been introduced primarily to be used with data-bound controls so that developers can easily guess the ID of, say, a Label control used to render a given property on the nth data item. In this case, you want the ID to reflect the hierarchy of the control (simplifying the naming container structure to the parent controls) but also a particular key value, such as the primary key. Consider the following code:

<asp:GridView ID="GridView1" runat="server"
ClientIDMode="Predictable"
RowClientIdSuffix="CustomerID">
...
</asp:GridView>

In this case, each row of the grid is identified by one or more columns in the data source with a trailing index. Here's an example:

Panel1_GridView1_ALFKI_1

The GridView is the sole control to support multiple columns. If you have multiple columns to concatenate, then you use the comma to separate names. The ListView will accept only one column, whereas the Repeater control will limit to trail a 0-based index and accept no column name.

Finally, note that the ClientIDMode property affects only the ID attribute of the resulting HTML element. By design, the name attribute remains unchanged.

View Controls Refinements

Most data-bound controls offer the ability to select a given displayed element -- mostly a row. In previous versions of ASP.NET, the selection was stored as the index of the selected item within the page. This means that for pageable controls (such as the GridView control), the same selection made on, say, page one is retained on page two unless you programmatically reset the selection during the page/changing event.

In ASP.NET 4.0, the current selection on a data-bound control is tracked via the value on the data key field you indicate through the DataKeyNames property. To enable this feature, you use the new PersistSelection Boolean property and set it to true. The default value is false for compatibility reasons.

In addition, the FormView and ListView controls offer a little better control over their generated HTML markup. In particular, the FormView now accounts for a brand new RenderTable Boolean property. If you set it to false (the default is true), then no extra HTML table tags will be emitted and the overall markup will be easier to style via CSS. The ListView no longer needs a layout template in ASP.NET 4.0:

<asp:ListView ID="ListView1" runat="server">
<ItemTemplate>
<% Eval("CompanyName")%>
<hr />
</ItemTemplate>
</asp:ListView>

The preceding code snippet is sufficient to repeat the content of the CompanyName column for each element in the data source.

HTML Enhancements

In the beginning, ASP.NET didn't offer much programmatic control over all possible tags of a Web page. For a long time, the title of a page missed an ad hoc property on the Page class. The same could be said for other popular features, like CSS files.

In ASP.NET 4.0, the Page class exposes two new string properties to let you set some common tags in the <head> section of a page. The two new properties are Keywords and Description. The content of these server properties will replace any content you may have indicated for the metatags as HTML literals.

The Keywords and Description properties can also be set directly as attributes of the @Page directive, as in the following example:

<%@ Page Language="C#"
AutoEventWireup="true"
CodeFile="Default.aspx.cs"
Inherits="_Default"
Keywords="ASP.NET, AJAX, 4.0"
Description="ASP.NET 4.0 Web Forms" %>

In ASP.NET, when you invoke Response.Redirect, you return the browser an HTTP 302 code, which means that the requested content is now available from another (specified) location. Based on that, the browser makes a second request to the specified address, and that's it. A search engine that visits your page, however, takes the HTTP 302 code literally. This is the actual meaning of the HTTP 302 status code in that the requested page has been temporarily moved to a new address. As a result, search engines don't update their internal tables and when the user clicks to see your page, the engine returns the original address. Next, the browser gets an HTTP 302 code and makes a second request to finally display the desired page.

To smooth the whole process, in ASP.NET 4.0 you can leverage a brand new redirect method, called RedirectPermanent. You use the method in the same way you use the classic Response.Redirect, except that this time, the caller receives an HTTP 301 status code. Code 301 actually means that the requested content has been permanently moved. For the browser, it makes no big difference, but it is a key difference for search engines.

Search engines know how to process an HTTP 301 code and use that information to update the page URL reference. Next time they display search results that involve the page, the linked URL is the new one. In this way, users can get to the page quickly and save themselves a second round-trip.

More Control over Output Caching

In ASP.NET 2.0, several key parts of the ASP.NET runtime were refactored and made much more flexible and configurable. This was achieved through the introduction of the provider model. The functionality of some ASP.NET core services, including session state, membership and role management, were abstracted to a sort of service contract so that different implementations of a given service could be used interchangeably and administrators could indicate the one they wanted from the configuration file.

For example, under the hood of the old faithful Session object, there's an instance of the HttpSessionState class whose content is retrieved by the selected session state provider. The default session state provider gets data from the in-process memory (specifically, from a slot within the Cache dictionary), but additional providers exist to store data in databases and external host processes.

In ASP.NET 4.0, the provider model covers another extremely important feature of ASP.NET that for some reason was left behind in previous versions: the output caching.

There are many situations where it is acceptable for a page response to be a little stale if this brings significant performance advantages. Think of an e-commerce application and its set of pages for a products catalog, for example. These pages are relatively expensive to create because they could require one or more database calls and likely some form of data join. Product pages tend to remain the same for weeks and are rarely updated more than once per day. Why should you regenerate the same page a hundred times per second? Since ASP.NET 1.0, output caching allows you to cache page responses so that following requests can be satisfied returning the cached output instead of executing the page. Figure 3 shows the sequence of application events, including the step where the system attempts to resolve the request looking into the output cache.

Figure 5 Split Script Files in the Microsoft AJAX Library for ASP.NET 4.0

File Goal
MicrosoftAjaxCore.js Core functionalities of the library
MicrosoftComponentModel.js Base classes for the client-side object model
MicrosoftAjaxSerialization.js JSON serialization
MicrosoftAjaxGlobalization.js Data and funtions for globalization
MicrosoftAjaxHistory.js History points and the client API for history management
MicrosoftAjaxNetwork.js Raw AJAX and HTTP functionalities
MicrosoftAjaxWebServices.js Wrapper client API for invoking services
MicrosoftAjaxApplicationServices.js Wrapper client API for predefined ASP.NET services such as authentication
MicrosoftAjaxTemplates.js Client-side rendering API for ASP.NET AJAX 4.0
MicrosoftAjaxAdoNet.js Wrapper client API for ADO.NET services in ASP.NET AJAX 4.0

 

Up until now, any page output (which can be grouped by form and query string parameters, requesting URL, or custom strings) is stored in memory in a private area of the ASP.NET Cache. In the long run, the amount of the output cache puts additional pressure on the Web server machine by consuming memory and generating frequent updates on the Cache object. In ASP.NET 4.0, the output caching subsystem fully supports the provider model, thus giving developers the opportunity of storing page responses outside the ASP.NET worker process.A custom output cache provider is a class that derives from OutputCacheProvider. The name of the class must be registered in the configuration file, as shown below:

<caching>
<outputCache defaultProvider="AspNetInternalProvider">
<providers>
<add name="DiskCacheProvider"
type="Samples.DiskCacheProvider, MyProvider"/>
</providers>
</outputCache>
</caching>

As usual, you can have multiple providers registered and select the default one via the defaultProvider attribute on the outputCache node. The default behavior is offered through the AspNetInternalProvider object that turns out to be the default provider, if you don't change anything in the configuration file.

Figure 7 The MicrosoftAjaxMode Enumeration

Value Description
Enabled Legacy option, indicates that just one file will be linked from the pages as in previous versions of ASP.NET.
Disabled No script file is automatically referenced from the ScriptManager control.
Explicit Script files to be referenced are explicitly listed in the Scripts section of the ScriptManager control.

The output cache provider doesn't have to be the same for all pages. You can choose a different provider on a per-request basis or even for a particular user control, page, or combination of parameters for a page. You can specify the provider name in the @OutputCache directive wherever this directive (page and/or user controls) is accepted:

<% @OutputCache Duration="3600"
VaryByParam="None"
providerName="DiskCache" %>

To change the provider on a per-request basis, instead, you need to override a new method in global.asax, as shown below:

{
// Decide which provider to use looking at the request
string providerName = ...;
return providerName;
}

Starting with version 2.0, session state can be stored outside the memory of the worker process. This means that any data stored in the Session object must be serialized to and from an out-of-process environment. If you look back at Figure 3, session state is loaded into the Session object around the AcquireRequestState application event. The in-memory content is then serialized back to storage at the end of the request processing.
ASP.NET 4.0 enables developers to request some compression to be applied to the data stream being sent in and out of the session provider (see Figure 4). Compression is obtained by using the GZipStream class instead of a plain Stream class to do any serialization:

<sessionState mode="SqlServer" compressionEnabled="true" ... />

To enable compression, you simply add the compressionEnabled attribute to the sessionState section of the configuration file. By default, compression is not enabled.

Any JavaScript library is made up of myriad specific JavaScript files with a more or less sophisticated graph of interconnections. ASP.NET AJAX, instead, always tried to abstract JavaScript details away from developers and offered a rather monolithic client JavaScript library, via the ScriptManager control. This will change in ASP.NET 4.0. The Microsoft AJAX client library has been refactored and split into the individual files listed in Figure 5. Figure 6 shows the dependencies between individual script files in the overall library.

A new property has been added to the ScriptManager control that lets you specify how the building blocks of the library should be handled. The property is MicrosoftAjaxMode, and it accepts values as shown in Figure 7.

Better Platform

ASP.NET 4.0 Web Forms contains a number of small-scale changes that, taken together, make it a better development platform. Web Forms is a mature framework that needs refinement, not redesign. If you don't feel entirely comfortable with Web Forms and are looking for something completely different, then ASP.NET MVC is worth a careful look.


Dino Esposito* is an architect at IDesign and co-author of "Microsoft .NET: Architecting Applications for the Enterprise" (Microsoft Press, 2008). Based in Italy, Esposito is a frequent speaker at industry events worldwide.*