Share via


[Editor's Update - 1/20/2006: This article refers to a beta version of Visual Studio 2005. An updated version of the article, reflecting features found in the final release of Visual Studio 2005, can be found at Web Apps: An Overview Of The New Services, Controls, And Features In ASP.NET 2.0.]

The Big Story

An Overview of the New Services, Controls, and Features in ASP.NET 2.0

Jeff Prosise

This article is based on the March 2004 Community Technology Preview of ASP.NET 2.0. All information contained herein is subject to change.

This article discusses:

  • New data controls
  • Administration and roles
  • Personalization and themes
  • Coding and compilation options
This article uses the following technologies:
ASP.NET 1.x and C#

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

Contents

Master Pages
Data Source Controls
Themes and Skins
New Controls
The GridView and DetailsView Controls
What's New in Administration
Membership Service
Login Controls
Role Manager
Personalization
SQL Cache Dependencies
New Dynamic Compilation Model
Precompiling and Deploying Without Source
New Code Separation Model
Client Callback Manager
Validation Groups
Cross-page Posting
Conclusion

In the four short years since it was first unveiled, ASP.NET has become the gold standard for Web applications run on servers powered by Windows®, adding runat="server" to the vocabularies of Web developers everywhere. It has also provided a revealing glimpse into the future of Web programming—a future that revolves around server-side controls that render HTML and script, and that fire events.

In the next major release of the Microsoft® .NET Framework, ASP.NET 2.0 sheds its new-kid-on-the-block status and matures into a full-blown adult. Its aim is to reduce the amount of code required to accomplish common Web programming tasks by 70 percent or more. The goal is a lofty one, but also one that's achievable thanks to a diverse assortment of new services, controls, and features that promise to make ASP.NET 2.0 almost as dramatic an improvement to ASP.NET 1.x as ASP.NET 1.x was to ASP.

Here I'll provide a broad overview of what you can expect to see in ASP.NET 2.0, with drill-downs in selected areas and sample programs to highlight key features. All code samples were built and tested against a pre-beta build of ASP.NET 2.0, and some code samples may have to be modified to work with the first beta.

Master Pages

One of the most glaring deficiencies of ASP.NET 1.x is its lack of support for page templates. Missing is the ability to 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. 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 and use ContentPlaceHolder controls to define the locations where subpages can plug in content of their own. Then you build subpages—ASPX files—that reference the master using directives like this one:

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

In the subpages, you use Content controls to fill in the placeholders in the Master Page. Bring up a subpage in your browser and the content that appears is a perfect combination of the content defined in both the master and the subpage.

The application that is shown in Figure 1 uses a Master Page to define a header and footer that appear on every page. The subpage inserts content between the header and the footer by plugging a Content control into the master's ContentPlaceHolder. You should note the matching ID and ContentPlaceHolderID, and the @ Master directive in the Master Page.

Figure 1 Master Pages

Master.master

<%@ Master %> <html> <body leftmargin="0" topmargin="0" rightmargin="0" bottommargin="0" marginheight="0" marginwidth="0"> <!-- Banner --> <table cellspacing="0" cellpadding="0" style="background-image: url('images/stripes.gif'); background-repeat: repeat-x, repeat-y" width="100%"> <tr><td align="center"> <span style="font-family: verdana; font-size: 36pt; font-weight: bold; color: white">Master Pages</span><br> <span style="font-family: verdana; font-size: 10pt; font-weight: normal; color: white">This banner provided by Master.master</span> </td></tr> </table> <!-- Placeholder for content between banner and footer --> <asp:ContentPlaceHolder ID="Main" RunAt="server" /> </form> <!-- Footer --> <table width="100%"><tr><td align="center"> <span style="font-family: verdana; font-size: 8pt; color: red"> Copyright (c) 2004 by Me Inc. All rights reserved<br> This footer provided by Master.master </span> </td></tr></table> </body> </html>

Subpage.aspx

<%@ Page MasterPageFile="~/Master.master" %> <asp:Content ContentPlaceHolderID="Main" RunAt="server"> <table width="100%" height="256px"><tr><td align="center"> <h2>This content provided by Subpage.aspx</h2> </td></tr></table> </asp:Content>

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 subpage to programmatically reference its master and the controls defined therein. Master Pages can be nested and can include default content that can be overridden in subpages:

<asp:ContentPlaceHolder ID="Main" RunAt="server"> This is default content that will appear in subpages 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 subpages enjoy the freedom to override the default and designate Master Pages of their own.

The icing on the cake is the Master Page support in Visual Studio® 2005. When you load a subpage, the IDE shows a grayed-out, read-only version of the content defined in the master, and a full-color, fully editable version of the content defined in the subpage. Distinguishing between the two is easy, and if you want to edit content that belongs to the Master Page, all you have to do is simply open the master in the IDE.

For a more in-depth look at Master Pages, refer to Fritz Onion's article on the subject in this issue of MSDN® Magazine.

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 ASP code that query a database and use repeated calls to the Response.Write method to render the query results into HTML. Data binding is even easier in ASP.NET 2.0, often requiring no code at all thanks to the new data source controls listed in the "New Controls Planned for ASP.NET 2.0" sidebar on the following page.

The following DataSource1.aspx page uses ASP.NET 2.0 data binding to display a portion of the SQL Server Pubs database:

<html> <body> <form runat="server"> <asp:SqlDataSource ID="Titles" RunAt="server" ConnectionString="server=localhost;database=pubs;Integrated Security=SSPI" SelectCommand="select title_id, title, price from titles" /> <asp:DataGrid DataSourceID="Titles" RunAt="server" /> </form> </body> </html>

The SqlDataSource control defines the data source and the query performed on it, and the DataGrid's DataSourceID property points to the SqlDataSource. When the page loads, the SqlDataSource control performs the query and feeds the results to the DataGrid.

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. The page in Figure 2 uses one SqlDataSource to populate a dropdown list with the countries listed 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. Note the <SelectParameters> element instructing the DataGrid's SqlDataSource to get the value for @country from the dropdown list. Also note the EnableCaching and CacheDuration attributes in the SqlDataSource bound to the dropdown list. These declarations cache the results of the SELECT DISTINCT query for 60 seconds.

Figure 2 DataSource2.aspx

<html> <body> <form runat="server"> <asp:SqlDataSource ID="Countries" RunAt="server" ConnectionString="server=localhost;database=northwind; Integrated Security=SSPI" SelectCommand="select distinct country from customers order by country" EnableCaching="true" CacheDuration="60" /> <asp:SqlDataSource ID="Customers" RunAt="server" ConnectionString="server=localhost;database=northwind; Integrated Security=SSPI" SelectCommand="select * from customers where country=@country"> <SelectParameters> <asp:ControlParameter Name="Country" ControlID="MyDropDownList" PropertyName="SelectedValue" /> </SelectParameters> </asp:SqlDataSource> <asp:DropDownList ID="MyDropDownList" DataSourceID="Countries" DataTextField="country" AutoPostBack="true" RunAt="server" /> <asp:DataGrid DataSourceID="Customers" RunAt="server" /> </form> </body> </html>

These examples merely scratch the surface of what's possible with data source controls. For example, 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. Expect to see a lot written about data source controls as ASP.NET 2.0 nears final release. Dino Esposito covers them in much greater detail in his article in this issue of MSDN Magazine.

While I'm on the subject of data binding, you should also know that ASP.NET 2.0 supports a simplified data-binding syntax. ASP.NET developers can find expressions like this one imposing:

<%# DataBinder.Eval (Container.DataItem, "title") %>

In ASP.NET 2.0, the same expression can be written like this:

<%# Eval("title") %>

In addition to the Eval operator, ASP.NET 2.0 supports operators named XPath and XPathSelect that use XPath expressions to target data in XML documents.

Themes and Skins

ASP.NET pages can be pretty boring if you don't use attributes to dress up controls. The problem is that up to now, attributes had to be applied one at a time and you weren't able to "theme" controls by setting their visual attributes as a group. Enter themes and skins, a new feature of ASP.NET 2.0 that simplifies the task of building great-looking pages.

To see themes and skins in action, add the following directive to DataSource2.aspx, shown in Figure 2:

<%@ Page Theme="BasicBlue" %>

Now refresh the page. The result is shown in Figure 3.

Figure 3 Themes and Skins in Action

Figure 3** Themes and Skins in Action **

Next, give the page a completely different look by adding a <head runat="server" /> element and changing the @ Page directive to indicate a page theme to use:

<%@ Page Theme="SmokeAndGlass" %>

The @ Page directive's new Theme attribute declaratively applies a theme to a page. Themes can also be applied programmatically using the Page class's Theme property. A theme is a collection of skins, and a skin is a set of visual attributes applied to a control type. BasicBlue and SmokeAndGlass are two of several predefined, or global, themes provided with ASP.NET 2.0. You'll find them in subdirectories underneath Microsoft.NET\Framework\...\ ASP.NETClientFiles\Themes.

You can define themes and skins of your own and privately deploy them in subdirectories stemming from an application's Themes directory. Each subdirectory constitutes a theme, and the theme name equals the subdirectory name. A theme subdirectory contains one or more .skin files as well as any other resources used by the theme, such as image files and style sheets. The actual skin definitions are contained in the .skin files and look very much like the tags used to declare control instances in ASPX files.

To demonstrate this, create a subdirectory named Themes in the folder where DataSource2.aspx is located. In the Themes directory, create a subdirectory named ShockingPink. Copy the following to a new .skin file in the ShockingPink directory:

<asp:DropDownList runat="server" BackColor="hotpink" ForeColor="white" /> <asp:DataGrid runat="server" BackColor="#CCCCCC" BorderWidth="2pt" BorderStyle="Solid" BorderColor="#CCCCCC" GridLines="Vertical" HorizontalAlign="Left"> <HeaderStyle ForeColor="white" BackColor="hotpink" /> <ItemStyle ForeColor="black" BackColor="white" /> <AlternatingItemStyle BackColor="pink" ForeColor="black" /> </asp:DataGrid>

Next, change the @ Page directive in DataSource2.aspx to read:

<%@ Page Theme="ShockingPink" %>

The result of these actions is undoubtedly one of the most garish Web pages ever created!

ShockingPink.skin defines a default look for DropDownList and DataGrid controls. Skin files don't have to have the same names as the themes they belong to, although they often do. A theme can contain multiple .skin files, and a single .skin file can define attributes for any number of control types. Additionally, ASP.NET distinguishes between default skins and non-default skins. A skin that lacks a SkinID attribute is, by definition, a default skin. Non-default skins include SkinIDs that can be referenced with SkinID attributes in control tags.

New Controls

ASP.NET 2.0 will introduce about 50 new control types to help you build rich UIs while insulating you from the vagaries of HTML, client-side script, and browser Document Object Models (DOMs). The "New Controls" sidebar lists the new controls planned at the time of this writing, excluding Web Parts controls.

The DynamicImage control simplifies the task of displaying dynamically generated images in Web pages. In the past, developers often wrote custom HTTP handlers for dynamic image generation, or worse, generated images in ASPX files. DynamicImage renders both techniques obsolete. The code in Figure 4 uses a DynamicImage control to draw a pie chart. The key statement is the one that assigns the image bits to the control's ImageBytes array.

Figure 4 DynamicImage.aspx

<%@ Import Namespace="System.Drawing" %> <%@ Import Namespace="System.Drawing.Imaging" %> <%@ Import Namespace="System.IO" %> <html> <body> <asp:DynamicImage ID="PieChart" DynamicImageType="ImageBytes" RunAt="server" /> </body> </html> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { // Create a bitmap and draw a pie chart Bitmap bitmap = new Bitmap (240, 180, PixelFormat.Format32bppArgb); Graphics g = Graphics.FromImage (bitmap); DrawPieChart (g, Color.White, new decimal[] { 100.0m, 200.0m, 300.0m, 400.0m }, 240, 180); g.Dispose(); // Attach the image to the DynamicImage control MemoryStream stream = new MemoryStream (); bitmap.Save (stream, ImageFormat.Gif); bitmap.Dispose(); PieChart.ImageBytes = stream.ToArray (); } void DrawPieChart (Graphics g, Color bkgnd, decimal[] vals, int width, int height) { // Erase the background SolidBrush br = new SolidBrush (bkgnd); g.FillRectangle (br, 0, 0, width, height); br.Dispose (); // Create an array of brushes SolidBrush[] brushes = new SolidBrush[6]; brushes[0] = new SolidBrush (Color.Red); brushes[1] = new SolidBrush (Color.Yellow); brushes[2] = new SolidBrush (Color.Blue); brushes[3] = new SolidBrush (Color.Cyan); brushes[4] = new SolidBrush (Color.Magenta); brushes[5] = new SolidBrush (Color.Green); // Sum the inputs decimal total = 0.0m; foreach (decimal val in vals) total += val; // Draw the chart float start = 0.0f; float end = 0.0f; decimal current = 0.0m; for (int i=0; i<vals.Length; i++) { current += vals[i]; start = end; end = (float) (current / total) * 360.0f; g.FillPie (brushes[i % 6], 0.0f, 0.0f, width, height, start, end - start); } // Clean up and return foreach (SolidBrush brush in brushes) brush.Dispose (); } </script>

The DynamicImage control takes advantage of the new ASP.NET 2.0 image-generation service. Another way to access the image-generation service is to build images dynamically in ASIX files which are brand new in ASP.NET 2.0. The sample files accompanying this article (available on the MSDN Magazine Web site) include one named DynamicImage.asix that demonstrates the basics of ASIX files. To run it, copy DynamicImage.asix to a virtual directory on your Web server and call it up in your browser.

Another interesting and potentially very useful control debuting in ASP.NET 2.0 is the MultiView control. Paired with View controls, MultiView 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. MultiViews are ideal for pages that use tabs or other controls to allow users to navigate between logical pages.

The page in Figure 5 uses a MultiView to show two different views of the Pubs database's Titles table: one rendered with a GridView and another with a DetailsView. 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.

Figure 5 MultiView.aspx

<%@ Page Theme="BasicBlue" %> <html> <body> <form runat="server"> <asp:SqlDataSource ID="Titles" RunAt="server" ConnectionString="server=localhost;database=pubs; Integrated Security=SSPI" SelectCommand="select title_id, title, price from titles" /> <asp:DropDownList ID="ViewType" AutoPostBack="true" OnSelectedIndexChanged="OnSwitchView" RunAt="server"> <asp:ListItem Text="GridView" Selected="true" RunAt="server" /> <asp:ListItem Text="DetailsView" RunAt="server" /> </asp:DropDownList> <asp:MultiView ID="Main" ActiveViewIndex="0" RunAt="server"> <asp:View RunAt="server"> <asp:GridView DataSourceID="Titles" RunAt="server" /> </asp:View> <asp:View RunAt="server"> <asp:DetailsView DataSourceID="Titles" AllowPaging="true" RunAt="server" /> </asp:View> </asp:MultiView> </form> </body> </html> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { if (IsPostBack) DataBind (); } void OnSwitchView (Object sender, EventArgs e) { Main.ActiveViewIndex = ViewType.SelectedIndex; } </script>

The GridView and DetailsView 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 two new controls named GridView and DetailsView offer features commonly requested in the DataGrid control, as well as adding a few surprises of their own.

GridViews render HTML tables like DataGrids do, but unlike DataGrids they can page and sort entirely on their own. GridViews also support a richer variety of column types (field types in GridView parlance) than DataGrids, and they have smarter default rendering behavior, automatically rendering Boolean values, for example, with checkboxes. GridViews are also easily paired with DetailsViews to create master-detail views. The chief deficiency of the GridView control is that, like DataGrid, it does most of its work by posting back to the server.

The page in Figure 6 combines a GridView and a DetailsView to create a simple master-detail view of the Pubs database's Titles 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.

Figure 6 MasterDetail.aspx

<%@ Page Theme="BasicBlue" %> <html> <body> <form runat="server"> <asp:SqlDataSource ID="Titles1" RunAt="server" ConnectionString="server=localhost;database=pubs;Integrated Security=SSPI" SelectCommand="select title_id, title, price from titles" /> <asp:SqlDataSource ID="Titles2" RunAt="server" ConnectionString="server=localhost;database=pubs;Integrated Security=SSPI" SelectCommand="select title_id, title, price from titles where title_id=@title_id"> <SelectParameters> <asp:ControlParameter Name="title_id" ControlID="MyGridView" PropertyName="SelectedValue" /> </SelectParameters> </asp:SqlDataSource> <table><tr><td> <asp:GridView ID="MyGridView" DataSourceID="Titles1" Width="100%" RunAt="server" AutoGenerateColumns="false" SelectedIndex="0" AutoGenerateSelectButton="true" DataKeyNames="title_id"> <Columns> <asp:BoundField HeaderText="Title ID" DataField="title_id" /> <asp:BoundField HeaderText="Book Title" DataField="title" /> <asp:BoundField HeaderText="Price" DataField="price" DataFormatString="{0:c}" NullDisplayText="TBD" /> </Columns> </asp:GridView> </td></tr> <tr><td> <asp:DetailsView DataSourceID="Titles2" RunAt="server" AutoGenerateRows="false" Width="100%"> <Fields> <asp:BoundField HeaderText="Title ID" DataField="title_id" /> <asp:BoundField HeaderText="Book Title" DataField="title" /> <asp:BoundField HeaderText="Price" DataField="price" DataFormatString="{0:c}" NullDisplayText="TBD" /> </Fields> </asp:DetailsView> </td></tr></table> </form> </body> </html>

Note the <Columns> and <Fields> elements defining field types in the GridView and DetailsView controls. These are morally equivalent to <Columns> elements in DataGrid controls. Figure 7 lists the supported field types. Of particular interest are ImageField and DropDownListField, which single handedly eliminate much of the code developers write today to include images and data-bound dropdown lists in DataGrids.

Figure 7 GridView and DetailsView Field Types

Field Type Description
AutoGeneratedField Default field type
BoundField Bound to a specific column in the data source
ButtonField Displays a push button, image button, or link button
CheckBoxField Displays a checkbox
CommandField Displays buttons for selecting and editing items
DropDownListField Displays a dropdown list
HyperLinkField Displays a hyperlink
ImageField Displays an image
TemplateField Content is defined by HTML templates

What's New in Administration

Another glaring deficiency of ASP.NET 1.x that's been fixed in ASP.NET 2.0 is the complete lack of interfaces, either declarative or programmatic, for administering Web sites. In the past, changing a configuration setting meant firing up Notepad and editing Machine.config or Web.config. Not any more. ASP.NET 2.0 features a full-blown administration API that simplifies the task of reading and writing configuration settings. It also includes an administrative GUI which is displayed by requesting Webadmin.axd in your browser, as shown in Figure 8.

Figure 8 Administrative GUI

Figure 8** Administrative GUI **

Though incomplete at the time of this writing, Webadmin.axd is designed to allow you to configure the various services included in ASP.NET 2.0 (such as the membership and role management services), view Web site statistics, and apply security settings.

Membership Service

One of the best new features of ASP.NET 2.0 is the new membership service, which provides an easy-to-use API for creating and managing user 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 the gaps in the ASP.NET 1.x forms authentication offerings and makes implementing forms authentication vastly simpler than before.

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

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");

This statement retrieves a registered user's e-mail address (assuming an e-mail address is recorded):

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 will include membership providers for Microsoft Access databases, SQL Server databases, Active Directory, and perhaps other data stores. You, or third parties, can write providers for others. Rob Howard, a program manager on the Microsoft Web Platform and Tools team, provides more detail on providers in his "Nothin' But ASP.NET" column.

By default, the membership service uses the Access provider and stores membership data in a file named AspNetDB.mdb in the application's Data subdirectory. Alternative providers can be selected through the <membership> section of Web.config. Rather than modify Web.config yourself, you can let Webadmin.axd modify it for you. The following excerpt is from Web.config after Webadmin.axd created a SQL Server™ database named WhidbeyLogin and configured the membership service to use it:

<membership defaultProvider="WhidbeyLogin"> <providers> <add name="WhidbeyLogin" type="System.Web.Security.SqlMembershipProvider, ..." connectionStringName="webAdminConnection632112624221191376" applicationName="/Whidbey" requiresUniqueEmail="false" enablePasswordRetrieval="true" enablePasswordReset="false" requiresQuestionAndAnswer="false" passwordFormat="Encrypted" /> </providers> </membership>

The connectionStringName attribute references a connection string in the new <connectionStrings> section of Web.config. ASP.NET 2.0 will include the ability to encrypt that part of Web.config in order to safeguard database connection strings.

The uses of Webadmin.axd aren't limited to creating databases and selecting membership providers: it can also be used to create users, manage credentials, and more. Between Webadmin.axd and the membership API, both declarative and programmatic means exist for managing registered users of your site. That's a huge step forward from ASP.NET 1.x, which left the problem of credentials management largely up to you.

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, but they integrate so well with that service that 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 "New Controls" sidebar includes a list of the login controls slated to ship with ASP.NET 2.0.

Figure 9 The Login Control

Figure 9** The Login Control **

The Login control, shown in Figure 9, is the centerpiece of the family. In addition to offering a highly customizable UI, it's also capable of calling Membership.ValidateUser to validate user names and passwords. The Login control can also call FormsAuthentication.RedirectFromLoginPage to redirect users to the pages they were trying to get to when they were redirected to the login page. FormsAuthentication.RedirectFromLoginPage would then issue authentication cookies. You'll see Login and other login controls in action a little later in this article.

Role Manager

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 roles 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 the user's access to various resources based on roles.New Controls Planned for ASP.NET 2.0

Simple Controls Description
BulletedList Displays bulleted lists of items
FileUpload Permits files to be uploaded through Web pages
HiddenField Represents hidden fields (<input type="hidden">)
ImageMap Represents HTML image maps
Data Source Controls Description
AccessDataSource For data binding to Microsoft Access databases
DataSetDataSource For data binding to non-hierarchical XML data
ObjectDataSource For data binding to classes implemented in custom data layers
SiteMapDataSource For data binding to XML site maps
SqlDataSource For data binding to SQL databases using ADO.NET providers
XmlDataSource For data binding to XML documents
Login Controls Description
Login Interface for logging in with user names and passwords
LoginName Renders the names of authenticated users
LoginStatus Interface for logging in and out
LoginView Shows one view for authenticated users and another for unauthenticated users
ChangePassword Interface for changing passwords
PasswordRecovery Interface for e-mailing forgotten passwords
CreateUserWizard Interface for creating new accounts
Rich Controls Description
Content Defines content for placeholders inherited from Master Pages
DetailsView Renders individual records into HTML tables
DynamicImage Represents dynamically generated images
FormView Templated data-binding control with highly flexible UI
GridView Super DataGrid; renders sets of records into HTML tables
Menu Displays dropdown and fly-out menus
MultiView Partitions a page into multiple logical views
SiteMapPath Displays "cookie crumbs" showing paths to pages
TreeView Renders hierarchical data as expandable/collapsible trees
View Used with MultiView to define individual views
Wizard Guides users through step-wise procedures
Mobile Controls Description
Pager Partitions pages for rendering to small devices
PhoneLink Dials phone numbers on phone-enabled devices

Role management is provider based and is enabled through Web.config. The role manager exposes an API through the new Roles class, which exposes methods with names such as CreateRole, DeleteRole, and AddUserToRole. Significantly, you may never have to call these methods because Webadmin.axd is fully capable of creating roles, assigning users to roles, and so on. Once enabled, role-based security "just works" using the role information provided and URL authorization directives in Web.config files—the same URL authorizations that you're already familiar with from ASP.NET 1.x.

Now that you're acquainted with the membership service, the login controls, and the ASP.NET role manager, you might want to see an example that uses all three. The code samples that you can download for this article include a two-page application that demonstrates forms authentication in the style of Visual Studio 2005. To deploy the app and take it for a test drive, first copy PublicPage.aspx, LoginPage.aspx, and Web.config to a virtual directory on your Web server. Create a subdirectory named Secure in the virtual directory and copy ProtectedPage.aspx and the other Web.config file into it.

Fire up Webadmin.axd and configure the site to use forms authentication and the membership and role services to use the provider of your choice. Also create users named Bob and Alice and roles named Manager and Developer. Assign Bob to the role of Manager and Alice to the role of Developer. (I won't list all the steps because they'll probably change before you read this anyway. Fortunately, Webadmin.axd is reasonably intuitive, and it has wizards to guide you through the setup process.)

Next, call up PublicPage.aspx in your browser and click the "View Secret Message" button to view ProtectedPage.aspx. ASP.NET will redirect you to LoginPage.aspx, which uses a Login control to solicit a user name and password. Log in using Bob's user name and password. ProtectedPage.aspx should appear in the browser window because Bob is a Manager and the Web.config file in the Secure directory allows access to managers. Note the user name displayed by the LoginName control and the "Log out" link displayed by the LoginStatus control. Finally, close the browser, then reopen it and call up PublicPage.aspx again. Click "View Secret Message" and log in as Alice. This time you can't get to ProtectedPage.aspx because Alice isn't a manager.

I used a similar application to teach forms authentication in ASP.NET 1.x, but the 1.x version required significantly more code. The 2.0 version is remarkable for its brevity, particularly for the absence of any code to validate credentials typed into the login form or map user names to roles. If you're still not convinced, try implementing the same app in ASP.NET 1.x! Also, be sure to check out the changes made by Webadmin.axd to Web.config. Among other things, you should see a <roleManager> element enabling the role manager and perhaps designating a role management provider.

You might be curious to know whether the role manager makes a round-trip to the database where roles are stored in each and every request. The answer, fortunately, is no. It encodes roles in cookies and encrypts them for privacy. In the unlikely event that a user belongs to too many roles to fit in a cookie, the cookie contains a list of the most recently used roles and the database is only queried as a last resort.

Personalization

Another new service is personalization, which provides a ready-built solution to the problem of storing personalization settings for users of your site. Currently, such settings are typically stored in cookies, back-end databases, or a combination of the two. Regardless of where settings are stored, ASP.NET 1.x does little to help. It's up to you to set up and manage the back-end data stores and to associate personalization data using authenticated user names, cookies, or some other mechanism.

The ASP.NET 2.0 personalization service makes it easy to store per-user settings and retrieve them at will. It's based on user profiles, which you define in Web.config using the new <profile> element. The following code is from Web.config:

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

It defines a profile containing 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.

At run time, you access these properties for the current user using the page's Profile property, which refers to an instance of a dynamically compiled class containing the properties defined in the profile. For example, 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 the personalization service is strong typing. Another benefit is that personalization 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 personalization service 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 personalization data stored? That depends. The personalization service is provider based, so you can configure it to use any of the available providers. ASP.NET 2.0 will ship with at least two personalization providers: one for Access and another for SQL Server. If you don't specify otherwise, the personalization service uses the Access provider, which by default stores personalization data locally in Data\AspNetDB.mdb. You can use a SQL Server database instead by modifying Web.config, either by hand or by using Webadmin.axd. If you don't want to store personalization data in either an Access database or a SQL Server database, you can write a provider of your own.

By default, ASP.NET uses authenticated user names to key stored personalization 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 the Theme property will be available as a personalization setting 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 the personalization service to use cookieless anonymous identification, 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.

To see personalization at work, run the Personalize.aspx sample in the download that accompanies this article. It lets each visitor to your site choose a theme, and then it records that theme and applies it each time the visitor returns. Observe that the theme is programmatically applied to the page in the page's PreInit event, which is a new event that fires even before Init.

Before you run the sample, you need to enable anonymous identification and define a profile that includes a string property named Theme. The following lines of code shows a Web.config file that performs those two tasks:

<configuration> <system.web> <anonymousIdentification enabled="true" /> <profile> <properties> <property name="Theme" allowAnonymous="true" /> </properties> </profile> </system.web> </configuration>

SQL Cache Dependencies

Another feature sorely missing from ASP.NET 1.x is database cache dependencies. Items placed in the ASP.NET application cache can be keyed to other cached items and 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 simplicity itself. The following statement inserts a DataSet named ds into the application cache and creates a dependency between the DataSet and the Northwind database's Products table:

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

If the contents of the Products table changes, ASP.NET automatically removes the DataSet.

SQL cache dependencies can also be used with the ASP.NET output cache. The following directive instructs ASP.NET to cache output from the containing page until the contents of the Products table changes or 60 seconds elapse, whichever comes first:

<%@ OutputCache Duration="60" VaryByParam="None" SqlDependency="Northwind:Products" %>

SQL cache dependencies work with SQL Server 7.0, SQL Server 2000, and the upcoming SQL Server 2005. No preparation is required for SQL Server 2005, but SQL Server 7.0 and SQL Server 2000 databases must be configured to support SQL cache dependencies. Preparation involves creating database triggers and creating a special table which ASP.NET consults to determine whether changes have occurred. This table is periodically polled by a background thread using a configurable polling interval that defaults to five seconds. Neither the special table nor polling is required to detect changes in SQL Server 2005. In addition, SQL Server 2005 cache dependencies can be applied at the row level and SQL Server 7.0 and SQL Server 2000 cache dependencies work at the table level. You can prepare a database to support SQL cache dependencies using the Aspnet_regsqlcache.exe tool or Webadmin.axd.

New Dynamic Compilation Model

One of the many innovations introduced in ASP.NET 1.x was the ability for the system to compile your code on first access. Only pages were autocompiled, however, and auxiliary classes such as data access components had to be compiled separately.

ASP.NET 2.0 extends the dynamic compilation model so that virtually anything can be autocompiled. The bin directory is still there for backwards compatibility, but it's now complemented by directories named Code and Resources. C# and Visual Basic® files in the Code directory and RESX and RESOURCE files in the Resources directory are automatically compiled by ASP.NET and cached in system subdirectories. Furthermore, Web Services Description Language (WSDL) files dropped into the Code directory are compiled into Web service proxies, and XML Schema Definition Language (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 commonly asked questions regarding ASP.NET 1.x is whether it's possible to precompile pages to avoid the compilation delay that occurs the first time a page is accessed. While the question itself is something of a red herring (the delay is minimal, and the cost of the delay is amortized over the span of thousands or even millions of subsequent requests), Microsoft felt compelled to do something to alleviate developers' concerns. That "something" is the ability to precompile all the pages in an application by submitting a request for a phantom resource named precompile.axd.

But precompilation doesn't stop there. Another often-requested feature is the ability to precompile entire applications into managed assemblies that can be deployed without source code, a capability that is especially interesting in hosting scenarios. ASP.NET 2.0 includes a new command-line tool named Aspnet_compiler.exe that precompiles and deploys without source; Visual Studio 2005 will include a similar feature. The following command precompiles the application in the Web1 directory and deploys it without source code to Web2:

Aspnet_compiler -v /web1 -p c:\web1 c:\web2

Afterwards, the destination directory contains empty ASP.NET files (ASPX, ASCX, ASIX, and so on), plus copies of any static content present in the source directory such as HTML files, .config files, and image files. Deploying without source doesn't provide ironclad protection of your intellectual property since a clever ISP could still find out what makes the application tick by decompiling the generated assemblies, but it does raise the bar significantly for casual code snoopers.

New Code Separation Model

ASP.NET 1.x supports two programming models: the inline model, in which HTML and code coexist in the same ASPX file, and the codebehind model, which separates HTML into ASPX files and code into source code files (for example, C# files). ASP.NET 2.0 introduces a third model: a new form of codebehind that relies on the partial classes support in the Visual C#® and Visual Basic .NET compilers. The new codebehind fixes a nagging problem with the original: the fact that traditional codebehind classes must contain protected fields whose types and names map to the corresponding controls declared in the ASPX file.

Figure 10 shows the new codebehind model in action. Hello.aspx contains the declarative portion of the page and Hello.aspx.cs contains the code. You should take note of the CompileWith attribute in the @ Page directive. In addition, note the absence of any fields in the MyPage class providing mappings to the controls that are declared in the ASPX file. Old-style codebehind will still be supported, but the new style is the preferred programming model going forward. Not surprisingly, Visual Studio 2005 will support the new code separation model natively.

Figure 10 Codebehind Model

Hello.aspx

<%@ Page CompileWith="Hello.aspx.cs" ClassName="MyPage" %> <html> <body> <form runat="server"> <asp:TextBox ID="Input" RunAt="server" /> <asp:Button Text="Test" OnClick="OnTest" RunAt="server" /> <asp:Label ID="Output" RunAt="server" /> </form> </body> </html>

Hello.aspx.cs

using System; partial class MyPage { void OnTest (Object sender, EventArgs e) { Output.Text = "Hello, " + Input.Text; } }

Client Callback Manager

One of my favorite 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 the XML-HTTP protocol, which generally means Microsoft Internet Explorer 5.0 or higher.

Using the client callback manager entails three steps. First, call Page.GetCallbackEventReference to obtain a reference to a function that can be called from client-side script to perform an XML-HTTP callback to the server. 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. The method name is one of the arguments passed to GetCallbackEventReference. Third, implement the ICallbackEventHandler interface in the page. The interface contains one method, RaiseCallbackEvent, which is called on the server side when a callback occurs. The string that RaiseCallbackEvent returns is returned to the method described in the second step.

The code in Figure 11 shows client callbacks in action and demonstrates one very practical use for them. The page displays a form that solicits a name and address. Type a 378xx or 379xx ZIP code into the Zip Code field and click the Autofill button, and a name appears in the City field. Significantly, the page goes back to the server to fetch the city name, but it does so using a client callback instead of a full postback. In real life, it could hit a database to convert ZIP codes into city names. Observe that the page doesn't repaint as pages tend to do when they post back to the server. Instead, the update is quick and clean!

Figure 11 Callback.aspx

<%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %> <html> <body> <h1>Please Register</h1> <hr> <form runat="server"> <table> <tr> <td>First Name</td> <td><asp:TextBox ID="FirstName" RunAt="server" /></td> <td></td> </tr> <tr> <td>Last Name</td> <td><asp:TextBox ID="LastName" RunAt="server" /></td> <td></td> </tr> <tr> <td>Address 1</td> <td><asp:TextBox ID="Address1" RunAt="server" /></td> <td></td> </tr> <tr> <td>Address 2</td> <td><asp:TextBox ID="Address2" RunAt="server" /></td> <td></td> </tr> <tr> <td>City</td> <td><asp:TextBox ID="City" RunAt="server" /></td> <td></td> </tr> <tr> <td>State</td> <td><asp:TextBox ID="State" RunAt="server" /></td> <td></td> </tr> <tr> <td>Zip Code</td> <td><asp:TextBox ID="Zip" RunAt="server" /></td> <td><asp:Button ID="AutofillButton" Text="Autofill" RunAt="server" /></td> </tr> </table> </form> </body> </html> <script language="javascript"> // Function called when callback returns function __onCallbackCompleted (result, context) { // Display the string returned by the server's RaiseCallbackEvent // method in the input field named "City" document.getElementById ('City').value = result; } </script> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { // Get callback event reference (e.g., "__doCallback (...)") string cbref = GetCallbackEventReference (this, "document.getElementById ('Zip').value", "__onCallbackCompleted", "null", "null"); // Wire the callback event reference to the Autofill button with // an onclick attribute (and add "return false" to event reference // to prevent a postback from occurring) AutofillButton.Attributes.Add ("onclick", cbref + "; return false;"); } // Server-side callback event handler string ICallbackEventHandler.RaiseCallbackEvent (string arg) { if (arg.StartsWith ("378")) return "Oak Ridge"; else if (arg.StartsWith ("379")) return "Knoxville"; else return "Unknown"; } </script>

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 one fatal flaw in that there's no good way to group them together 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 problem is illustrated by ValidationGroups1.aspx, which is included with the samples that you can download for this article. The page's designer intended for users to be able to fill in one group of TextBoxes and post back to the server without having to fill in the other group as well, but it doesn't work that way. Unless all the input fields are filled the validators complain, as shown in Figure 12.

Figure 12 Validation Controls in ASP.NET 1.x

Figure 12** Validation Controls in ASP.NET 1.x **

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 ValidationGroup property. Button controls can be 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, provided, of course, the postbacks are generated by controls that belong to the same group as the validators. ValidationGroups2.aspx demonstrates the technique (see Figure 13). On the outside, this page is identical to ValidationGroups1.aspx. On the inside, it's very different. Now you can fill in either set of TextBoxes and post back by clicking the button in the TextBoxes' validation group.

Figure 13 ValidationGroups2.aspx

<html> <body> <form runat="server"> <h1>New Users</h1> <table> <tr> <td>User Name</td> <td><asp:TextBox ID="NewUserName" RunAt="server" /></td> <td><asp:RequiredFieldValidator ValidationGroup="NewUsers" ControlToValidate="NewUserName" ErrorMessage="Required" RunAt="server" /></td> </tr> <tr> <td>Password</td> <td><asp:TextBox ID="NewPassword1" TextMode="Password" RunAt="server" /></td> <td><asp:RequiredFieldValidator ValidationGroup="NewUsers" ControlToValidate="NewPassword1" ErrorMessage="Required" RunAt="server" /></td> </tr> <tr> <td>Retype Password</td> <td><asp:TextBox ID="NewPassword2" TextMode="Password" RunAt="server" /></td> <td><asp:RequiredFieldValidator ValidationGroup="NewUsers" ControlToValidate="NewPassword2" ErrorMessage="Required" RunAt="server" /></td> </tr> <tr> <td>E-Mail Address</td> <td><asp:TextBox ID="NewEMail" RunAt="server" /></td> <td><asp:RequiredFieldValidator ValidationGroup="NewUsers" ControlToValidate="NewEMail" ErrorMessage="Required" RunAt="server" /></td> </tr> <tr> <td></td> <td><asp:Button ValidationGroup="NewUsers" Text="Create Account" OnClick="OnCreateAccount" RunAt="server" /></td> <td></td> </tr> </table> <hr> <h1>Existing Users</h1> <table> <tr> <td>User Name</td> <td><asp:TextBox ID="UserName" RunAt="server" /></td> <td><asp:RequiredFieldValidator ValidationGroup="ExistingUsers" ControlToValidate="UserName" ErrorMessage="Required" RunAt="server" /></td> </tr> <tr> <td>Password</td> <td><asp:TextBox ID="Password" TextMode="Password" RunAt="server" /></td> <td><asp:RequiredFieldValidator ValidationGroup="ExistingUsers" ControlToValidate="Password" ErrorMessage="Required" RunAt="server" /></td> </tr> <tr> <td></td> <td><asp:Button ValidationGroup="ExistingUsers" Text="Log In" OnClick="OnLogIn" RunAt="server" /></td> <td></td> </tr> </table> </form> </body> </html> <script language="C#" runat="server"> void OnCreateAccount (Object sender, EventArgs e) {} void OnLogIn (Object sender, EventArgs e) {} </script>

Cross-page Posting

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, as shown by the following code in PageOne.aspx:

<html> <body> <form runat="server"> <asp:TextBox ID="Input" RunAt="server" /> <asp:Button Text="Test" PostBackUrl="PageTwo.aspx" RunAt="server" /> </form> </body> </html>

When clicked, the button in PageOne.aspx posts back to PageTwo.aspx:

<html> <body> <asp:Label ID="Output" RunAt="server" /> </body> </html> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { TextBox input = (TextBox) PreviousPage.FindControl ("Input"); Output.Text = "Hello, " + input.Text; } </script>

PageTwo.aspx uses the new PreviousPage property of the Page class to acquire a reference to the originating page. A simple call to FindControl returns a reference to the TextBox declared in PageOne.aspx so the user's input can be retrieved.

By default, System.Web.UI.Page.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, however, PageTwo.aspx could use the new @ PreviousPageType directive to gain strongly typed access to PageOne.aspx, as demonstrated in the code that follows:

<%@ PreviousPageType TypeName="ASP.PageOne.aspx" %> ... Output.Text = "Hello, " + PreviousPage.Input.Text;

Conclusion

ASP.NET 2.0 includes other new features that I haven't written about. For example, a built-in site-counter service enables you to record site usage statistics and view them in Webadmin.axd or in custom GUIs of your own. The new Web Parts subsystem provides a framework for building SharePoint Server-style portals (more information on Web Parts and portals in ASP.NET 2.0 is available in Steven Smith's article in this issue of MSDN Magazine), while integrated mobile device support means you no longer have to install a separate toolkit to adapt output to PDAs and other small-footprint devices. A myriad of enhancements to existing controls make those controls more versatile than ever before in building component-based Web pages.

The time to learn about ASP.NET 2.0 is now because knowing what's coming (and what's not) is essential to planning an architecture today that will easily move forward tomorrow. Your ASP.NET 1.x applications should run without modification on version 2.0 because Microsoft has pledged that the new platform will be backward compatible with the old. But the future is ASP.NET 2.0, and that future is one of richer functionality and less code. What's not to like about that?

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 Microsoft .NET.