Web Apps

An Overview Of The New Services, Controls, And Features In ASP.NET 2.0

Jeff Prosise

This article discusses:

  • New data controls
  • Membership and roles
  • Personalization and themes
  • Coding and compilation options
This article uses the following technologies:
ASP.NET 2.0

Code download available at:ASPNET20Overview.exe(138 KB)

Contents

Codebehind 2.0
New Dynamic Compilation Model
Precompiling and Deploying Without Source
Master Pages
Data Source Controls
Data Controls
Other New Controls
Membership Service
Login Controls
Role Management
Profiles
Data-Driven Site Navigation
URL Mapping
SQL Cache Dependencies
Validation Groups
Cross-Page Postbacks
Client Callback Manager (XML-HTTP)
Asynchronous Pages
Encrypted Configuration Sections
Conclusion

Since its introduction in 2002, ASP.NET has become the gold standard for Web applications run on servers powered by Microsoft® Windows®. In version 2.0 of the Microsoft .NET Framework, ASP.NET sheds its new-kid-on-the-block status. Its aim is to reduce the amount of code required to accomplish common Web programming tasks by 70 percent or more. New services, controls, and features make ASP.NET 2.0 almost as dramatic an improvement to ASP.NET 1.x as that was to ASP Classic.

Here's a broad overview of the new features in ASP.NET 2.0, with drill-downs in selected areas and sample programs to highlight key features. The numerous sample pages referred to in this article are part of a site named AspNet2Samples, which you can download from the MSDN®Magazine Web site.

Codebehind 2.0

ASP.NET 1.x supports two coding models: the inline model, in which markup and code coexist in the same ASPX file, and the codebehind model, which places markup in ASPX files and code in source code files. ASP.NET 2.0 introduces a third model: a new form of codebehind that relies on the new partial classes support in the Visual C#® and Visual Basic® compilers. Codebehind in ASP.NET 2.0 fixes a nagging problem with version 1.0: the requirement that codebehind classes contain protected fields whose types and names map to controls declared in the ASPX file.

Figure 1 demonstrates the new codebehind model. Hello.aspx contains the markup and Hello.aspx.cs contains the code. The Inherits attribute in the @ Page directive identifies the codebehind class, while the CodeFile attribute identifies the file containing the class. Note the absence of any fields in the Hello class providing mappings to controls in the ASPX file. Old-style codebehind is still supported, but this new model is now the preferred one. Not surprisingly, Visual Studio® 2005 supports the new model natively.

Figure 1 Codebehind 2.0

Hello.aspx

<%@ Page Language="C#" CodeFile="Hello.aspx.cs" Inherits="Hello" %> <html> <body> <form id="form1" runat="server"> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" /> <asp:Label ID="Label1" runat="server"></asp:Label> </form> </body> </html>

Hello.aspx.cs

using System; using System.Web.UI; using System.Web.UI.WebControls; public partial class Hello : System.Web.UI.Page { protected void Button1_Click(object sender, EventArgs e) { Label1.Text = TextBox1.Text; } }

New Dynamic Compilation Model

One of the many innovations introduced in ASP.NET 1.x was the ability for the runtime to compile code contained in ASPX files. ASP.NET 2.0 extends the dynamic compilation model so that virtually anything can be automatically compiled.

The bin directory is still there for compatibility, but it's now complemented by "special" directories named App_Code, App_GlobalResources, and App_LocalResources. C# and Visual Basic files in the App_Code directory and RESX files in the resources directories are automatically compiled by ASP.NET. Web Services Description Language (WSDL) files in the App_Code directory are compiled into Web service proxies, and XSD files are compiled into typed DataSets. Through web.config, these directories can be extended to support other file types as well.

Precompiling and Deploying Without Source

Speaking of dynamic compilation, one of the most common questions regarding ASP.NET 1.x is whether it's possible to precompile pages to avoid the compilation delay incurred the first time a page is accessed. The answer is the Visual Studio 2005 Build | Publish command, which precompiles an entire site and deploys it to the destination of your choice.

Another often-requested feature is the ability to precompile entire sites into managed assemblies that can be deployed without markup or source code, a capability that is especially interesting in hosting scenarios. The Visual Studio 2005 Publish Web Site dialog includes an "Allow this precompiled site to be updateable" box that, if unchecked, precompiles everything (including markup) and deploys empty ASPX files. Deploying without source doesn't provide ironclad protection of your intellectual property, since a clever person with physical access to the server could still decompile the generated assemblies, but it does raise the bar significantly for casual code snoopers.

Master Pages

One of the most glaring deficiencies of ASP.NET 1.x is its lack of support for page templates. You can't define a "master page" that other pages inherit from. Developers make up for this by composing pages from user controls, which are easily replicated from page to page, or by using HTTP modules and injecting common UI into each rendered page.

Such tricks are no longer necessary in ASP.NET 2.0 thanks to a new feature known as Master Pages. Think of "visual inheritance" and you'll understand what Master Pages are all about. First you define a Master Page containing content that you want to appear on other pages ("content pages") and use ContentPlaceHolder controls to define the locations where content pages can plug in content of their own. Then you build content pages—ASPX files—that reference the master using directives like this one:

<%@ Page MasterPageFile="~/Foo.master" %>

In the content pages, you use Content controls to fill in the placeholders in the Master Page. The ContentPlaceHolderID property on a Content control identifies the corresponding ContentPlaceHolder control in the Master Page. Bring up a content page in your browser and what appears is a perfect combination of the content defined in both the master and the content page.

All the pages in the AspNet2Samples site use Master Pages to achieve a common UI. The Master Page, Site.master, contains three ContentPlaceHolder controls: one for the banner at the top of every page, a second for the descriptive text underneath the banner, and a third for the content in the main body of the page.

Master Pages enjoy full support in the ASP.NET object model. The System.Web.UI.Page class features a new property named Master that permits a content page to programmatically reference its master and any public type members the master contains. A content page can call FindControl on the Master Page to access controls declared in the Master Page, or the Master Page can expose public members that wrap functionality exposed by its controls. Master Pages can be nested (although the designer in Visual Studio 2005 doesn't support nested Masters) and can include default content that can be overridden in content pages:

<asp:ContentPlaceHolder ID="Main" RunAt="server"> This is default content that will appear in content pages unless explicitly overridden. </asp:ContentPlaceHolder>

In addition, an application can designate a default Master Page in web.config, as shown here:

<configuration> <system.web> <pages masterPageFile="~/Foo.master" /> </system.web> </configuration>

Individual content pages enjoy the freedom to override the default and designate Master Pages of their own. There is no limit to the number of Master Pages a site can contain.

Data Source Controls

Data binding occupies a position of prominence in ASP.NET 1.x. A few well-placed lines of data binding code can replace reams of would-be ASP code that query a database and use repeated calls to Response.Write to render the query results into HTML. Data binding is even easier in ASP.NET 2.0, often requiring no imperative code at all, thanks to a new family of controls known as the data source controls (see Figure 2).

Figure 2 Data Source Controls

Figure 2** Data Source Controls **

Data source controls provide declarative access to data sources such as databases and XML files. SqlDataSource interfaces with any database for which a managed provider is available, including SQL Server™ databases and Oracle databases. The following example uses a SqlDataSource and a DataGrid to display data from the SQL Server Pubs database:

<asp:SqlDataSource ID="Titles" RunAt="server" ConnectionString="<%$ ConnectionStrings:Pubs %>" SelectCommand="select title_id, title from titles" /> <asp:DataGrid DataSourceID="Titles" RunAt="server" />

The SqlDataSource control's SelectCommand property defines the query performed on the data source, while the ConnectionString property specifies the connection string used to connect to the data source ($ ConnectionStrings is a new expression type that loads connection strings from the <connectionStrings> section of web.config). The DataGrid's DataSourceID property links the DataGrid to the SqlDataSource. When the page loads, the SqlDataSource control performs the query and provides the results to the DataGrid. SqlDataSource1.aspx demonstrates this style of declarative data binding.

Of course, data binding in the real world is rarely this simple. Suppose you want to cache the query results or parameterize database queries using items selected in other controls. SqlDataSource2.aspx uses one SqlDataSource to populate a dropdown list with the countries in Northwind's Customers table, and another SqlDataSource to populate a DataGrid with a list of customers in the country selected in the dropdown list:

<asp:SqlDataSource ID="SqlDataSource2" Runat="server" ConnectionString="<%$ ConnectionStrings:Northwind %>" SelectCommand="SELECT [CustomerID], [CompanyName], [Country] FROM [Customers] WHERE [Country]=@Country"> <SelectParameters> <asp:ControlParameter Name="Country" ControlID="DropDownList1" PropertyName="SelectedValue" /> </SelectParameters> </asp:SqlDataSource>

Note the parameterized SelectCommand in the SqlDataSource and the <asp:ControlParameter> element instructing the SqlDataSource to get the value for the @Country parameter from the dropdown list's SelectedValue property. Additional attributes, EnableCaching and CacheDuration, allow you to cache the results of the query.

The problem with SqlDataSource is that it accesses data sources directly, resulting in a two-tier data access structure. If you're building n-tier apps with data access components in the middle, you're probably more interested in ObjectDataSource, which lets you marry declarative data binding with data access components. The two most important properties of ObjectDataSource are TypeName, which specifies the type name of a data component, and SelectMethod, which specifies which method the ObjectDataSource can call on that component to query the data source.

The sample page ObjectDataSource.aspx is identical to SqlDataSource2.aspx on the outside, but on the inside, it uses an ObjectDataSource and a custom data component named Customers (located in the App_Code directory) to query the database and bind to the DataGrid. With ObjectDataSource, the data source doesn't have to be a database; it could be a Web service or anything else that you can front-end with a data access component.

These examples merely scratch the surface of data source controls. You can use stored procedures; parameterize queries using values extracted from query strings, user input, session state, and cookies; and you can specify whether the control should use a DataSet or DataReader. Because data source controls subsume the functionality of data adapters, you can even update databases using data source controls—known as two-way data binding.

Data Controls

The DataGrid is one of the most popular controls in ASP.NET, but in some ways it's a victim of its own success: it's so rich in functionality that it leaves ASP.NET developers wanting even more. The DataGrid control doesn't change much in ASP.NET 2.0, but the new GridView control offers features commonly requested in DataGrid and adds a few surprises of its own. In addition, new controls named DetailsView and FormView simplify the building of master-detail views and Web-editable user interfaces.

GridViews render HTML tables like DataGrids do, but unlike DataGrids they can page, sort, select, and even edit entirely on their own. GridViews also support a richer variety of column types ("field types" in GridView parlance) than DataGrids do, and they have smarter default rendering behavior, automatically rendering Boolean values, for example, with checkboxes. GridViews are easily paired with DetailsViews to create master-detail views. And GridView's AllowPagingAndSortingCallbacks property enables the postbacks that normally accompany paging and sorting operations to be replaced with lightweight XML-HTTP callbacks.

MasterDetail.aspx combines a GridView and a DetailsView to create a simple master-detail view of the Northwind database's Employees table. SqlDataSource controls feed the controls their data and a SelectParameter in the SqlDataSource bound to the DetailsView control enables the DetailsView to display the record currently selected in the GridView. You select records by clicking the GridView's Select buttons, which are present because of the AutoGenerateSelectButton="true" attribute in the <asp:GridView> tag. You can also perform alternating ascending and descending sorts by clicking column headers, thanks to the AllowSorting="true" attribute.

Other New Controls

Numerous enhancements to existing controls make those controls more versatile than ever before in building component-based Web pages. For example, the Panel control now has a DefaultButton property that specifies which button should be clicked if the user presses the Enter key while the Panel has the focus. In addition, ASP.NET 2.0 introduces more than 40 new control types to help you build rich Web UIs while insulating you from the vagaries of HTML, client-side script, and browser DOMs. Figure 3 lists the new control types.

Figure 3 New Controls in ASP.NET 2.0

Category Controls
Data source controls SqlDataSource, ObjectDataSource, XmlDataSource, AccessDataSource, SiteMapDataSource
Data controls GridView, DetailsView, FormView
Login controls Login, LoginName, LoginStatus, LoginView, CreateUserWizard, ChangePassword, PasswordRecovery
Navigation controls Menu, TreeView, SiteMapPath
Web Parts controls WebPartManager, WebPartZone, CatalogZone, EditorZone, ConnectionsZone, DeclarativeCatalogPart, PageCatalogPart, ImportCatalogPart, AppearanceEditorPart, BehaviorEditorPart, LayoutEditorPart, PropertyGridEditorPart
Other controls BulletedList, FileUpload, HiddenField, ImageMap, Wizard, WizardStep, MultiView, View, Substitution, Content, ContentPlaceHolder

Wizard.aspx demonstrates the new Wizard control, which simplifies the task of building Web UIs that step users through sequential operations. Individual steps are defined by WizardStep controls. The Wizard control serves as a container for WizardSteps and provides a default interface for stepping backward and forward. It's also capable of displaying a list of steps enabling users to randomly navigate between them, and it fires events that can be used to programmatically control step ordering. For more information, see Cutting Edge: The ASP.NET 2.0 Wizard Control.

Another interesting and potentially very useful control debuting in ASP.NET 2.0 is the MultiView control. Paired with View controls, MultiViews can be used to create pages containing multiple logical views. Only one view (the one whose index is assigned to the MultiView's ActiveViewIndex property) is displayed at a time, but you can switch views by changing the active view index. MultiView.aspx uses a MultiView control to show three views of the Northwind database. View switching is accomplished by selecting from a dropdown list. Note the AllowPaging attribute in the <asp:DetailsView> tag permitting users to browse records in the DetailsView.

The Substitution control supports the new post-cache substitution feature in ASP.NET 2.0, which permits output-cached pages and controls to replace cached content with dynamically generated content. The sample page Substitution.aspx demonstrates how post-cache substitution works. The current time displayed in the body of the page is updated every time you refresh the page, even though the page output is cached due to the @ OutputCache directive at the top. The current time is displayed by a Substitution control, whose MethodName property identifies a method called to retrieve dynamically generated content. In ASP.NET 2.0, the AdRotator control uses post-cache substitution to prevent content displayed by AdRotators from being affected by output caching.

Membership Service

One of the best new features of ASP.NET 2.0 is the membership service, which offers an easy-to-use API for creating and managing user accounts on secure sites, as well as storage for those accounts. ASP.NET 1.x introduced forms authentication to the masses, but still required you to write a fair amount of code to do real-world forms authentication. The membership service fills in the gaps and makes implementing forms authentication vastly simpler than before.

The membership API is exposed through two new classes: Membership and MembershipUser. The former represents the membership service itself and contains static methods for creating users, validating users, and more. MembershipUser represents individual users and contains methods and properties for changing passwords, fetching last-login dates, and the like. The following statement takes a user name and password and returns true or false, indicating whether the credentials are valid. It replaces calls to home-rolled methods in ASP.NET 1.x apps that were used to validate credentials using queries against Active Directory® or other back-end databases:

bool isValid = Membership.ValidateUser (username, password);

The next statement returns a MembershipUser object representing the user whose user name is "jeffpro":

MembershipUser user = Membership.GetUser ("jeffpro");

And this statement retrieves the registered user's e-mail address:

string email = user.Email;

Where are user names, passwords, and other data managed by the membership service stored? Like virtually all state-management services in ASP.NET 2.0, membership is provider-based. Providers are modules that permit services to interact with physical data sources. ASP.NET 2.0 includes membership providers for SQL Server databases (including SQL Server Express) and Active Directory. You, or third parties, can write providers for other data stores. Many companies have already modified the membership service to run on top of legacy user databases by building custom membership providers (for more information on adapting your 1.x authentication to the 2.0 provider model, see Who Goes There? Upgrade Your Site's Authentication with the New ASP.NET 2.0 Membership API). Figure 4 shows how the membership service is architected and the role that providers play in the service's operation.

Figure 4 Membership Service

Figure 4** Membership Service **

Login Controls

The membership service alone dramatically reduces the amount of code required to validate logins and manage users, but a new family of controls called the login controls makes forms authentication even easier. Login controls can be used with or without the membership service. When login controls and membership are used together, fundamental tasks such as validating user names and passwords and e-mailing forgotten passwords can often be accomplished with zero lines of code.

The Login control solicits users for user names and passwords, and offers a highly customizable UI. It's also capable of calling Membership.ValidateUser to validate user names and passwords. Following a successful login, it can forward users to the page they were trying to get to when they were redirected to the login page.

Of course, a user must have a user name and password before she can log in. The CreateUserWizard control makes short work of building pages for registering new users.

The LoginStatus control displays a link for logging in and logging out. To an unauthenticated user, it reads "Login" and goes to the login page. To an authenticated user, it reads "Logout" and logs the current user out if clicked. Both looks and behavior can be customized through properties such as LoginText and LogoutText. If you'd like to personalize a page by displaying the current user's user name, you can do that using the LoginName control, which is a declarative replacement for User.Identity.Name.

And then there's the LoginView control, which varies the content a user sees based on whether the user is authenticated and, if she is authenticated, what role or roles she belongs to. Figure 5 documents the schema used by LoginView. Unauthenticated users see the content declared in <AnonymousTemplate>, while authenticated users see the content in <LoggedInTemplate>. If you'd like to be even more granular and specify what an authenticated user sees based on role memberships, you can use a <RoleGroups> element. In this example, authenticated users who are administrators will see the content defined in the <ContentTemplate>.

Figure 5 LoginView Schema

<asp:LoginView ID="LoginView1" Runat="server"> <AnonymousTemplate> <!-- Content seen by unauthenticated users --> </AnonymousTemplate> <LoggedInTemplate> <!-- Content seen by authenticated users --> </LoggedInTemplate> <RoleGroups> <asp:RoleGroup Roles="Administrators"> <ContentTemplate> <!-- Content seen by administrators --> </ContentTemplate> </asp:RoleGroup> ... </RoleGroups> </asp:LoginView>

For a first-hand look at the membership service and login controls at work, refer to MembershipInfo.aspx, Login.aspx, and MembersOnly\MembershipUserInfo.aspx in the AspNet2Samples site. MembershipInfo uses the Membership class to display information about the membership service, and a LoginStatus control to display a login/logout link. Login.aspx contains a Login control for logging in and a CreateUserWizard control for creating new accounts. MembershipUserInfo.aspx uses the MembershipUser class to display information about the currently logged-in user.

The web.config file in the AspNet2Samples site configures the SqlMembershipProvider and other SQL-based providers to use the ASP.NET provider database aspnetdb. You can create that database by running the Aspnet_regsql.exe tool that comes with ASP.NET 2.0. If SQL Server Express is installed on your box and you'd prefer to use it instead, simply delete the two "LocalSqlServer" entries from the <connectionStrings> section of web.config, and the SQL Server Express database will be created automatically on first access.

Role Management

The membership service and login controls wouldn't be complete without support for role-based security. In ASP.NET 1.x, combining forms authentication with role-based security required you to write code to map role information onto each incoming request. The new role manager in ASP.NET 2.0, which can be used with or without the membership service, obviates the need for such code and simplifies the task of authorizing access to resources based on roles.

Role management is provider-based and is enabled by placing the following statement in web.config:

<roleManager enabled="true" />

The role manager exposes an API through the new Roles class, which contains methods with names such as CreateRole, DeleteRole, and AddUserToRole. But you may never have to call these methods because the new Web Site Administration Tool, pictured in Figure 6 and invoked with the Visual Studio 2005 Web site | ASP.NET Configuration command, is fully capable of creating roles, assigning users to roles, and so on. (This tool utilizes the ASP.NET 2.0 configuration API, which provides a fully featured and strongly typed read/write interface to configuration data.)

Figure 6 Web Site Administration Tool

Figure 6** Web Site Administration Tool **

Once role management is enabled, URL directives like this one can be used to restrict access to resources based on role memberships:

<authorization> <allow roles="Administrators" /> <deny users="*" /> </authorization>

In addition, roles can be referenced in LoginView controls to vary what a user sees based on role memberships, and roles can be used with site maps to customize navigation UIs based on role memberships.

Profiles

Another compelling new feature of ASP.NET 2.0 is profiles, which provide a ready-built solution to the problem of persistently storing personalization settings and other per-user data items. Currently, such data is typically stored in cookies, back-end databases, or a combination of the two. No matter where settings are stored, ASP.NET 1.x does little to help.

ASP.NET 2.0 profiles make it easy to store per-user data and to retrieve it at will. You begin by defining a profile in web.config:

<profile> <properties> <add name="Theme" /> <add name="Birthday" Type="System.DateTime" /> <add name="LoginCount" Type="System.Int32" defaultValue="0" /> </properties> </profile>

This profile defines three properties: a string named Theme, a DateTime value named Birthday, and an integer named LoginCount. The latter is assigned a default value of 0. (Yes, you can use custom data types in profiles, too. You can even use serializeAs attributes to specify how instances of those types are serialized.)

At run time, you access these properties for the current user using the page's Profile pseudo-property ("pseudo" because Profile isn't a property of System.Web.UI.Page, but is one that ASP.NET adds to dynamically compiled Page derivatives), which refers to an instance of a dynamically compiled class containing the properties defined in the profile. The following statements read the property values from the current user's profile:

string theme = Profile.Theme; DateTime birthday = Profile.Birthday; int logins = Profile.LoginCount;

Values can also be assigned to profile properties:

Profile.Theme = "SmokeAndGlass"; Profile.Birthday = new DateTime (1959, 9, 30); Profile.LoginCount = Profile.LoginCount + 1;

An obvious benefit of profiles is strong typing. Another benefit is that profile data is read and written on demand. Contrast that to session state, which is loaded and saved in every request whether it's used or not. But perhaps the biggest benefit of the profiles is that you don't have to explicitly store the data anywhere; the system does it for you, and it stores the data persistently so it'll be around when you need it. Profiles don't time out as sessions do.

So where is profile data stored? Profiles are provider-based, so you can configure them to use any of the available providers. ASP.NET 2.0 ships with one profile provider that works with SQL Server (including SQL Server Express) databases. Other types of data stores can be utilized by writing custom profile providers.

By default, ASP.NET uses authenticated user names to key profile data, but you can configure it to support anonymous users as well. First, enable anonymous identification by adding the following statement to web.config:

<anonymousIdentification enabled="true" />

Then add allowAnonymous="true" to the profile properties that you want to store for anonymous users:

<name="Theme" allowAnonymous="true" />

Now Theme will be available as a profile property regardless of whether callers to your site are authenticated.

By default, anonymous identification uses cookies to identify returning users. Attributes supported by <anonymousIdentification> allow these cookies to be configured in various ways. For example, you can specify the cookie name and indicate whether the cookie's contents should be encrypted. You can also configure anonymous identification to run in cookieless mode, whereupon it will rely on URL munging to identify returning users. There's even an autodetect option that uses cookies if the requesting browser supports them and URL munging if it doesn't.

Data-Driven Site Navigation

Developers spend lots of time building slick navigation UIs featuring navbars, dropdown/fly-out menus, and other eye candy. They spend even more time modifying those UIs when the structure of their site changes.

ASP.NET 2.0 vastly simplifies the development and maintenance of navigation UIs. You build a site map (typically stored as an XML file), slap a SiteMapDataSource control onto the page, and bind a Menu or TreeView control to the SiteMapDataSource. The SiteMapDataSource uses the default site map provider to read the site map, and then passes site map nodes to the Menu or TreeView, which renders the nodes into HTML. For good measure, you can add a SiteMapPath control to the page, too. SiteMapPath displays the familiar "bread-crumb" element showing the path to the current page. The controls do the hard work of rendering out HTML and JavaScript. And if the structure of your site changes, simply modify the site map and the changes propagate out to all the pages (especially if you declare the TreeViews and Menus in Master Pages). Figure 7 shows the components of the site navigation subsystem and illustrates how they fit together.

Figure 7 Data-Driven Site Navigation Schema

Figure 7** Data-Driven Site Navigation Schema **

The one drawback to site navigation is that XmlSiteMapProvider is the one and only site map provider included in the box with ASP.NET 2.0, which means that site maps must be stored in XML files. Even before ASP.NET 2.0 shipped, developers were clamoring for a means to store site maps in databases.

SQL Server site map providers are widely available. The June 2005 Wicked Code column presented one such provider named SqlSiteMapProvider. Unlike XmlSiteMapProvider, SqlSiteMapProvider reads site maps from SQL Server databases. An improved version that reloads the site map if the underlying database changes is available at Wicked Code: The SQL Site Map Provider You've Been Waiting For. It takes advantage of the ASP.NET 2.0 provider architecture to integrate seamlessly with the site navigation subsystem.

URL Mapping

Speaking of site navigation, ASP.NET 2.0 offers another new feature that's useful when the structure of your site changes but you want to preserve bookmarks to locations on the old site. Called URL mapping, that feature allows you to declaratively map links on the old site to links on the new one. The following configuration section, which lives in the <system.web> section of web.config, remaps old URLs such as Home.aspx to new ones:

<urlMappings enabled="true"> <add url="~/Home.aspx" mappedUrl="~/Default.aspx?tabindex=0" /> <add url="~/Forums.aspx" mappedUrl="~/Default.aspx?tabindex=1" /> <add url="~/Faq.aspx" mappedUrl="~/Default.aspx?tabindex=2" /> </urlMappings>

The same thing can be accomplished in ASP.NET 1.x by writing an HTTP module that programmatically maps old URLs to new ones. URL mapping is another example of how what used to require code can often be accomplished with no code in ASP.NET 2.0.

SQL Cache Dependencies

Another feature missing from ASP.NET 1.x is database cache dependencies. Items that are placed in the ASP.NET 1.x application cache can be keyed to other cached items and also to objects in the file system, but not to database entities. ASP.NET 2.0 corrects this error-by-omission-by introducing SQL cache dependencies.

SQL cache dependencies are represented by instances of the new SQLCacheDependency class. Using them is simple. If the SQL cache dependency targets a SQL Server 7.0 or SQL Server 2000 database, the following statement inserts a DataSet named ds into the cache and creates a dependency between the DataSet and the Northwind database's Products table:

Cache.Insert ("Products", ds, new SqlCacheDependency ("Northwind", "Products");

The first parameter passed to SqlCacheDependency's constructor isn't an actual database name; instead, it's a reference to an entry in the <databases> section of web.config. That entry, in turn, identifies a connection string in the <connectionStrings> section that ASP.NET can use to poll the database for changes:

<caching> <sqlCacheDependency enabled="true" pollTime="5000"> <databases> <add name="Northwind" connectionStringName="Northwind" /> </databases> </sqlCacheDependency> </caching>

If the target is a SQL Server 2005 database, you don't have to specify the database and table names; you simply wrap a SQLCacheDependency object around the command used to perform the query like this:

SqlCommand command = new SqlCommand ("...", connection); SqlCacheDependency dependency = new SqlCacheDependency (command); DataSet ds = ...; Cache.Insert ("Products", ds, dependency);

Rather than poll the database, ASP.NET uses SQL Server 2005 query notifications to receive an asynchronous callback if any of the data returned by the query changes.

SQL cache dependencies require no database preparation if used with SQL Server 2005. However, SQL Server 7.0 and SQL Server 2000 databases must be prepared to support SQL cache dependencies. Preparation is accomplished with the Aspnet_regsql.exe tool that comes with ASP.NET 2.0. You run the tool once to prepare the database and once more to prepare the table targeted by the SQL cache dependency. The Aspnet_regsql.exe tool creates triggers, stored procedures, and a special table that ASP.NET consults to determine whether changes have occurred. This table is periodically polled by a background thread using the configurable polling interval specified by the <sqlCacheDependency> element's pollTime attribute.

SQL cache dependencies can also be used with the ASP.NET output cache (via the @ OutputCache directive's new SqlDependency attribute) and with data source controls (via their SqlCacheDependency properties). For a SQL Server 7.0 or SQL Server 2000 database, you specify the database and table name, as in:

<%@ OutputCache SqlDependency="Northwind:Products" ... %>

For a SQL Server 2005 database, you write "CommandNotification" instead:

<%@ OutputCache SqlDependency="CommandNotification" ... %>

A final note regarding enhanced caching support in ASP.NET 2.0 is that the CacheDependency class is no longer sealed. You can derive from it to create custom cache dependency classes of your own. Can you spell RssCacheDependency?

Validation Groups

Validation controls are among the more brilliant innovations in ASP.NET 1.x. Controls such as RequiredFieldValidator and RegularExpressionValidator enable developers to do smart input validation on both the client and server without having to become experts in client-side scripting and browser DOMs. Unfortunately, version 1.x validation controls suffer from a major flaw: there's no good way to group them so that validators on one part of the page can override validators on another part of the page and allow postbacks to occur regardless of the state of the other validators.

The new validation groups feature in ASP.NET 2.0 fixes this problem once and for all. Validation controls can now be grouped using the new ValidationGroup property. Button controls are assigned to groups the same way, and when all of the validators in a group are satisfied with the input, they permit postbacks to occur from buttons in the same group. ValidationGroups.aspx in the sample download demonstrates the technique quite well. You can fill in either set of TextBoxes and post back by clicking the button in the corresponding validation group.

Cross-Page Postbacks

A common complaint about ASP.NET 1.x is that pages are only allowed to post back to themselves. That changes in version 2.0 with the introduction of cross-page posting. To set up cross-page posting, you designate the target page using the PostBackUrl property of the control that causes the postback to occur:

<asp:Button ID="Button1" Runat="server" Text="Postback to PageTwo.aspx" PostBackUrl="~/PageTwo.aspx" />

When clicked, the button posts back to PageTwo.aspx, which uses the new Page.PreviousPage property to acquire a reference to the originating page. A simple call to FindControl returns a reference to a TextBox declared in PageOne.aspx so the user's input can be retrieved, as shown in the following:

void Page_Load(object sender, EventArgs e) { if (PreviousPage != null && PreviousPage.IsCrossPagePostBack) { TextBox TextBox1 = (TextBox) PreviousPage.FindControl("TextBox1"); Label1.Text = "Hello, " + TextBox1.Text; } }

By default, PreviousPage returns a weakly typed reference to the page that originated the postback. If PageOne.aspx is the only page capable of posting to PageTwo.aspx, the latter could use the new @ PreviousPageType directive to gain strongly typed access to PageOne.aspx. For more information, see Extreme ASP.NET: Page Navigation.

Client Callback Manager (XML-HTTP)

One of my favorite new features in ASP.NET 2.0 is the "lightweight postback" capability offered by the new client callback manager. In the past, ASP.NET pages had to post back to the server to invoke server-side code. Postbacks are inefficient because they include all the postback data generated by the page's controls. They also force the page to refresh, resulting in an unsightly flashing.

ASP.NET 2.0 introduces a client callback manager that permits pages to call back to the server without fully posting back. Callbacks are asynchronous and are accomplished with XML-HTTP. They don't include postback data, and they don't force the page to refresh. (On the server side, the page executes as normal up to the PreRender event, but stops short of rendering any HTML.) They do require a browser that supports XML-HTTP, which generally rules out older browsers such as Internet Explorer 4.0 and Netscape Navigator.

Using the client callback manager entails three steps. First, call the new Page.GetCallbackEventReference method to obtain the name of a function that can be called from client-side script to perform an XML-HTTP callback. ASP.NET provides the function's name and implementation. Second, write a method in client-side script that will be called when the callback returns. That method's name is one of the arguments passed to GetCallbackEventReference. Third, implement the ICallbackEventHandler interface in the page. The interface contains two methods, RaiseCallbackEvent and GetCallbackResult. Both methods are called on the server side, when an XML-HTTP callback arrives. RaiseCallbackEvent is called first, followed by GetCallbackResult. The string that GetCallbackResult returns is returned to the completion method registered with GetCallbackEventReference.

XmlHttp.aspx demonstrates a use for XML-HTTP callbacks. It uses a GridView to display some fictitious stock prices stored on the server (in ~/App_Data/Stocks.xml), and it uses a JavaScript timer to fire off an XML-HTTP callback to server every five seconds to get the latest prices. When the callback returns, a few lines of JavaScript code update the GridView. The result? Live changes to data on the server cause live updates on the client, too—something that every Web developer has wished for at one time or another. Notice that the page doesn't repaint when an update occurs. Thanks to XML-HTTP, updates are quick and clean.

Asynchronous Pages

You didn't know that ASP.NET 2.0 supports asynchronous pages? It's one of the least publicized of all the new features, and yet it's a key building block for highly scalable Web sites.

When ASP.NET receives a request for a page, it grabs a thread from a thread pool and assigns that request to the thread. A normal, or synchronous, page holds onto the thread for the duration of the request, preventing the thread from processing other requests. If a synchronous request becomes I/O bound—for example, if it calls out to a remote Web service or queries a remote database and waits for the call to come back—then the thread assigned to the request is stuck doing nothing until the call returns. That impedes scalability because the thread pool has a finite number of threads available. If all request-processing threads are blocked waiting for I/O operations to complete, additional requests get queued up waiting for threads to be free. At best, throughput decreases because requests wait longer to be processed. At worst, the queue fills up and ASP.NET fails subsequent requests with 503 "Server Unavailable" errors.

Asynchronous pages offer a neat solution to the problems caused by I/O-bound requests. Page processing begins on a thread-pool thread, but that thread is returned to the thread pool once an asynchronous I/O operation begins in response to a signal from ASP.NET. When the operation completes, ASP.NET grabs another thread from the pool and finishes processing the request. Because thread-pool threads are used more efficiently, this can result in increased scalability. Threads that would otherwise be waiting for I/O to complete can now be used to service other requests. The direct beneficiaries are requests that don't perform lengthy I/O operations and can therefore get in and out of the pipeline quickly. Long waits to get into the pipeline have a disproportionately negative impact on the performance of such requests.

To build an asynchronous page, you begin by including an Async="true" attribute in the @ Page directive:

<%@ Page Async="true" ... %>

Then, sometime before the page's OnPreRenderComplete event, register a pair of methods: a Begin method to start an asynchronous operation and an End method to fetch the results once the operation has completed. You wrap the former method in a BeginEventHandler delegate and the latter in an EndEventHandler delegate. ASP.NET calls the Begin method before the page enters the rendering phase.

You launch an async operation (for example, an asynchronous database query or an asynchronous Web service call) and return an IAsyncResult interface that ASP.NET uses to monitor the pending operation for completion. Upon completion, ASP.NET calls your End method, where you do whatever's necessary to finish up (for example, retrieve the results of an asynchronous query).

AsyncDataBind.aspx demonstrates asynchronous data binding—powerful stuff that can dramatically increase throughput on sites that rely heavily on remote databases. Page_Load calls the new Page.AddOnPreRenderCompleteAsync method to register Begin and End methods. (A related method, RegisterAsyncTask, can be called in lieu of AddOnPreRenderCompleteAsync to register a time-out method as well as Begin and End methods. RegisterAsyncTask has other advantages, too, including the ability to launch multiple asynchronous operations in parallel and delay rendering until all operations have completed.) The Begin method launches an asynchronous database query by calling SqlCommand.BeginExecuteReader, and the End method retrieves a SqlDataReader encapsulating the results using SqlCommand.EndExecuteReader. Later, in Page_PreRenderComplete, the page binds the SqlDataReader to a GridView control.

That's just one way to use the new asynchronous page infrastructure in ASP.NET 2.0. For more information, see the October 2005 Wicked Code column in MSDN Magazine (Wicked Code: Asynchronous Pages in ASP.NET 2.0).

Encrypted Configuration Sections

Most web.config files contain database connection strings and other information that could be used for malicious purposes if it fell into the wrong hands. Security-minded developers encrypt such information to protect it from prying eyes. ASP.NET 1.x offered little help in this regard, but ASP.NET 2.0 makes it so easy to encrypt configuration data—indeed, entire sections of web.config—there's no excuse for storing sensitive configuration data in plain text.

Aspnet_regiis.exe has been enhanced to encrypt and decrypt configuration sections. The following command encrypts the <connectionStrings> section of the web.config file in C:\Websites\MyComix:

aspnet_regiis –pef connectionStrings c:\websites\mycomix

And this command decrypts <connectionStrings>:

aspnet_regiis –pdf connectionStrings c:\websites\mycomix

Encryption is transparent to your code. If your app reads a connection string from an encrypted <connectionStrings> section, ASP.NET automatically returns a plain text connection string. All but a handful of configuration sections can be encrypted, and you don't have to write a single line of code to encrypt or decrypt the data. How good is that?

ASP.NET supports two encryption schemes out of the box: one that uses the Windows Data Protection API (DPAPI) and an autogenerated encryption key, and another that uses RSA public/private-key encryption. The architecture is extensible, too. You can plug in encryption algorithms of your own by writing protected configuration providers.

Conclusion

ASP.NET 2.0 includes other new features not discussed here. For example, Web Parts provide a framework for building SharePoint-style portals. Content localization is easier than ever before, thanks to autocompiled RESX files, $ Resources expressions for loading resources declaratively, and new declarative means for mapping language preferences in HTTP headers to CultureInfo objects. Themes and skins permit controls, pages, and even entire sites to be visually themed using simple configuration directives. And the new Web events subsystem (also known as "health monitoring") provides a rich and customizable platform for logging failed logins and other important events that occur over an application's lifetime.

The time to learn about ASP.NET 2.0 is now. Your ASP.NET 1.x applications should run without modification on version 2.0. The new platform is backwards compatible, but the future is ASP.NET 2.0, and that future is one of richer functionality and less code.

Jeff Prosise is a contributing editor to MSDN Magazine and the author of several books, including Programming Microsoft .NET (Microsoft Press, 2002). He's also a cofounder of Wintellect, a software consulting and education firm that specializes in the Microsoft .NET Framework.