Chapter 6: Building Web Applications

 

Chapter 6: Building Web Applications

Microsoft Corporation

March 2006

Summary: Visual Studio 2005 builds on the innovation introduced in Visual Studio .NET and makes Web development easier than ever before for both experienced and novice Web developers. (55 printed pages)

Contents

Introduction
Application: Zero-Code Web Sites
Application: Membership, Profiles, and Roles
Application: New Web Controls
Application: Master Pages and Themes

Introduction

The majority of Visual Basic programmers do at least some Web development. Two common reasons for developing solutions as Web applications are to work with a simple deployment model and to have the broadest potential user base (that is, anyone with a browser).

Although each successive version of the Microsoft .NET Framework has made the deployment of Microsoft Windows applications easier, Web applications continue to have deployment and reach advantages in many situations. The familiar event-driven drag-and-drop development paradigm in Visual Studio 2005 allows you to decide to build a Web application based on target users and the desired deployment model rather than the Web programming experience of the development team.

In the early years of Web programming, programmers built applications that parsed incoming HTTP requests and generated HTML output using string manipulation. Libraries such as the CGI library for Perl were used to handle common tasks, but ultimately the responsibility for properly parsing and handling requests fell to the programmer. Over time, the development paradigm evolved and new Web development technologies emerged, such as Java servlets or the combination of ASP and COM components to generate HTML output. These newer technologies often required programmers to work in multiple development environments, they still required a lot of code to generate basic HTML output, and the resulting applications were difficult to debug, maintain, and deploy. Then the release of the .NET Framework (including ASP.NET 1.0) and Microsoft Visual Studio .NET introduced developers to a better way of building Web applications using server controls, event handlers, code-behind classes written in any .NET language, and efficient one-way data binding.

Visual Studio 2005 builds on the innovation introduced in Visual Studio .NET and makes Web development easier than ever before for both experienced and novice Web developers.

ASP.NET was a huge leap forward for Web developers. ASP.NET 2.0 is another big leap forward. One of the key areas of improvement is the drastically reduced number of lines of code required for common tasks such as data access.

Application: Zero-Code Web Sites

This application demonstrates the improvements in ASP.NET 2.0 that give you the ability to create powerful, interactive Web applications without writing any Microsoft Visual Basic code.

New Concepts

Most nontrivial Web applications require some sort of data store, whether it is a Microsoft SQL Server database, a Microsoft Access data file, an XML file, or some other type of data source. Depending on the complexity of the application, the UI displays data either retrieved directly from the data store or obtained from a set of business objects. In ASP.NET 1.1, even a simple Web page used to display a list of products directly from a database requires numerous lines of code for connecting to the database and binding the data to a server control. In ASP.NET 2.0, many common data access scenarios can be implemented using only declarative ASPX markup—no programming required.

The new "zero code" features in ASP.NET 2.0 cover the most common data access scenarios. The simplest scenario is one-way data binding using a server control to display data from a data source. A common example of this is displaying a list of states or provinces in a drop- down list. A more complex scenario is one-way data binding using multiple controls where the value selected in one control influences the values displayed in a dependent control—for example, displaying a list of countries in a drop-down list and then displaying a country-specific list of states or provinces in another drop-down list when a country is selected. For certain server controls, ASP.NET 2.0 even includes built-in sorting and paging.

The most impressive "zero code" feature in ASP.NET 2.0 is the ability to easily provide the user interface and data access logic for adding, updating, and deleting data without writing the type of plumbing code required for these basic operations in ASP.NET 1.1.

Declarative Languages

The phrase zero code Web site is perhaps a bit of a misnomer because the ASP.NET markup language (that is, the ASPX language) is increasingly being recognized as a declarative programming language. Referring to ASPX as a programming language might seem a bit confusing if you come from a strictly imperative programming background using languages such as C, C++, Java, and Visual Basic because declarative programming is quite a different programming model. For many programmers, an XML-based language just does not "feel" like a programming language. Imperative programming tends to map quite directly to the way a computer operates at a very low level. At any given point in time, your program is in a specific state reached by executing the instructions you provided. By executing additional instructions, your program will end up in a new state. When you program with an imperative language, you reach a desired goal by providing the specific operations required to reach the goal (that is, you tell the computer how to reach your goal). When you program with a declarative language, you specify your goal and a compiler or interpreter uses its predefined algorithms to determine the appropriate operations to reach that goal.

If you are not used to thinking about declarative programming, these concepts might seem a bit foreign, but you are probably already a more experienced declarative programmer than you realize. Consider the following declarative code:

SELECT  *  FROM  Products  WHERE  ProductID  =  55

You probably recognize this as SQL code. SQL is an example of a popular declarative language. The SELECT query expresses an end result: select all the columns from each row of the Products table that has a value of 55 in the ProductID column. The burden of expressing this query as a set of executable operations is taken care of by the query processing engine of whatever database this query is sent to.

Now consider a block of ASP.NET markup:

<form  runat="server">
<asp:TextBox  id="txtCountry"  Text="Canada"  runat="server"/>
<asp:Button  id="btnSave"  Text="Save"  runat="server"  />
</form>

This "code" does not contain a set of instructions but rather defines an end result: a form containing a text box prefilled with "Canada" and a button labeled "Save." ASP.NET has a predetermined algorithm for turning these declarations into output appropriate for various types of browsers (for example, HTML for Internet Explorer). You do not have to provide any of the specific operations required to get the correct output because ASP.NET takes care of that for you.

As the applications in this section demonstrate, ASP.NET markup provides a lot of power with very little programming effort required. The ASPX language can be used to control much more than just layout or simple mappings to HTML. ASPX allows you to define behavior including a rich user interface and database communication. When you work with ASPX, you are working with a lot more than just a data format for encoding a user interface—you are programming with one of the latest and most popular declarative languages.

Data Binding

Prior to the release of the .NET Framework, data binding had garnered a bad reputation because it was awkward to use in Visual Basic 6 for applications that required any moderately complex behavior. Version 1.0 of the .NET Framework introduced new data-binding functionality that mimics what most developers would code themselves to get data-binding behavior.

One of the greatest things that the .NET Framework brought with it was the ability for Web developers to use one-way data binding on Web forms that is both efficient and easy. You simply provide a bindable server control such as a DropDownList with a data source and the server control takes care of iterating through the data and generating the appropriate HTML for each data item. Although it's quite easy to do, data binding in ASP.NET 1.1 still requires code to create the data source, assign it to the appropriate data control, and then call the DataBind method of the data control to actually induce the control to iterate through the data source. In ASP.NET 2.0, you only have to encapsulate the settings for the data source and bindings in the ASPX and let the server controls take care of the rest of the work.

Walkthrough

The simplest data binding in ASP.NET 2.0 is a read-only tabular display of data from a data source. Using the Visual Studio 2005 IDE, you first create a new Web form and drag a data source control onto the form. This application uses the SqlDataSource control, although controls are also available for alternate data sources such as a Microsoft Access data file or an XML document. The SqlDataSource control takes care of connecting to your Microsoft SQL Server database and retrieving the requested data.

When you add a new SqlDataSource control to your form, the Data Source Configuration Wizard guides you through the setup for the control. The first step is to establish the connection settings for the control to communicate with the database. Figure 6-1 shows that first step with a connection to the Northwind database already configured.

Aa730863.netvbdev0601(en-US,VS.80).gif

Figure 6-1. Data Source Configuration Wizard

Next you specify the data you want the data source control to select from the data source. This application uses a straightforward query to select all rows from the Products table of the Northwind database:

Aa730863.netvbdev0602(en-US,VS.80).gif

Figure 6-2. Defining a query

With a connection and a SELECT query, the SqlDataSource control has all the information it needs to retrieve data from the database. The resultant ASPX markup generated by the wizard is:

<asp:SqlDataSource ID="SqlDataSource1" 
    runat="server" 
    ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT * FROM [Products]">
</asp:SqlDataSource>

You will notice that the new syntax for declaratively reading connection strings from the web.config configuration file is used. The corresponding entry from web.config is as follows (with line breaks added for readability):

<connectionStrings>
    <add name="NorthwindConnectionString" 
        connectionString="Data Source=localhost;Initial Catalog=Northwind;Integrated Security=True"
        providerName="System.Data.SqlClient" />
</connectionStrings>

Now that the Web form has a data source control, you can add other controls that support data binding and bind them to the data source control. For a simple tabular display, the new GridView control needs no special configuration—just give it the ID of the data source control and it generates a complete table of data. You can use the Common Tasks smart tag for the GridView to assign the data source or simply use the following ASPX markup to set the DataSourceID attribute of the GridView:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="True" DataKeyNames="ProductID"
    DataSourceID="SqlDataSource1">
    
</asp:GridView>

The end result in the visual Web designer is shown in Figure 6-2. The SqlDataSource control is visible in design view, but when it is viewed in Internet Explorer, as shown in Figure 6-3, only the data table generated by the GridView is visible.

Aa730863.netvbdev0603(en-US,VS.80).gif

Figure 6-3. GridView bound to a SqlDataSource with zero code

Aa730863.netvbdev0604(en-US,VS.80).gif

Figure 6-4. Final output viewed in Internet Explorer

Two-Way Data Binding

Although one-way data binding with zero code is convenient, the overall productivity benefit for creating a quick read-only display is not all that significant. The more daunting tasks in ASP.NET 1.1 are doing two-way data binding for CRUD (create, read, update, and delete) operations and presenting a usable interface, including features such as sorting and paging (for example, presenting 10 records per page with Next and Previous buttons). ASP.NET 2.0 makes short work of two-way data binding and requires, once again, zero code. The secrets lie in the abilities of the data source and GridView controls.

In the first page of this application, you saw how to create a SqlDataSource control configured to only select data. But the SqlDataSource control can also be used to update, insert, and delete data. The SqlDataSource control just requires the appropriate SQL commands and parameters for the different types of queries. You can type the many lines of ASPX markup yourself, or you can use the advanced features of the Data Source Configuration Wizard to create the markup for you.

When setting up the data selection for the Data Source Configuration Wizard, an Advanced Options button is available that opens the dialog window shown in Figure 6-4. Selecting the first check box will prompt the wizard to probe the database for the structure of the data using the select information you provide. After querying the database for the structure, the wizard will automatically create all the ASPX markup required for the SqlDataSource control to perform CRUD operations against your database.

Aa730863.netvbdev0605(en-US,VS.80).gif

Figure 6-5. Advanced SQL generation options for the Data Source Configuration Wizard

In Visual Studio .NET 2003, there are wizards to help you create the code for doing CRUD operations (for example, the Data Adapter Wizard), but the result is Visual Basic (or C#) code that you must then bind to data controls programmatically. Plus the generated code is placed in the "designer generated" region of the code file, where you make changes at your own peril because those changes will be lost if you invoke the wizard again. And moving the generated code out of the "designer generated" region means you cannot use the wizard to alter the adapter settings. By contrast, the Data Source Configuration Wizard in Visual Studio 2005 generates ASPX markup that declaratively expresses the update, insert, and delete commands with corresponding parameters. The following ASPX listing shows the markup generated by the wizard for the Northwind Products table.

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    DeleteCommand="DELETE FROM [Products] WHERE [ProductID] = @ProductID" 
    InsertCommand="INSERT INTO [Products] ([ProductName], [SupplierID], [CategoryID], [QuantityPerUnit],    
        [UnitPrice], [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]) VALUES (@ProductName,  
        @SupplierID, @CategoryID, @QuantityPerUnit, @UnitPrice, @UnitsInStock, @UnitsOnOrder, @ReorderLevel, 
        @Discontinued)"
    SelectCommand="SELECT * FROM [Products]" UpdateCommand="UPDATE [Products] SET [ProductName] = 
        @ProductName, [SupplierID] = @SupplierID, [CategoryID] = @CategoryID, [QuantityPerUnit] = 
        @QuantityPerUnit, [UnitPrice] = @UnitPrice, [UnitsInStock] = @UnitsInStock, [UnitsOnOrder] = 
        @UnitsOnOrder, [ReorderLevel] = @ReorderLevel, [Discontinued] = @Discontinued WHERE [ProductID] = 
        @ProductID">
    <DeleteParameters>
        <asp:Parameter Name="ProductID" Type="Int32" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="SupplierID" Type="Int32" />
        <asp:Parameter Name="CategoryID" Type="Int32" />
        <asp:Parameter Name="QuantityPerUnit" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="UnitsInStock" Type="Int16" />
        <asp:Parameter Name="UnitsOnOrder" Type="Int16" />
        <asp:Parameter Name="ReorderLevel" Type="Int16" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="ProductID" Type="Int32" />
    </UpdateParameters>
    <InsertParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="SupplierID" Type="Int32" />
        <asp:Parameter Name="CategoryID" Type="Int32" />
        <asp:Parameter Name="QuantityPerUnit" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="UnitsInStock" Type="Int16" />
        <asp:Parameter Name="UnitsOnOrder" Type="Int16" />
        <asp:Parameter Name="ReorderLevel" Type="Int16" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
    </InsertParameters>
</asp:SqlDataSource>

Other data controls such as the GridView can now be used to modify data in the database via the SqlDataSource control. To enable data modification for a GridView control, set the AutoGenerateDeleteButton and AutoGenerateEditButton properties to True in the Properties window as shown in Figure 6-5.

Aa730863.netvbdev0606(en-US,VS.80).gif

Figure 6-6. GridView properties for enabling data modification

The resulting markup is not much different from the one-way data binding example. All that has been added is a CommandField element with two attributes that tell ASP.NET to generate edit and delete link buttons:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="SqlDataSource1" AutoGenerateDeleteButton="True" AutoGenerateEditButton="True">
    <Columns>
        <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
            ReadOnly="True" SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
        <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" />
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" />
        <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
    </Columns>
</asp:GridView>

When you view this GridView in your browser, you see Edit and Delete links for each row of data. If you click an Edit link, the row changes to a set of editable controls (normally text boxes) as shown in Figure 6-6. Clicking Update saves the changes to the underlying data source (which is Microsoft SQL Server in this case). That's two-way data binding without writing a single line of code!

Aa730863.netvbdev0607(en-US,VS.80).gif

Figure 6-7. Editing products in Internet Explorer with zero code

Prettifying a Zero-Code Site

These zero-code features are certainly compelling, but so far they're not very visually appealing or user friendly. As home and business users become more accustomed to the Web for work and pleasure, they demand more visually appealing Web pages—even for editing inventory data. Fortunately, ASP.NET 2.0 has plenty of support for formatting and otherwise enhancing the appearance of zero-code Web sites. For example, the GridView control allows you to control color, font, and layout information to create an attractive yet practical data table.

The fastest way to select a professional prepackaged look for your data control is to use the Auto Format feature, which is accessible via the GridView Tasks smart tag window as shown in Figure 6-7. You can also use the GridView Tasks window to enable sorting and paging for a GridView control. If you ever tried to work with paging in ASP.NET 1.1, with the DataGrid for example, you will appreciate how simple it is with the GridView control—just select one check box. The GridView and SqlDataSource take care of the rest of the details to control paging.

Aa730863.netvbdev0608(en-US,VS.80).gif

Figure 6-8. GridView Common Tasks smart tag dialog window

The Auto Format dialog lets you choose from a number of prebuilt formats that include colors, fonts, row style, alternating row style, selected row style, header row style, and more. Figure 6-8 shows the Auto Format dialog being used to select a format for a GridView that will support paging (with up to 10 records per page) but not sorting.

Aa730863.netvbdev0609(en-US,VS.80).gif

Figure 6-9. Auto Format dialog window

Maintaining the same format for grids of data throughout your Web site by duplicating all this formatting for each grid would be an arduous task. Fortunately, this format could be applied universally in a Web site by creating a new Theme, as described in an upcoming application.

**Note   **When paging is enabled, you can use the PageSize attribute of the GridView control to change the number of rows of data displayed on each page.

Conclusion

Every Web application is unique, but there are always some similarities. One similarity is basic data access for either a read-only display or an interface for viewing and modifying data. The new zero-code features in ASP.NET 2.0 make it possible to write useful Web application building blocks without writing any code! Using wizards and other design-time features, Visual Studio 2005 can generate for you all the ASPX markup for defining a nontrivial Web application. Data access is just one of the common tasks available with zero code. As you will see in the next two applications, you can also create a Web site with authentication features, including new user registration and user login, without writing any code.

Application: Membership, Profiles, and Roles

This application highlights the new membership, profile, and role-based security features in ASP.NET 2.0 that make it easier to secure your Web applications.

New Concepts

Most real-world Web applications have some restricted sections that require users to be authenticated so that access can be granted or denied based on whether a user is authorized to use a protected resource. A common example is an administration section for updating content or viewing site statistics. ASP.NET supports several authentication schemes, including integrated Windows authentication and Passport authentication. Windows and Passport authentication are appropriate for some types of applications, but for many applications neither is practical. Windows authentication requires a user account for every user. Passport authentication is cost-prohibitive for small and medium-size sites. Therefore, the choice for many sites is custom authentication logic and a custom data store for storing credentials and other user-related data, such as first name, last name, address, and so on. ASP.NET 1.1 sites can take advantage of Forms Authentication for things like setting an authentication cookie, and they can use a general-purpose API for determining whether a user is logged in and what authorization roles that user belongs to. But creating the plumbing for managing user accounts is the responsibility of the Web developer.

The result has been countless ASP.NET Web applications put into production with almost identical authentication models implemented in each one. This common authentication model is so pervasive that it should be embedded right into the Web application platform. And that is exactly what ASP.NET 2.0 provides with its new membership and user profile features.

Membership

The new membership features in ASP.NET 2.0 simplify user management in three key ways:

  1. They automatically create a data store. When you attempt to use membership features, ASP.NET 2.0 checks whether the specified data store is configured. If it is not, ASP.NET creates it.
  2. They include server controls for creating and validating users and displaying user- specific information and login status. New controls such as the Login, LoginStatus, CreateUserWizard, and ChangePassword ** controls provide prebuilt user-interface building blocks, including functionality for the most common membership-related tasks. These controls are highlighted in the New Web Controls application.
  3. They provide an application programming interface (API) for programmatically managing users. The Membership API is accessed via the Membership ** class and includes helpful methods such as CreateUser, DeleteUser, and ValidateUser.

With ASP.NET 2.0 Membership, you can actually create a Web site with protected pages, automatic redirects to a login page, user creation (registration), and user login without writing any code! As you will see in the "Walkthrough" section for this application, a few XML elements in web.config and a few server controls are all you need to create an authentication-enabled Web application with ASP.NET 2.0.

User Profiles

The membership features in ASP.NET 2.0 let you collect basic user information that is important for authentication, such as username, password, e-mail address, and a secret question/ answer pair for password retrieval. But often you will need to collect and store additional information, such as first name, last name, Web site or Weblog URL, job title, and possibly much more. The new user profile features in ASP.NET 2.0 give you the ability to define additional pieces of information about a user that your application must store. You simply specify in web.config the pieces of information you require and then populate the corresponding fields in a special Profile object at runtime.

The Profile object that ASP.NET 2.0 generates is specific to your application and contains strongly typed properties that map to the entries in your application's web.config file. The Visual Studio IDE reads these entries at design time and builds a specialized class (inherited from HttpProfileBase) automatically so that your application-specific properties are available in the Intellisense menu and can be verified by the compiler. The "Walkthrough" section for this application shows you how to set up user profiles in web.config and then how to access user- specific profile data at run time.

Role-Based Security

Authenticating users on your Web site allows you to identify users, but without additional role-based authorization you cannot restrict access to Web resources based on the user's identity. ASP.NET has always included built-in support for determining whether a user is in a role via the user's security principal object. When using Windows authentication, roles come from the Windows security groups. But when using Forms authentication in ASP.NET 1.1, you have to build your own security principal object and populate its roles collection as each Web request is authenticated. As a developer, you are also responsible for writing the code to maintain a data store for user roles and to load that data at run time.

ASP.NET 2.0 expands the role-based security story with the introduction of ASP.NET role management, including the new Roles class and the <roleManager> configuration section for web.config. ASP.NET role management provides the most common role-based security features that you would have previously had to build yourself. The Roles class provides an API for creating and deleting roles, adding and removing users from roles, enumerating roles, enumerating the users in a role, enumerating the roles a user is in, and more. ASP.NET role management takes care of persisting the role membership data, loading it at run time, and adding the appropriate roles to a user's security principal.

Walkthrough

This application demonstrates the use of the new membership, profile, and role-based security features in ASP.NET 2.0. This walkthrough includes details about configuring these new features and working with them programmatically.

Membership

ASP.NET Membership is easy to configure in Visual Studio 2005. When you create a new Web site in Visual Studio 2005, the Solution Explorer looks similar to the example shown in Figure 6-10.

Aa730863.netvbdev0610(en-US,VS.80).gif

Figure 6-10. Solution Explorer before accessing the Membership API

To configure a site for membership, you select the WebSite | ASP.NET Configuration menu command. Select the Security Tab:

Aa730863.netvbdev0611(en-US,VS.80).gif

Figure 6-11. Web site administration tool

The link to "Use the security Setup Wizard to configure security step by step" walks you through configuring security one step at a time. First, you are asked if you want to support internet users, or only users with domain accounts. Next your prompted regarding the data store. The only data store that's initially configured is SQL Server, which also includes SQL Server Express. You are then asked if you want to define roles for your site. After roles are defined, you can enter individual user accounts. The next task is to define which roles are authorized to access certain directories:

Aa730863.netvbdev0612(en-US,VS.80).gif

Figure 6-12. Configuring authorization

When the wizard has completed, a SQL Express database is added to the site to contain the membership information.

Aa730863.netvbdev0613(en-US,VS.80).gif

Figure 6-13. Solution Explorer showing an auto-generated membership data file

Visual Studio also contains a wealth of controls to manage login. The toolbox contains controls for Login, PasswordRecovery, ChangePassword, and CreateUser. These controls can be added to pages to allow your sites to support user authentication. Figure 6-14 shows a simple page with a LoginStatus control. The login status control automatically toggles between "Login" and "Logout" text depending on whether the user is currently logged in:

Aa730863.netvbdev0614(en-US,VS.80).gif

Figure 6-14. Page with a LoginStatus control

When the LoginStatus control is clicked by the user, they are automatically sent to the login.aspx page. In this application the login.aspx page contains a Login control, and a PasswordRecovery control:

Aa730863.netvbdev0615(en-US,VS.80).gif

Figure 6-15. Login and PasswordRecovery controls

When the user successfully logs in, they are returned to the default.aspx page for the site. This page also contains a LoginView control. This lets you define regions of the page that are visible depending on the authentication status of the user. Since the user is now logged in, this control is visible and shows a link to Member Pages. Also note in Figure 16 that LoginStatus control now shows the text "Logout".

Aa730863.netvbdev0616(en-US,VS.80).gif

Figure 6-16. Home page showing LoginView control

When "Member Pages" is clicked, the user is taken to a page under the MemberPages directory. The user is only allowed to access this page because the user is authenticated. The member page can display the logged in user name with the LoginName control as shows in figure 6-17.

Aa730863.netvbdev0617(en-US,VS.80).gif

Figure 6-17. Member page showing user with LoginName control

User Profiles

User profiles in ASP.NET 2.0, sometimes referred to as user personalization or profile personalization, are configured in the web.config file for a Web site. You state declaratively in the configuration file the data you want to store for each user and then populate the corresponding properties in a special Profile object. The following subset of a web.config file configures a Web site to store FirstName and LastName string values for each user:

    <profile defaultProvider="AspNetSqlProfileProvider">
      <properties>
        <add name="FirstName" type="System.String"/>
        <add name="LastName" type="System.String" />
      </properties>
    </profile>

Visual Studio 2005 reads this configuration information from the web.config file and dynamically creates a specialized class that inherits from HttpProfileBase. The specialized class contains all the properties defined in the web.config file with the proper data types. Figure 6-12 shows an example of a custom profile property being made available in Intellisense.

Aa730863.netvbdev0618(en-US,VS.80).gif

Figure 6-18. Strongly typed profile properties available in Intellisense

At run time, ASP.NET 2.0 takes care of managing the persistence of the profile data. As a programmer, your only tasks are to read and write that data. The following code shows how you set the FirstName and LastName properties and save them to the profile data store:

    Protected Sub SaveButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SaveButton.Click
        Profile.LastName = LastNameTextBox.Text
        Profile.FirstName = FirstNameTextBox.Text
    End Sub

To read the data back from the Profile object, simply invert the assignment statements:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Not IsPostBack Then
            LastNameTextBox.Text = Profile.LastName
            FirstNameTextBox.Text = Profile.FirstName
        End If
    End Sub

By default, these profile properties can be set only for (and therefore are stored only for) authenticated users. But in some situations you will want to capture information when a user is browsing anonymously and maintain that data when the user logs in to your site. ASP.NET user profiles support this type of scenario natively. You must explicitly enable anonymous profiling using the <anonymousIdentification> element in web.config. Then any properties that you want to support for anonymous users must have the allowAnonymous attribute set to true. The following XML from web.config shows these settings for this application:

<anonymousIdentification  enabled="true"  />

Role-Based Security

ASP.NET 2.0 role management provides a number of related features for creating and maintaining roles and role membership. When you are configuring role management, you can choose to have the roles for a user encrypted and cached in a cookie. Although the caching is useful for reducing the number of trips to the data store to obtain user role information, it can also introduce a problem in which the roles in the cookie do not match a recently updated set of roles in the server-side data store. This difference can be especially problematic when you are changing role membership a lot. For this application, cookie caching is disabled by setting the cacheRolesInCookie attribute of the <roleManager> element to false:

<roleManager enabled="true" cacheRolesInCookie="false"/>

Now every time an authenticated request is processed, the roles will be re-read from the data store and loaded into the user's security principal object.

Once you have created roles, you can do the expected maintenance tasks, such as obtaining a list of all roles in your application. The GetAllRoles method returns an array of String objects containing the names of the roles previously created:

Dim  allRoles()  As  String  =  Roles.GetAllRoles

Now that the roles actually exist, users can be assigned to roles. To programmatically assign one user to one role at a time, use the AddUserToRole method:

Roles.AddUserToRole(UserList.SelectedValue,  RolesList.SelectedValue)

If you want to add multiple users to a role simultaneously, you can use the AddUsersToRole method. If you want to add one user to multiple roles, you can use the AddUserToRoles method. And to add multiple users to multiple roles simultaneously, you can use the AddUsersToRoles method.

ASP.NET 2.0 role management also adds one new feature that solves a small but frustrating limitation from ASP.NET 1.1—the ability to get a list of all the roles for a user without having to write data-access code to read from the underlying data source:

Roles.GetRolesForUser(User.Identity.Name)

Conclusion

User management is an almost ubiquitous Web development task for which there are some very well-established patterns, including authentication using a username and password; storing user profile information such as first name, last name, and address; and role-based authorization. ASP.NET 1.1 provides some support for user management—such as a generic authentication model and built-in differentiation between authenticated and anonymous users—and it provides some support for role-based authorization. But with ASP.NET 1.1, the bulk of the burden for creating and maintaining the logic and data stores to do commonplace user management tasks fell to the developer. Similar code including data access for credentials, profiles, and roles has been written over and over again by countless developers. Finally, with ASP.NET 2.0 there is support for common user management chores built right into the ASP.NET Web platform.

Application: New Web Controls

This application demonstrates some of the new Web controls in ASP.NET 2.0, such as the new security controls that provide prebuilt user interface elements for the new ASP.NET 2.0 membership system.

New Concepts

There are a lot of new controls in ASP.NET 2.0—too many to introduce in one sample application. There are new data-source controls, new data-bound controls, new security controls, new Web part controls, new navigation controls, and new mobile-aware features in the controls from ASP.NET 1.1. This application highlights the new controls that are most applicable to general Web development—hopefully, they are the ones that will fulfill some of your most immediate needs as a Web developer.

Security Controls

Any Web site that uses authentication to verify a user's identity or restrict access to protected resources requires some sort of user interface for all the different authentication tasks, such as logging in, registering (creating a new account), displaying login status, displaying a personalized logout link, and resetting passwords. In ASP.NET 1.1, you were on your own for all these user interface components. In ASP.NET 2.0, there is a set of new security controls with which you can add basic security features to your site without even writing a single line of code.

The security controls included in Visual Studio 2005 are as follows:

  • Login: This is a composite control that ties into the ASP.NET membership features. It contains TextBox ** controls for entering a username and password, as well as a Submit button. This control also includes a number of customizable features, including formatting and all the various pieces of text that can appear, such as instructional text, login failed text, and title text for the control. You can also choose to have the control display hyperlinks to registration and password recovery pages. The Login ** control even includes built-in required field validation for both username and password. (Validation can be disabled.) You can write code to support a Login ** control—in the Authenticate ** event handler, for example—but it is entirely unnecessary. Even the simplest use of the Login ** control with absolutely zero code provides a complete working authentication interface.
  • CreateUserWizard: This control provides a wizard-style interface for creating a new user in the membership system. The CreateUserWizard ** is a lot more than a simple form for entering a username and password. It includes field validation for all of its fields, including optional regular expression validation for e-mail addresses. It prompts the user to enter a proposed password twice and confirms that the passwords match. The CreateUserWizard ** control can also gather a security question and answer from the user if the underlying membership provider supports security questions. The "Walkthrough" section for this application also shows how you can add custom steps to the wizard to collect additional information from the user during registration.
  • LoginName: This control is a placeholder that displays the username of the user. If the current user is anonymous (that is, not authenticated), this control does not render any output. The LoginName ** exposes a FormatString ** property that you can use to display more than just the username. For example, the FormatString ** could be set to "Welcome, {0}", which would produce the output "Welcome, UserTwo" when UserTwo is logged in.
  • LoginView: This control provides templates in which you can create different content for anonymous and authenticated users. The LoginView ** control is also role-aware, so you can actually create different templates for authenticated users in different roles.
  • PasswordRecovery: This control provides the user interface and corresponding functionality to help users retrieve or reset their passwords. The control has three views: Username, Question, and Success. The Username view allows the user to enter the username for which a password has to be retrieved or reset. The Question view prompts the user for the answer to a question that was entered during registration. The Success view displays a success message after the password has been delivered via e-mail. The options available for password retrieval are determined in part by the Membership provider used to provide membership services. ASP.NET 2.0 includes a provider for Microsoft SQL Server. This support password retrieval and security questions. Other providers, such as a third-party offerings or one you create yourself, might not support all those features. Also, the default the built-in providers to stores password hashes. (Clear text and encrypted text are the other options.) Password hashes cannot be used to determine the original password, so by default the only recovery scheme available is to reset the password and e-mail the new password to the user.
  • LoginStatus: This control produces a Login or Logout hyperlink based on the login status of the current user: an anonymous user sees a Login link, and an authenticated user sees a Logout link. The actual text for the hyperlink as well as the target of the link is, of course, customizable. The Logout link can be configured to do one of three things after the user has logged out: refresh the current page, redirect the user to the login page, or redirect the user to some other page.
  • ChangePassword: This composite control provides users with an interface for changing their passwords. The default is to require the user to type the current password once and the new password twice. Like the other security controls, ChangePassword ** is completely customizable.

Site Map Controls

The new SiteMapPath control provides part of a new site map solution built into ASP.NET 2.0. The SiteMapPath control provides a "breadcrumb" navigation interface that is popular on many Web sites. In ASP.NET 1.1, you had to create all the logic for breadcrumb navigation or buy a third-party solution. The SiteMapPath control only requires your Web site to have an XML file called web.sitemap that defines the hierarchy of pages in your site. The SiteMapPath control determines where the current page sits within the site map hierarchy and automatically creates the breadcrumb navigation. If the SiteMapPath control is placed on a Web form not included in the site map file, the SiteMapPath simply does not render a breadcrumb trail.

ASP.NET 2.0 also includes a new SiteMapDataSource control that reads the web.sitemap file and acts as a data source for other controls such as a TreeView. The "Walkthrough" section for this application shows how to create a web.sitemap file, how to use the SiteMapPath control, and how to create a site map tree by using the SiteMapDataSource control.

Substitution Control (Post-Cache Substitution)

Output caching is a technique for saving the output from an ASP.NET Web form and reusing the same output to fulfill multiple requests for the same page. Output caching is a huge boon for Web site performance when it can be used. In ASP.NET 1.1, there was no way to alter the contents of a cached page for individual users, so many Web pages could not benefit from output caching because of some small portion of the page content that had to be customized for each user (for example, a personalized greeting or the number of items in the user's shopping cart).

Page-fragment caching is a way of caching the output from user controls on a Web form. If you can design your Web forms as a set of user controls, you can get some of the performance benefit of output caching while having control over page elements that have to be personalized. But page-fragment caching can be awkward to use because you have to break your pages into user controls to allow for even the simplest per-request customization. ASP.NET 2.0 introduces a new way to benefit from output caching and do per-user customization. This new approach is called post-cache substitution.

Post-cache substitution is accomplished by placing Substitution controls on a Web form and then providing the name of a shared (static) callback method that ASP.NET can call to obtain content to inject into the cached output. Although post-cache substitution is obviously not going to provide as much of a performance boost as pure output caching, the extra cost of calling the callback method is directly tied to the performance characteristics of the callback method.

Walkthrough

This walkthrough shows you how to use the new security Web controls in ASP.NET 2.0. This walkthrough also demonstrates how to use the new site map controls and how to do post-cache substitution using the new Substitution control.

Security Controls

ASP.NET 2.0 introduces seven new security controls to cover the most common authentication and user management needs in Web applications. A small time investment (to discover the capabilities of these seven controls) can yield much larger time savings as you build new Web applications or migrate existing applications to ASP.NET.

Login Control   Although it is not the most complex of the security controls, the Login control can be considered the core security control because it provides the interface that actually collects credentials and validates them against the membership database. The Login control contains a considerable number of properties that let you configure the display and behavior of the control. Figure 6-14 shows some of the Login control properties you can edit in the Properties window.

Aa730863.netvbdev0619(en-US,VS.80).gif

Figure 6-19. Properties window for the Login control

You can configure the Login control to redirect the user to a specific page after successfully validating the user's credentials. You can also configure the Login control to either refresh the current page or redirect the user to your site's login page after an unsuccessful login attempt. You can also modify all the text displayed by the control, including the labels, instructions, help text, the failure message, and the control title.

This application uses most of the default settings for the Login control. The ASPX markup for the Login control on the login.aspx page of this application is as follows:

<asp:Login ID="Login1" runat="server" 
    CreateUserText="Register for a new account" 
    CreateUserUrl="~/CreateUser.aspx" 
    PasswordRecoveryText="Forgot your password?" 
    PasswordRecoveryUrl="~/RecoverPassword.aspx">
</asp:Login>

The CreateUserUrl attribute tells the Login control to provide a hyperlink to the user registration page. The CreateUserText attribute specifies the text displayed for that hyperlink. Similarly, the PasswordRecoveryUrl attribute tells the Login control to provide a hyperlink to a page where users can have their passwords reset. The PasswordRecoveryText attribute specifies the text displayed for that hyperlink. Figure 6-15 shows the Login control in Internet Explorer after a failed login attempt.

Aa730863.netvbdev0620(en-US,VS.80).gif

Figure 6-20. Login control at run time after failed login attempt

CreateUserWizard Control   To log in, you will first have to create an account. As you saw in the previous application, you can do this via the ASP.NET Configuration page for a site, but that would require you to manually add each user. With the CreateUserWizard, you simply add one control to a Web form and set a few properties. The control takes care of the rest of the details.

The CreateUserWizard is actually a very powerful control because it lets you add your own custom steps to the wizard. In this application, a new step has been added to the wizard that asks new users for their favorite color, which is then stored using the built-in ASP.NET user profile features.

The CreateUserWizard lets you configure almost everything about its appearance and behavior—everything from labels and prompts to the regular expression used to validate e-mail addresses. One attribute you will want to set for this control is the ContinueDestinationPageUrl, which determines where the CreateUserWizard control redirects the user after successfully creating a new account. The following ASPX listing is the markup that defines the CreateUserWizard control for the CreateUser.aspx page in this application.

<asp:CreateUserWizard ID="CreateUserWizard1" runat="server" ActiveStepIndex="1">
    <WizardSteps>
        <asp:CreateUserWizardStep runat="server">
        </asp:CreateUserWizardStep>
        <asp:WizardStep runat="server" Title="Favorite Color">
            What is your favorite color?<br />
            <asp:TextBox ID="FavoriteColorTextBox" runat="server"></asp:TextBox>
        </asp:WizardStep>
        <asp:CompleteWizardStep runat="server">
        </asp:CompleteWizardStep>
    </WizardSteps>
</asp:CreateUserWizard>

Figures 6-21, 6-22, and 6-23 show the steps that the user goes through to create a new account. Notice the custom step (Figure 6-22) in the wizard that asks for the user's favorite color.

Aa730863.netvbdev0621(en-US,VS.80).gif

Figure 6-21. CreateUserWizard control showing the ConfirmPasswordCompareErrorMessage

Aa730863.netvbdev0622(en-US,VS.80).gif

Figure 6-22. CreateUserWizard custom wizard step

Aa730863.netvbdev0623(en-US,VS.80).gif

Figure 6-23. CreateUserWizard final success step

When the user clicks the Continue button on the final success step, the CreateUserWizard control raises a ContinueClick event for which you can write an event handler. At that point, the user will be logged in automatically (unless you change the LoginCreatedUser attribute to false). In the event handler, you can access the controls in custom wizard steps and retrieve the values entered by the user during the account-creation process. The following code shows how to obtain a reference to the txtColor control and save the user's favorite color in the user's profile:

    Protected Sub CreateUserWizard1_ContinueButtonClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles CreateUserWizard1.ContinueButtonClick
        Dim FavoriteColorTextBox As TextBox
        FavoriteColorTextBox = CreateUserWizard1.FindControl("FavoriteColorTextBox")
        If Not FavoriteColorTextBox Is Nothing Then
            Profile.FavoriteColor = FavoriteColorTextBox.Text
        End If
    End Sub

LoginName Control The LoginName control is the simplest of the new security controls. If the current user is authenticated, the LoginName control displays the username. If the current user is not authenticated, the LoginName does not display any output. The following ASPX markup shows how the LoginName control is used in this application:

Thank  you  for  using  our  site,
<asp:LoginName  ID="Loginname1"  Font-Bold="true"  Runat="server"  />

In this example, the username is in bold but the greeting preceding the username is not. If both the greeting and the username were going to have the same font weight, the greeting could be placed in the FormatString attribute as shown here:

<asp:LoginName  ID="Loginname1"
FormatString="  Thank  you  for  using  our  site,  {0}" Runat="server"  />

LoginView Control   If you have ever created a Web page that had to render different content for different types of users, you know that the code is reasonably simple but tedious to create and maintain. Even in ASP.NET 1.1 you have to do a lot of work placing the content within server controls that can act as containers (for example, <div runat="server">) and then writing the logic to show and hide the server controls based on the type of user viewing the page.

The LoginView control has built-in support for user roles, so you can create different views for authenticated users based on role. The LoginView control handles the task of selecting the appropriate view based on the current user's roles. The following code is from the Default.aspx page in this application. The LoginView control in this example has four views: one for anonymous users, one for authenticated users, one for authenticated users in the Admin role, and one for authenticated users in the NormalUser role.

<asp:LoginView ID="LoginView1" runat="server">
    <AnonymousTemplate>
        Welcome to the site.&nbsp; You are not currently <a href="Login.aspx">logged in</a>.&nbsp;
        If you don't have an account, you can <a href="CreateUser.aspx">sign-up</a>.
    </AnonymousTemplate>
    <RoleGroups>
        <asp:RoleGroup Roles="Admin">
            <ContentTemplate>
                Hello Admin.<br />
            </ContentTemplate>
        </asp:RoleGroup>
        <asp:RoleGroup Roles="NormalUsers">
            <ContentTemplate>
                Ah, a normal user.
            </ContentTemplate>
        </asp:RoleGroup>
    </RoleGroups>
    <LoggedInTemplate>
        Welcome
        <asp:LoginName ID="LoginName1" runat="server" />
    </LoggedInTemplate>
</asp:LoginView>

One of the great features of the LoginView control is that all the templates you create do not clutter your visual design surface in Visual Studio 2005, so your design-time representation of the Web form is not skewed in the same way it can be when working with multiple <div runat="server"> or <asp:Panel> controls. Figure 6-19 shows the LoginView control from Default.aspx in Visual Studio 2005 at design time.

Aa730863.netvbdev0624(en-US,VS.80).gif

Figure 6-24. LoginView control at design time

The Default.aspx page in this application also contains the code below. The code contains two Click event handlers that both call the SetRole method but pass in different role names. The SetRole method creates the Admin and NormalUser roles if they do not already exist. Then the SetRole method removes the current user from all roles. Finally, the SetRole method adds the current user to the role specified in the roleName parameter. When Default.aspx is then rendered, the LoginView on the page displays the appropriate view based on the user's current role membership.

Partial Class _Default
    Inherits System.Web.UI.Page

    Protected Sub MakeAdminButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MakeAdminButton.Click
        SetRole("Admin")
    End Sub

    Protected Sub MakeNormalUserButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MakeNormalUserButton.Click
        SetRole("NormalUser")
    End Sub

    Sub SetRole(ByVal roleName As String)
        If Roles.GetAllRoles().Length = 0 Then
            Roles.CreateRole("Admin")
            Roles.CreateRole("NormalUser")
        End If
        For Each r As String In Roles.GetRolesForUser(User.Identity.Name)
            Roles.RemoveUserFromRole(User.Identity.Name, r)
        Next
        Roles.AddUserToRole(User.Identity.Name, roleName)
    End Sub

End Class

PasswordRecovery Control   The PasswordRecovery control is deceptively simple at first glance. The ASPX markup required for a fully functional e-mail-enabled password recovery solution is a mere one line:

<asp:PasswordRecovery  ID="Passwordrecovery1"  Runat="server"  />

With this one line of markup, ASP.NET 2.0 empowers your Web site with the ability for users to enter their username, answer their security question, and have a new password sent to them by e-mail. And as with the other security controls, the PasswordRecovery control is highly configurable.

Figures 6-25 and 6-26 show the steps that users go through when resetting a password.

Aa730863.netvbdev0625(en-US,VS.80).gif

Figure 6-25. First step in password recovery

Aa730863.netvbdev0626(en-US,VS.80).gif

Figure 6-26. Second step in password recovery

LoginStatus Control The LoginStatus control is not a very complex control like some of the other security controls, but it is very convenient. Web sites commonly have a login link somewhere that takes users to a login page. After a user logs in, that link becomes a logout link. In many Web development environments, you would have to write the logic to check the user's login status and render the appropriate HTML (for example, by enabling the appropriate HyperLink or LinkButton control in ASP.NET 1.1). In ASP.NET 2.0, all you need is a LoginStatus control:

<asp:LoginStatus  ID="Loginstatus1"  Runat="server"     />

The LoginStatus control can be configured in several ways. For example, you can state what the control should do after a user logs out: refresh the page, redirect to another page, or redirect to the login page. Figure 6-27 shows the Login link seen by an anonymous user. Figure 6-28 shows the Logout link seen by an authenticated user.

Aa730863.netvbdev0627(en-US,VS.80).gif

Figure 6-27. LoginStatus control rendered for an anonymous user

Aa730863.netvbdev0628(en-US,VS.80).gif

Figure 6-28. LoginStatus control rendered for an authenticated user

ChangePassword Control The ChangePassword control provides an interface for users to change their passwords. As with the other security controls, ChangePassword can be used with a single line of ASPX markup or it can also be completely customized. The following markup changes only the type of navigation that the control presents to the user: instead of buttons the control will use hyperlinks.

<asp:ChangePassword ID="ChangePassword1" runat="server" 
    CancelButtonType="Link" 
    ChangePasswordButtonType="Link"
    ContinueButtonType="Link">
</asp:ChangePassword>

Aa730863.netvbdev0629(en-US,VS.80).gif

Figure 6-29. ChangePassword control with links instead of buttons for navigation

Site Maps

Site maps are an essential part of many Web sites. They can act as a launching point into any part of your site and help improve search engine rankings. Site maps are commonly maintained as HTML documents, but there are many drawbacks to maintaining a site map as HTML. One of the biggest problems is that site maps contain hierarchical data that can change regularly. Maintaining that data in HTML requires you to think about formatting and placement issues every time you add, move, or delete a page. In ASP.NET 2.0, you can maintain your site map in a special file called web.sitemap. The web.sitemap file for this application looks like this:

<siteMap xmlns="https://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode url="default.aspx" 
      title="Home"  
      description="Home">
      <siteMapNode url="Login.aspx" title="Login"  
        description="Login" />
      <siteMapNode url="CreateUser.aspx" title="Register"  
        description="Register for an account" />
      <siteMapNode url="ChangePassword.aspx" title="Change Password"  
        description="Change Password" />
      <siteMapNode url="RecoverPassword.aspx" title="Recover Password"  
        description="Recover Password" />
      <siteMapNode url="Articles.aspx" title="Articles"  
        description="Articles">
        <siteMapNode url="Article1.aspx" title="Article 1" 
          description="Article 1" />
      </siteMapNode>
    </siteMapNode>
</siteMap>

Every page in your site can be entered in the site map as a <siteMapNode> element. Because every page is a <siteMapNode> element and every <siteMapNode> element can contain other <siteMapNode> elements, you can easily change the structure of your Web site without worrying about indentation and other formatting issues that Webmasters have to struggle with when they maintain site maps as HTML.

Because the site map is stored as XML, it is easy to work with in a number of ways. One of the new data source controls introduced in ASP.NET 2.0 is the SiteMapDataSource. The SiteMapDataSource automatically reads web.sitemap and exposes that data for other controls to use. The following markup is from SiteMap.aspx. It binds a TreeView control to a SiteMapDataSource. The result can be seen in Figure 6-30.

<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1">
</asp:TreeView>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />

Aa730863.netvbdev0630(en-US,VS.80).gif

Figure 6-30. TreeView control bound to a SiteMapDataSource

Another important part of many Web sites is the breadcrumb navigation trail that shows you where you are within the hierarchy of pages in that Web site. The hierarchical information displayed in breadcrumb navigation is generally the same hierarchical data in a site map, so it makes perfect sense to have a control that can create the breadcrumb navigation based on the site map. The SiteMapPath control in ASP.NET 2.0 reads the site map and renders appropriate breadcrumb navigation based on where the current page fits in the site hierarchy. The following ASPX markup is from Articles/Article1.aspx:

<asp:SiteMapPath ID="SiteMapPath1" runat="server">
</asp:SiteMapPath>

When viewed in Internet Explorer, the SiteMapPath produces the navigation path shown in Figure 6-31.

Aa730863.netvbdev0631(en-US,VS.80).gif

Figure 6-31. SiteMapPath control rendered in Article1.aspx

Post-Cache Substitution

The Substitution control, which allows you to inject data into cached pages, is really little more than a placeholder for data to be substituted at run time. The only interesting attribute of the Substitution control is MethodName. The MethodName attribute specifies a method to be called at run time to obtain the data to be substituted for the placeholder in the cached output. The name of the method does not matter, but it has to be shared (static) and must accept an HttpContext object as a parameter.

The ASPX markup that follows is from PostCacheSubstitution/default.aspx. The page contains some static text and a Label control named CacheTime. The current time is put into the Text property of CacheTime when the Page_Load event handler fires. This page uses the @OutputCache directive to cache page output for 60 seconds. That means that the content of the page, including the text in the CacheTime Label control, is generated once and then retrieved from the cache for any subsequent requests during those 60 seconds. The Page_Load event handler will execute on the first request for this page, but it will not execute again until the first request after the content in the output cache expires (after 60 seconds).

The Substitution control is an exception to the output caching model. The callback method specified in the MethodName attribute is called on every request. ASP.NET passes the current HttpContext to the callback method, so it has complete access to the assortment of objects normally available during request fulfillment, such as Request, Server, and User. In this example, the callback method is named Substitute. Substitute returns a string that replaces the Substitution control in the output before it is sent to the user's browser:

<body>
    <form id="form1" runat="server">
    <div>
        Post Cache Substitution<br />
        <br />
        This is cache output<br />
        <br />
        The time this page was cached is:
        <asp:Label ID="CacheTime" runat="server" Text="Label"></asp:Label><br />
        <br />
        Substituted on request at:
        <asp:Substitution ID="Substitution1" runat="server" MethodName="Substitute" />
        <br />
        <br />
        This is cache output</div>
    </form>
</body>

    Protected Shared Function Substitute(ByVal h As HttpContext) As String
        Return DateTime.Now.ToLongTimeString()
    End Function

Figure 6-32 shows the output from this Web page after refreshing the browser 7 seconds after the first request. Notice that the time in the Label is different from the time returned by the Substitute method. Most of the page content came from the output cache, but the part that says, "Substitute on request at: 7:42:27 AM" came from the callback method of the Substitution control.

Aa730863.netvbdev0632(en-US,VS.80).gif

Figure 6-32. Post-cache substitution using the Substitution control

Conclusion

This application has only scratched the surface of some of the new controls available in ASP.NET 2.0. As a Web developer, you will recognize the potential time savings that these new controls offer as well as the opportunity to have a common implementation for things like user management, security-related UI, and site maps that you can use in all your Web sites.

Application: Master Pages and Themes

This application introduces Master Pages and Themes, two new features in ASP.NET 2.0 that provide the built-in skinning and template functionality that was noticeably absent from ASP.NET 1.1.

New Concepts

Almost every real-world Web site composed of more than one page requires a consistent look across pages. Maintaining the same graphic design and layout on every page is cumbersome without some sort of template-based solution. Web developers using various Web technologies have tried many different ways to create template-based Web applications that are easy to create and update. Unfortunately, this is an area where ASP.NET 1.1 was weak.

Many ASP.NET 1.1 developers resorted to the awkward approach of using header and footer user controls that had to be placed on every page. Although tolerable in many situations, this approach provides a poor design-time experience, makes interactions with graphic designers difficult, and can be surprisingly constricting and inflexible when trying to create "skinnable" sites. In fact, header and footer controls are used only to emulate a template-based Web site. Content from individual pages is not injected into a template—individual pages simply contain common elements, the user controls, that produce a consistent look on each page.

**Note   **A "skinnable" site is a Web site that has more than one presentation option for the same content by changing fonts, colors, images, layout, and so on.

ASP.NET developers who were discontented with the "header and footer" approach have chosen alternatives such as custom template-based solutions. Several common techniques for implementing custom solutions can be easily found on the Web. Most approaches make it much easier for graphic designers to create templates that can be selected at run time. Although more flexible than header and footer controls, custom solutions still provide a poor design-time experience and add more complexity for you to manage.

An ideal solution is one that lets your graphic designer create a template with one or more content areas specified. Then as you build individual Web pages, you simply point to the template and indicate which content goes into which content area in the template. As you build these individual pages, the design environment should provide visual feedback so that you know how the final page will look with the content injected into the template.

Master Pages

Master pages in ASP.NET 2.0 provide the design-time and run-time support for true template- based development that Web developers have been longing for. Here are the basic steps to use master pages:

  1. **Create a master template.   **This can be easily done by an HTML graphic designer. Placeholders for page-specific content simply have to be designated using the <asp:ContentPlaceholder> ** server control.
  2. **Make the template a master page.   **Turning a template into an ASP.NET 2.0 master page is as easy as adding the @Master ** directive to the ASPX markup.
  3. **Create individual content pages.   **Individual Web forms can be created as usual except that the content must be placed inside of <asp:Content> ** tags that reference the content placeholders in the template. When working in Visual Studio 2005, you can specify the master page when creating a content page and have the visual designer create the <asp:Content> ** tags for you.
  4. **Reference the master page.   **Any ASP.NET 2.0 Web form can be a content page by referencing a master page in the @Page ** directive. You can either edit the ASPX markup directly or use the Properties window for the Web form.

The visual Web designer in Visual Studio 2005 offers complete support for creating and editing both master and content pages. You will normally create a master page first and then reference the master page when you add new Web forms to your application. The "Walkthrough" section for this application shows you how to do that.

Themes

Master pages make it easy to control the layout of a Web site and define common visual elements, such as a corporate logo in the top-left corner or a navigation bar along the left-hand side of every page. But there are other visual elements you will need to control across a Web site, such as the font and color used for text boxes or formatting for grids of data. Cascading style sheets (CSS) are commonly used to centralize control of many aspects of a Web user interface. Every page in a Web site can reference the same cascading style sheet, giving you the ability to change one file and affect the entire Web site. But cascading style sheets are not a complete solution for ASP.NET developers. For example, a complex control such as the GridView has a number of visual elements that are not easily controlled using CSS. If you want every GridView to use the same fonts and colors, you have to explicitly format each and every GridView.

Themes in ASP.NET 2.0 provide a new way to control the presentation of server controls without abandoning your existing investment in cascading style sheets. A theme in ASP.NET 2.0 is a folder containing style sheets and .skin files. The .skin files define the desired formatting attributes for server controls in your Web application. When the theme is applied to a Web form, the attributes defined in the .skin files are applied to the appropriate server controls. For example, the SmokeAndGlass theme that ships with ASP.NET 2.0 contains the following attributes for Label, TextBox, Button, and LinkButton in a .skin file:

<asp:Label  runat="server"  ForeColor="#585880"
Font-Size="0.9em"  Font-Names="Verdana"  />

<asp:TextBox  runat="server"  BackColor="#FFFFFF"  BorderStyle="Solid"
Font-Size="0.9em"  Font-Names="Verdana"     ForeColor="#585880" BorderColor="#585880"  BorderWidth="1pt" CssClass="theme_textbox"  />

<asp:Button  runat="server"  BorderColor="#585880"  Font-Bold="true" BorderWidth="1pt"  ForeColor="#585880"  BackColor="#F8F7F4"  />

<asp:LinkButton  runat="server"  Font-Size=".9em"  Font-Names="Verdana"/>

When a Label is placed on a Web form, it will have a ForeColor value of #585880 and use the Verdana font. A Button or TextBox will have the same ForeColor. You should note that these entries in the .skin file do not have ID attributes because they do not represent actual instances of server controls. When an instance of a Label control is created on a Web form, it will have its own ID attribute but be assigned the other attribute values from the skin regardless of any attribute values specified for the control in the Web form. The only way to override the visual appearance of a themed control is to disable "theming" on that control by using the EnableTheming attribute:

<asp:Label  EnableTheming="False"  ID="lblText"  runat="server"  Text="A"  />

With ASP.NET 2.0 Themes, you can continue to use your existing cascading style sheet classes and inline style attributes. Style sheet files (.css) in a theme folder are automatically referenced by any themed pages using the <link> tag. However, be forewarned that settings in .skin files usually take precedence over CSS settings because most server controls render attributes such as BackColor as inline styles that will override settings from CSS classes or previous inline styles.

Walkthrough

This walkthrough shows you how to easily provide a consistent user interface across a Web site using master pages and themes.

Creating a Master Page

Master pages are as easy to create as any other ASP.NET Web form. There are only two differences: the @Master directive instead of @Page, and ContentPlaceHolder controls to indicate the places where content pages will inject page-specific content. To create a new master page using Visual Studio 2005, right-click the root node in the Solution Explorer and select Add New Item. In the Add New Item dialog, select Master Page. Figure 6-33 shows the Add New Item dialog with Master Page selected. Notice that the file extension for a master page is .master instead of the usual .aspx extension.

Aa730863.netvbdev0633(en-US,VS.80).gif

Figure 6-33. Add New Item dialog

The design-time experience in the visual Web designer is exactly like working with any Web form. The only difference is the use of ContentPlaceHolder controls. Figure 6-34 shows a master page in design view with a ContentPlaceHolder control in a table.

Aa730863.netvbdev0634(en-US,VS.80).gif

Figure 6-34. Master page in design view in Visual Studio 2005

The ASPX markup behind the master page in Figure 6-34 is very straightforward. Most of the markup is regular HTML for defining an HTML document with a table in the body.

<%@ Master Language="VB" CodeFile="MasterPage.master.vb" Inherits="MasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        &nbsp;<table border="0" cellpadding="0" cellspacing="0" style="width: 100%; height: 100%">
            <tr>
                <td colspan="2" style="height: 200px">
                </td>
            </tr>
            <tr>
                <td style="width: 200px">
                </td>
                <td>
                    <asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
                    </asp:contentplaceholder>
                </td>
            </tr>
            <tr>
                <td colspan="2" style="height: 200px">
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

The @Master directive identifies this Web form as a master page. This particular master page has only one content area identified as ContentPlaceHolder1. The name of the ContentPlaceHolder control is actually very important. Content controls in content pages are associated with placeholders in the master page by using the ID of the ContentPlaceHolder.

Using a Master Page

With Visual Studio 2005, creating a content page that uses a master page is almost effortless. When adding a new Web form to your Web site, select the Select Master Page check box in the Add New Item dialog as shown in Figure 6-35.

Aa730863.netvbdev0635(en-US,VS.80).gif

Figure 6-35. Add New Item dialog for adding a content page

Visual Studio 2005 will next prompt you to select an existing master page from your Web site as shown in Figure 6-36.

Aa730863.netvbdev0636(en-US,VS.80).gif

Figure 6-36. Select a master page for the new Web form

When you open the new content page in the visual Web designer, the layout from the master page is displayed but is dimmed and grayed out, providing a quick visual reference as to how the page will ultimately look and where the page-specific content will be displayed. Visual Studio 2005 creates the Content controls for you, one for each ContentPlaceHolder control in the master page. Figure 6-37 shows a content page using the master page created earlier for this application.

Aa730863.netvbdev0637(en-US,VS.80).gif

Figure 6-37. Content page in design view

The ASPX markup generated for a content page is quite simple because much of the markup that provides the structure for the resulting HTML document is defined in the master page. There are just two key pieces of information required in the content page: the master page file to use, and the mappings between Content controls and ContentPlaceHolder controls. The following ASPX markup shows the source behind the page shown in Figure 6-37:

<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" AutoEventWireup="false" CodeFile="ContentPage.aspx.vb" Inherits="ContentPage" title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    Lorem ipsum dolor sit …</asp:Content>

In the preceding markup, the MasterPageFile attribute is used in the @Page directive to specify the master page. In the Content control, the ContentPlaceHolderID attribute is used to map Content1 in the content page to ContentPlaceHolder1 in the master page.

You can choose a new master page at run time by setting the MasterPageFile property of a Web form. A new master page can be used only if it contains ContentPlaceHolder controls corresponding to all the placeholders referenced by the Content controls in the content page. The MasterPageFile has to be changed before the page is initialized. For example, the following code could be used in the Page_PreInit event handler to select an alternate master page on every fifth load of the page (on average):

Sub  Page_PreInit(ByVal  sender  As  Object,  ByVal  e  As  System.EventArgs) Dim  rnd  As  New  System.Random
If  rnd.Next(1,  5)  =  1  Then
Me.MasterPageFile  =  "~/MasterPage2.master" End  If
End  Sub

This code will fail, however, if MasterPage2.master does not define the same ContentPlaceHolder controls as the original master page file.

Instead of dynamically choosing a master page at run time, you might find it more practical to manipulate the master page programmatically. Master pages in ASP.NET 2.0 are not static templates. Master pages are classes from which objects are instantiated on the server, and they expose a complete set of events. In fact, the MasterPage class inherits from System.Web.UI.UserControl and exposes the same events as a UserControl, including Init, Load, and PreRender events. Furthermore, you can add your own custom properties and methods to a master page that content pages can use to interact with your master page. Consider the following Property added to the master page for this application:

    Public Property SubTitle() As String
        Get
            Return TitleLabel.Text
        End Get
        Set(ByVal value As String)
            TitleLabel.Text = value
        End Set
    End Property

The SubTitle property is a wrapper around the Text property of a Label control added to the master page. From the content page, the following code can be used to set the SubTitle property of the master page:

        Dim m As ASP.masterpage_master = Master
        m.SubTitle = "Now Showing: Content page at " + Now.ToString()

The Master property of the content page is a property exposed by the System.Web.UI.Page class. The Master property returns a MasterPage object that has to be cast to the actual type of your derived master page class. A master page file saved as FileName.master is given the class name FileName_master, so the derived type in this example is MasterPage_master. Once cast properly, the custom properties and methods of the master page can be accessed in a type-safe way.

Using Themes

Themes are controlled using the theme attribute of the @Page directive, or a theme can be specified for an entire Web site in web.config using the <pages> element. Selecting a theme in the @Page directive is very straightforward:

<%@  page  language="VB"  Theme="red"     %>

Specifying a theme for an entire site is just as easy except that the settings go in the web.config file rather than in the @Page directive:

<?xml  version="1.0"  encoding="UTF-8"  ?>
<configuration>
<system.web>
<pages  theme="red"  />   <compilation  debug="false"  />
<globalization  requestEncoding="utf-8"  responseEncoding="utf-8"  />
</system.web>
</configuration>

ASP.NET 2.0 does not ship with any built-in themes, however, you can download sample themes from https://msdn.microsoft.com/asp.net/reference/design/templates/. From here, the basic html "Red" theme was downloaded and used with the site. Figure 6-38 shows the end result when viewed in a browser:

Aa730863.netvbdev0638(en-US,VS.80).gif

Figure 6-38. Content page in Internet Explorer using the Red theme

Creating a Custom Theme

Using the prebuilt themes in ASP.NET 2.0 is fine for testing out the new technology, but it's not very practical for real-world development because every site requires its own visual tweaks to stay aligned with corporate branding strategies or to realize the vision of a graphic design artist. Fortunately, ASP.NET Themes are easy to create. First you create a App_Themes folder in your Web site folder. Then within that Themes folder, you create a folder with the name for your Theme, such as BlackAndWhite. Then you create .css and .skin files in which you can define almost any of the visual characteristics of server controls and other HTML.

Conclusion

Prior to ASP.NET 2.0, Microsoft Web developers were at a disadvantage because of the limited support for robust Web site templates and skins. Many developers relied on the "header and footer user controls" approach for maintaining a consistent look for a Web site, but they were disappointed by the poor design-time experience and the difficulty in integrating the work done by Web graphic designers into the user controls to create visually appealing Web sites. The introduction of master pages and themes in ASP.NET 2.0 makes the creation of template- based Web sites simple and flexible. And the design-time experience in Visual Studio 2005 is exceptional. Combining master pages with themes makes it easy to create professional and consistent-looking Web pages without sacrificing developer productivity or the long-term maintainability of a site.