Portals

Unleash Your Site's Potential with Web Parts and Personalization in ASP.NET 2.0

Steven A. Smith

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:
  • Easy portal development in ASP.NET 2.0
  • Quick customization techniques
  • Creating Web Parts
  • Personalization and other data stores
  • Credentials and personalization
This article uses the following technologies:
Visual Basic, .NET Framework

Contents

The Solution
The Five Minute Portal Application
Portal Features
Creating a Web Part
Security Controls
Customize the Portal
Personalize the Portal
Configuring Personalization
Storing and Retrieving Profile Data
Anonymous Personalization
Personalization Versus Other State Stores
Personalization Providers
Custom Providers
Conclusion

Imagine that you need to build an intranet site that allows everyone in your organization to view reports and other information based on their login. Imagine also that each user must be able to personalize the site, adding and removing modules that interest them and customizing pieces like weather or news sources to suit their locale and interests. Now think about how you would do this with ASP.NET 1.1 and Visual Studio® .NET 2003, and without any third-party software or tools. How much time do you think it would take? How many lines of code would you have to write? The answers to both of those questions almost certainly represent more time and effort than you or your client would like to spend. ASP.NET 2.0 and Visual Studio 2005 will address this problem with personalized portal solutions.

The Solution

Although the release of ASP.NET 2.0 is still quite a few months away, a preview was released at the PDC 2003. Despite being a very early example of what will eventually ship, this preview includes a powerful IDE and many exciting features, including support for personalized portals (a follow-up Community Technology Preview was released in March 2004 and was used to create the samples for this article). What's more, using many of these new features will require little or no additional code since all of the necessary settings and properties are configured declaratively. With ASP.NET 2.0, it will be possible to solve the problem I just described in a matter of hours (minutes in some cases) writing little more than a few lines of code—if any.

Before I tell you how ASP.NET 2.0 makes this possible, let me address two concerns that seem to always come up when developers first hear of this. The first concern is, "Oh no, there goes my job," which is understandable until you realize that there is still plenty of work left for the developer to do to add value to Web applications or address client needs. ASP.NET 2.0 helps you provide your clients with more value because you're spending less time doing the same repetitive tasks these common solutions call for. This way, you get to spend more time on the fun stuff! The second concern tends to be "Oh no, if I can't write the code, it won't be flexible enough to meet my needs." Fortunately, you still can write code to extend these features however you want—you just won't need to write as much, especially considering the default or typical usage of each feature.

The Five Minute Portal Application

If you have Visual Studio 2005 installed and you haven't already done this, I encourage you to follow along with this next task. I'm going to build a portal with all of the features I've laid out and it will only take about five minutes. (It might take you a bit longer if you're following along, but you'll be amazed at how little work is actually required.)

Figure 1 New Web Site Wizard

Figure 1** New Web Site Wizard **

First, launch Visual Studio 2005 and select File | New Web Site. This will launch the New Web Site Wizard, which features several options for each language, as shown in Figure 1. Select the ASP.NET intranet site and name it "MSDN_Portal" in the default location. Notice that there is no need to set up an IIS Web or virtual directory or to specify an HTTP address. Upon clicking the OK button, the basic site is created, as shown in Figure 2. Notice that when the portal is browsed, the Visual Web Developer Web Server launches on a random port. This Web server comes with Visual Studio 2005, eliminating the need for IIS to be running on the developer's machine. For security reasons, only requests from the local computer are handled by this server.

Figure 2 Basic Home Page

Believe it or not, you're finished! This application includes everything you need. As you can see, the site includes my login information (in this case, the Administrator account in the ISENGARD domain), which it gets from its Windows® authentication features. The site layout is encapsulated in a separate file, so that new pages added to the site can easily share the same appearance. Finally, the different sections of the homepage can be rearranged and customized by the user (and each user can modify them and persist them individually). Each of these sections, like Welcome, Announcements, and My Weather, is a Web Part. If you've used SharePoint™ Portal Server, you're probably familiar with Web Parts. Web Parts will be built into ASP.NET 2.0.

Portal Features

Out of the box, this portal provides a number of features that would require a great deal of code using ASP.NET 1.x. These include built-in authorization and authentication, per-user customization, common layout, dynamic menus based on an XML sitemap file, and more. The most exciting of these features for a portal site has to be the Web Parts and the customization they allow, so let's look at these in more detail.

Web Parts allow individual users to customize a page. Pages that support such customization simply expose a link that lets the user put the page into Edit Mode. Once in Edit mode, the user can modify the settings of individual Web Parts, as well as add, remove, and rearrange Web Parts by dragging and dropping them on the page. Figure 3 illustrates the drag and drop functionality within the portal, along with customized titles and weather information for the default portal application I just built. Take a look at the transparent "Kent OH Weather" Web Part that is being dragged down below "Steve's Links" in the bottom-right.

Figure 3 Customizing a Page

Once the user has finished modifying the Web Parts and their locations on the page, clicking the End Personalization link returns the site to its normal state.

From the developer's perspective, adding Web Parts to an existing page is simple because they're just a special type of user control which are typically hosted within a WebPartZone control. Each page that includes Web Parts also needs a WebPartManager control. The WebPartZone control helps lay out Web Parts on the page and controls their layout, appearance, and colors. The portal that is shown in Figure 3 includes two WebPartZone controls, the Left Zone and the Right Zone.

The WebPartManager control is required on any page that uses Web Parts and is responsible for a lot of the plumbing required to make Web Parts work. It handles events, binding, communication between Web Parts, and calling the proper methods on the Web Parts on the page to ensure that their control tree is built and that they render properly. This control has no user interface and, in the case of the default portal, is simply declared as:

   <asp:webpartmanager id="Webpartmanager1" 
      runat="server"></asp:webpartmanager>
 

The WebPartManager is also used to toggle the page between Edit mode and Normal mode, using its SetDisplayMode method.

Creating a Web Part

Writing your own Web Parts is easy. There are actually three different ways you can do it. If you only need the Web Part for one page, simply add a ContentWebPart Web control to the page and add your content to the control's ContentTemplate. Typically, though, you'll want to build Web Parts that can be reused on multiple pages. This can be accomplished either with a user control or with a custom control that inherits from System.Web.UI.WebPart. For an example of a Web Part that uses the custom control approach, take a look at the WeatherWebPart source code in the /src folder of the portal application.

Most of the time you'll probably create Web Parts from user controls. To demonstrate this, I'll create a "Hello, User" Web Part that can be customized to display any name the user chooses. First, create a new .ascx file, with content of "Hello," and a Label control for the name. Create a local variable, _yourName, as a String. Set the default value of _yourName to "YourName," then create a public property called YourName that gets and sets the value of _yourName. Add the Personalizable and WebBrowsable attributes to the class. Next, register the user control on the containing page (in Visual Studio .NET, drag it onto the page's design surface). Place the user control inside a WebPartZone control and then view the page in a browser.

To change the text of the Web Part, change the page to Edit mode and edit the Web Part. YourName should be listed as one of the custom settings for this control. Change the value of YourName to one of your liking and switch back to Normal view mode.

You should see your greeting, personalized to suit you. Imagine thousands of users being able to customize any individual part of your Web site and you'll have some idea of the potential this holds. The final code for the user control is shown in Figure 4.

Figure 4 User Control

<%@ control language="VB" classname="Greeting"%>
<script runat="server">
    Private _yourName As String = "YourName"
    
    <Personalizable()> <WebBrowsable()> _
    Public Property YourName() As String
        Get
            Return _yourName
        End Get
        Set(ByVal Value As String)
            _yourName = value
        End Set
    End Property
    
    Sub Page_PreRender(ByVal sender As Object, _
             ByVal e As System.EventArgs)
        UserNameLabel.Text = YourName
    End Sub
</script>
Hello <asp:Label ID="UserNameLabel" Runat="Server" />

Security Controls

In addition to Web Parts, the portal also features zero-code authentication through the use of the new built-in security controls. These controls provide functionality to allow users to log in, view their login status, recover their password, or simply display their logged-in user name. All of these functions required custom code in ASP.NET 1.x, but now these common tasks are performed easily and in a very customizable fashion with little or no code required. Keith Brown covers these controls in greater depth in his article in this issue of MSDN® Magazine, but Figure 5 lists the controls and briefly describes their function. All of these security controls have support for skins, allowing them to be easily customized to fit within the look of a particular application or page.

Figure 5 Built-in Security Controls

Control Description
CreateUserWizard Displays a user registration form wizard
Login Displays a login name and password dialog, including support for remembering the user's login (via a cookie)
LoginName Displays the name of the currently authenticated user
LoginStatus Displays Log In or Log Out, as appropriate based on the user's status
LoginView Allows separate content to be displayed, based on the user's authentication
PasswordRecovery Provides the user with a wizard used to have a forgotten password sent and/or reset

Customize the Portal

There are, of course, many things left to be done to customize the sample portal for use by a real application. First, the look of the site and, at the very least, the My Company Name title in the header needs to be updated. Modifying the general layout and appearance of the default portal is easily accomplished by opening the Site.master file. ASP.NET 2.0 supports Master Pages, which provide visual inheritance. Pages can inherit their main appearance from a Master Page. Master Pages can inherit from other Master Pages. Modifications to a Master Page take effect immediately on every page that uses that Master Page.

Two of the best features of this implementation of visual inheritance are the IDE support within Visual Studio 2005 and the ease of implementation. Specifying a Master Page is as simple as adding a Master="site.master" parameter to a Page attribute, and a default application Master Page can be specified in the web.config. IDE support allows WYSIWYG editing of the site.master page and a read-only view of the Master Page layout on any page using that Master Page. Figure 6 shows the default.aspx page in design mode. Note the gray areas in the header and left navigation column. These sections are being displayed from the Master Page, and thus are not available for editing directly from the default.aspx page (although right-clicking and selecting "Edit Master" will open up the Master Page for editing). For some more details on creating Master Pages, see "Master Pages: Master Your Site Design with Visual Inheritance and Page Templates" by Fritz Onion in this issue of MSDN Magazine.

Figure 6 Default.aspx Page in Design Mode

In addition to editing the Master Page directly to modify the look of the site, individual pages can modify the theme—another new feature in ASP.NET 2.0 that provides an easy way to define multiple different looks for a site. A theme is comprised of numerous skins, where each skin describes the look of a particular Web control. Themes and skins will, of course, be easy to create from scratch, but ASP.NET 2.0 will also ship with some global themes. Two such themes available now are BasicBlue and SmokeAndGlass. These are copied into the %SystemRoot%\InetPub\wwwroot\system_web\[version]\Themes folder when ASP.NET is installed. Adding "theme=SmokeAndGlass" to the default.aspx page <%@ Page %> directive results in quite a different look. Creating your own skins and themes is as easy as configuring the appearance of individual Web controls. In fact, it uses the exact same syntax.

One common requirement of portal systems is that users be able to personalize the look of the site by selecting a skin themselves. This capability and a great deal more are handled by the personalization features of ASP.NET 2.0.

Personalize the Portal

As mentioned before, Personalization refers to a user's ability to modify the application to suit them. Ideally, such modifications will persist, so that the user does not need to reapply his changes on each new visit to the site. You have already seen how users can modify the portal application by rearranging the Web Parts on each page. These changes are stored and kept for later visits. In addition to modifying and moving Web Parts, users may also be prompted to select a default theme for the site or for individual pages.

Personalization in ASP.NET 2.0 is very powerful and easy to use. Visual personalization is just the tip of the iceberg. Because Personalization follows the provider model, it is very easy to extend the personalization support in ASP.NET 2.0 to track all kinds of data about individual users and keep it in any data store for which a provider has been written. You can learn more about personalization techniques, including how to build support for personalization in ASP.NET 1.1, by checking out Dino Esposito's Cutting Edge column from March 2004.

ASP.NET 2.0 Personalization supports personalization of anonymous users as well as authenticated users. Access to personalization data is performed through the Profile object, available from the current HttpContext. Unlike Session and other state stores, the Profile object is strongly typed and information is only read as needed, making it both easier to develop against and more efficient. Configuring an application to use personalization and specifying which types of things are stored in the personalization engine is accomplished using a new section in the web.config file.

Configuring Personalization

In order to take advantage of the support for personalization in ASP.NET 2.0, you must first configure it. Luckily, in the case of the intranet sample Web application, this is done for you, with the user data stored in the ASPNetDB.mdb file in the /Data folder by default. To configure the personalization engine to use another data source, such as SQL Server™, you can either use the Web Administration tool or the ASP.NET SQL Server Setup Wizard.

At the time of this writing, the prerelease build of ASP.NET 2.0 that I have been using does not have the personalization section of the Web Application Administration tool implemented yet. At the moment, if you want to configure SQL Server as the provider for personalization, you need to use the aspnet_regsql.exe command-line tool. This tool is also used for Membership and setup of the planned beta feature of SQL Cache invalidation for SQL Server 2000. It can be run either in command-line mode by providing all details on the command line, or in GUI mode. Running the wizard in GUI mode is very straightforward, and after you provide it with the name and connection details for the installation of SQL Server you want to configure, the wizard will run the necessary SQL scripts in order to set up the tables and other objects needed for Personalization.

Once the data source has been set up, the next task is to configure the ASP.NET application for Personalization. This involves editing the web.config file and adding a new element within the <system.web> element, as follows:

<profile>
  <properties>
    <add name="NickName" />
    <add name="College" type="System.String" />
    <add name="BirthDate" type="System.DateTime" />
  </properties>
</profile>

Note that each <add/> subelement of <properties> in the profile defaults to the type System.String, but you can also explicitly set the type. Properties can also be arranged into groups by specifying a <group name = "GroupName">...</group> element within the <properties> element.

Storing and Retrieving Profile Data

Storing information in the current user's Profile is simple since the Profile object is now accessible directly from any ASP.NET page. Unlike other state containers, like Session and Cache, which use key/value pairs and store everything as an Object, the Profile object is strongly typed and exposes all supported properties as actual properties of the class. Thus, to set the current user's NickName property to the value passed in from a TextBox, the code would be something like this:

Profile.NickName = TextBox1.Text;

Another great thing about the Profile object is that it has full IntelliSense® statement completion support. Instead of having to flip back to web.config to see what you named a particular property, you can just type "Profile." in Visual Studio and all of its available properties and methods will be displayed in a pop-up dialog.

Retrieving data from the Profile object is as simple as adding data to it. For instance, to display the user's BirthDate in a Label, you might do this:

Label1.Text = Profile.BirthDate.ToShortDateString();

Note once again that unlike the Session or Cache collections, the Profile returns a strongly typed object, so no casting is necessary.

Anonymous Personalization

Normally, Personalization works because the user has logged into the application, so his preferences can be stored and attached to his user credentials. However, sometimes you want to track information about a particular user without requiring that he first log in. This is a common requirement for an e-commerce store, allowing the user to browse and add items to his shopping cart without having to log in, and then collecting all of the needed information when the user is ready to check out. The ASP.NET 2.0 Personalization features provide support for this kind of scenario through anonymous personalization.

Anonymous personalization is disabled by default, and thus requires a few configuration changes to set up. Anonymous identification must be enabled and then each property in the profile that will support anonymous usage must be configured explicitly. Building on my current sample, adding support for anonymous access to a shopping cart would look something like the code that is shown in Figure 7.

Figure 7 Shopping Cart Access

<system.web>

<anonymousIdentification enabled="true" />
    
        <profile >
            <properties>
                <add name="NickName" type="System.String" />
                <add name="College" type="System.String" />
                <add name="BirthDate" type="System.DateTime" />
                <add name="Cart" 
                   type="Msdn.ShoppingCart, Msdn.ShoppingCart" 
                   serializeAs="XML" />
            </properties>
        </profile>
. . .
</system.web>

Note the serializeAs attribute, which in this case has been set to XML for the Msdn.ShoppingCart object. It defaults to String, but can also be set to XML, Binary, or ProviderSpecific to support whatever persistence method is being used.

By default, anonymousIdentification uses a cookie to track the anonymous user. However, if the user's browser does not support cookies, there are supported alternatives including an AutoDetect option (cookieless="AutoDetect") which will use cookies if supported, or store the anonymous ID in the URL otherwise. Finally, anonymous identification supports two events which can be handled to provide additional control over the process.

The AnonymousIdentification_OnCreate event is raised when the anonymous ID is created. It allows the generated anonymous ID to be overwritten in case you need to use a custom scheme for specifying the IDs of anonymous users. The AnonymousIdentification_OnRemove event is raised when an anonymous user authenticates (and thus is no longer anonymous). Its purpose is to let you clean up any data associated with the anonymous ID.

Just by configuring site settings this way, anonymous users will be able to store their personal information in their profile and have this data persist between requests and visits (from the same machine, assuming they haven't cleared their cookies). However, another necessary feature of anonymous personalization is the ability to transfer settings from the anonymous profile to an authenticated profile once the user registers or logs in. This is done by handling the Personalization_OnMigrateAnonymous event, which is raised after the AnonymousIdentification_OnRemove event. To migrate the anonymous user's shopping cart once they log in (perhaps during the checkout process), you would use something like this in your global.asax file:

void Personalization_OnMigrateAnonymous(Object sender, 
    PersonalizationMigrateEventArgs e)
{
    Profile.Cart = Profile.GetProfile(e.AnonymousId).Cart;
}

Personalization Versus Other State Stores

How does the Personalization engine compare to other state storage mechanisms? The obvious comparison is between the Profile object and the Session object, since both have per-user scope. The Profile object offers several key advantages over the Session object that make it an ideal place to store user information that you know you'll need at design time.

The Profile object is strongly typed, which makes it perform better than the Session object (Session requires explicit casts whenever objects are read from it). The Profile object also makes it much easier to work within an integrated development environment, where statement completion and compile-time checking ensure that the proper values are being accessed, unlike the Session object, which uses simple string values as keys.

Profile data is persisted beyond the user's session and can be stored in any of a variety of data stores. Session state, on the other hand, only lasts until the user is inactive for some period of time. In addition, Profile data is only read when needed, and session data is requested on every user request. (Session usage can be optimized on a page-by-page basis by setting the page directive EnableSessionState to either false or readonly as needed by the page.) Session data also continues to use memory resources long after the user has stopped using the application, whereas Profile data does not cause this memory to be held onto.

For all of those negatives, Session does have some advantages. Personalization requires some configuration and setup, though only once and minimally so. Session just works by default, though it supports configuration. Session data will generally be read and written faster than Profile data, assuming it is configured using the default InProc settings, because it is simply stored in memory. However, if you're using a state server or SQL Server for session state, this advantage disappears completely.

Personalization Providers

Personalization uses Microsoft® Access by default for its data store, though it can be configured to use SQL Server as well as custom user-written providers. A provider is simply a class that handles moving the profile data from the Web application to some kind of data store, and back again. The provider design pattern is a key feature of the ASP.NET 2.0 release and can be seen in a number of features. Custom providers can be used to map the Profile data to existing Microsoft databases (rather than using the personalization SQL schema) or to reference a data store that is not supported by default, such as Oracle or XML files. Multiple providers can be used within the same application. Each property can specify a provider that is used to access it, or properties can be arranged into groups, and each group associated with a separate provider.

For example, the following configuration will store some user data using the default AspNetAccessProvider provider, and some data using the AspNetSqlProvider. Note that each of these is defined in the machine.config file, and the AspNetAccessProvider is used as the default simply because it appears first in the machine.config file. It is recommended that you have each application specify its default provider by setting it on the <profile> element, as shown in Figure 8.

Figure 8 Personalization Provider

<profile defaultProvider="AspNetAccessProvider">
  <properties>
    <group name="Commerce">
      <add name="Cart"
        type="Msdn.ShoppingCart, Msdn.ShoppingCart" 
        serializeAs="XML" 
        provider="AspNetSqlProvider"
      />
    </group>
    <group name="UserInfo">
      <add name="NickName" />
      <add name="College" />
    </group>
  </properties>
</profile>

The UserInfo data in Figure 8 will be accessed using the AspNetAccessProvider, which is the default, while the Commerce data will be accessed via the AspNetSqlProvider. Even if you are only going to use a single provider for all of your Personalization settings, it can be useful to group like items together, since they are then referenced by group on the Profile object. For instance, to store something in the College property defined in Figure 8, you would use code like this:

Profile.UserInfo.College = CollegeTextBox.Text;

Custom Providers

In addition to the providers that ship with ASP.NET 2.0, you will also be able to write your own providers. This is fairly straightforward and will make some of the new features like Personalization and Membership usable by many more people than would have otherwise been possible, since many organizations will not want to have to use a new data store for this information. By writing your own provider, you have complete control over how the data is stored and retrieved for personalization. To store profile data in an alternate data store, such as an XML file on the Web server, you must create a class that inherits from the System.Web.Profile.ProfileProvider class. This base class provides some common functionality and specifies all of the properties and methods that your custom provider (XmlProfileProvider, for example) would need to implement in order to work as a provider for the Profile feature. Unfortunately, the full implementation of an XmlProfileProvider is beyond the scope of this article, but once it's been written, the following configuration snippet demonstrates how one would reference it:

<profile defaultProvider="XmlProfileProvider">
  <providers>
    <add name="XmlProfileProvider" 
type="Msdn.XmlProfileProvider, XmlProfileProvider" />
  </providers>
  <properties>
    <add name="NickName" />
    <add name="College" />
  </properties>
</profile>

The type attribute in the <add> element of the <providers> element should be formatted so that it specifies the fully qualified class name of the provider, followed by the name of the assembly in which it is located (without the .dll extension), which should be in the application's /bin folder or in the Global Assembly Cache.

For information on the Provider design pattern, see Rob Howard's "Provider Model Design Pattern and Specification, Part 1,".

Conclusion

The ASP.NET 2.0 portal framework is an excellent example of how a variety of the new features can work together to provide a powerful, complete solution. The new security controls make user authentication a breeze. Master Pages and themes make it child's play to manage the appearance of all the pages in the site. Web Parts provide a powerful way to allow users to personalize the site to suit their individual needs, and the Personalization/Profile features of ASP.NET 2.0 provide an easy-to-use API for tracking details about individual users within the application. It's clear that the ASP.NET team at Microsoft has targeted this common Web application archetype with many productivity-enhancing features that work in concert to produce a whole that is more than just the mere sum of its individual (Web) parts.

Steven A. Smith is president of AspAlliance.com and DevAdvice.com, two .NET-oriented developer communities. He is co-author of the ASP.NET Developer's Cookbook (SAMS, 2003) and is working on a 2.0 edition. He is a Microsoft ASP.NET MVP, and provides training on .NET through his company, ASPSmith.com.