Understanding the Profile Object of ASP.NET Version2, beta1
Author: Terry Voss

Introduction:

Why is the Profile Class (formally called Personalization) one of the most interesting new feature of ASP.NET version 2 for me? Because unlike membership, roles, and other personalization related areas it has much potential for general increase in state capability that is more efficient than we have had before in the generally stateless web application. Generally it is accepted good architectural advice to keep your app as stateless as possible to support scalability (doing just as well with more volume added), but state is what mainly differentiates us Web developers from the Windows developers in capability, so therefore it could potentially be very important to all aspects of web development.

The Session Object has been so valuable, and Profile has the potential to be an object that works alongside session to improve web applications control of state. Why not just create a session with a 2 year timeout? Because the session is very important for Authentication and Authorization. Running out to lunch and leaving your browser open could cause a read of all the companies passwords or other damage. So wouldn't it be nice if session could be used for that security purpose and if we had a strongly typed object, that was memorywise efficient, with an infinite timeout that we developer's can control. This is a simple description of the Profile object. Now because the profile object is strongly typed, at runtime we cannot add properties as we could with session. So there is no dynamic runtime change of structure with this Profile object. Planning ahead is the way to go here. Also, profile is more intelligent/efficient in the way it stores and reads data. Session always loads the whole session dictionary when you only want to query one value. Profile only looks up data when it is used. Why do I call it an object instead of a class? Because like the session object this Profile object is readily existent for our .ASPX pages. As the session object is held in the HttpContext.Current.Session property, the Profile object is held in the HttpContext.Current.Profile property. This means if you are not in a page object, then import System.Web.HttpContext, and use Current.Profile to access your properties with.

So setting/reading a profile property from a page object method is as simple as:

Profile.Name = "MyName"
Dim MyName as String = Profile.Name

 

and if you are not on a page object but in a custom class method:

Current.Profile.Name = "MyName"
Dim MyName as String = Current.Profile.Name

 

Comparing Profile and Session:
1) Both objects store separate data for each user of your web application without the developer having to worry about coding for such.
2) Session often needs a small timeout value to protect the user enough, while Profile only ends at the developer's discretion.
3) Profile allows control over clearing by inactive profiles versus active ones, time periods etc, while session is one or all.
4) Profile is strongly typed and Session is not.
5) At runtime a key can be added to Session, a property for Profile must be designed in before runtime.
6) Session must load the entire dictionary to read only one value, while Profile can read just one property efficiently (more scalable).
7) Both can store any kind of object as long as the object can be serialized.

Now Session can focus on Authentication/Authorization concerns, since we have a stronger candidate for storing statefulness.


Basic Knowledge:
System.Web.Profile namespace contains 17 classes/enumerations:
 1) DefaultHttpProfile (inherits HttpProfileBase, provides untyped access to profile properties)
 2) HttpProfileBase (provides untyped access to profile properties)
 3) HttpProfileGroupBase (provides untyped access to group profile properties)
 4) ProfileAuthenticationOption (enumeration:All, Anonymous, Authenticated)
 5) ProfileAutoSaveEventArgs (inherits eventargs)
 6) ProfileEventArgs (inherits eventargs)
 7) ProfileInfo (holds info about one user's profile)
 8) ProfileInfoCollection (holds info about all user's profiles)
 9) ProfileMigrateEventArgs (Provides event data for the MigrateAnonymous event of the ProfileModule class)
10) ProfileModule (noninheritable/sealed, implements IHttpModule)
11) ProfileProvider (mustinherit)
12) ProfileProviderAttribute (noninheritable/sealed, inherits Attribute)
13) ProfileProviderCollection (usual collection members, count, add)
14) SettingsAllowAnonymousAttribute (noninheritable/sealed, inherits Attribute)
15) AccessProfileProvider(I couldn't successfully instantiate this, could be Microsoft used class)
16) SqlProfileProvider (I couldn't successfully instantiate this, could be Microsoft used class)
17) ProfileManager (allows mgmt of profile info, like delete types, find types, get numbers of types, of profiles, most members are shared/static)

Use this important last class to manage either type of storage: MSAccess or MSSQLServer, as it has many valuable methods. Two other valuable classes are the ProfileInfo, and ProfileInfoCollection for reporting the user state status as you will see in later code samples.


If you create a new web project and immediately in the default.aspx file code-beside you type:

Dim x as string = Profile.Name
you will get squigglies regarding Name member not existing for the Profile object. With Visual Studio, go to the Website Menu & choose the ASP.NET Configuration Option, choose Profile, choose Add Property, Add Name property, Save. Now go back to your code. Squigglies are gone. This is because at least the following entries have been added to the web.config file.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.Web>
<profile inherits="System.Web.Profile.HttpProfileBase, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
  <properties>
    <add name="MyName" type="System.String" />
  </properties>
</profile>
</system.Web>
</configuration>

If you don't have Visual Studio, use the Profile tab of the Web Site Administration Tool (WAT) to manage how your site collects personal information about visitors. Profile properties represent data specific to a particular user. Using profile properties enables you to personalize your Web site for individual users. The html address for this web application is: http://localhost/AppFolderNameInWebsites/Webadmin.axd
 

Profile property groups allow you to group related profile properties together within a collection. For example, you may choose to group the Title property along with other properties such as First,  MiddleInitial, Middle, Last, Suffix, and in a property group called Name. In this example, you would set the value for the Suffix property with the following line of code:

Profile.Name.Suffix = "Junior"
 

Here is the associated web.config entries:

<profile>
 <group name="Name">
   <property name="Title" type="System.String "/>
   <property name="First" type="System.String "/>
   <property name="Middle" type="System.String "/>
   <property name="Initial" type="System.String "/>
   <property name="Last" type="System.String "/>
   <property name="Suffix" type="System.String "/>
</group>
 <group name="Address">
   <property name="Street" type="System.String"/>
   <property name="City" defaultValue="Redmond" type="System.String"/>
   <property name="State" defaultValue="WA" type="System.String"/>
   <property name="Zip" type="System.String"/>
</group>
</profile>

You can also create a custom type (class) that you use as a profile property. For example, you might have a class named ShoppingCart in a namespace called Sales that you can specify as a profile property and can then associate with each user in your application. So ShoppingCart can be a collection of ItemOrder classes. If you specify a custom type for your profile property, you must specify the fully qualified class name for the type so that ASP.NET can instantiate the class when the application runs. If the ShoppingCart class is defined in your Code folder with a namespace of sales, you would enter the following in the Fully qualified type name box under the Custom data type option:

Sales.ShoppingCart
 

You can also enable tracking by a generated unique ID for anonymous users of your site. This ID is stored in a cookie on the user's computer.


Note: (It is also possible to create custom profile providers. There are two primary reasons for creating a custom profile provider.
1) You need to store profile information in a data source that is not supported by the profile providers included with the .NET Framework, such as a FoxPro database, an Oracle database, or other data sources.
2) You need to manage profile information using a database schema that is different from the database schema used by the providers that ship with the .NET Framework. A common example of this would be user data that already exists in a SQL Server database for a company or Web site.)

After you create a SQLServer provider which is the preferred storage for a larger website for speed/scalability much more is added to your web.config.

<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="CurrentUsers" value="0" />
</appSettings>
<connectionStrings>
<add name="webAdminConnection632332750267812500" connectionString="server=(local);user ID=MyUserName1936;password=MyPassword6820;database=Profile" />
</connectionStrings>
<system.web>
<membership defaultProvider="Developer2 Data">
<providers>
<add connectionStringName="webAdminConnection632332750267812500"
applicationName="/profile" description="All administrative data for the application Profile"
requiresUniqueEmail="false" enablePasswordRetrieval="false" enablePasswordReset="false"
requiresQuestionAndAnswer="false" passwordFormat="Hashed" name="Developer2 Data"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</membership>
<siteCounters defaultProvider="Developer2 Data">
<providers>
<add connectionStringName="webAdminConnection632332750267812500"
description="All administrative data for the application Profile"
commitInterval="90" commitTimeout="60" name="Developer2 Data"
type="System.Web.SqlSiteCountersProvider, System.Web, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</siteCounters>
<roleManager defaultProvider="Developer2 Data" domain="Test">
<providers>
<add connectionStringName="webAdminConnection632332750267812500"
applicationName="/profile" description="All administrative data for the application Profile"
name="Developer2 Data" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</roleManager>
<profile defaultProvider="Developer2 Data">
<providers>
<add connectionStringName="webAdminConnection632332750267812500"
applicationName="/profile" description="All administrative data for the application Profile"
name="Developer2 Data" type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
<properties>
<add name="Name" defaultValue="Terry Voss" type="System.String"
allowAnonymous="true" />
<add name="Address" defaultValue="2403 N Nettleton St." type="System.String"
allowAnonymous="true" />
</properties>
</profile>
<anonymousIdentification enabled="true"/>
<compilation debug="true"/>
</system.web>
</configuration>

In a SqlProvider the tables go in a database of your choice in a Sql Server of your choice. The default provider is an Access database called aspnetdb.mdb in a data folder below your web application folder. Here is what the aspnet_Profile table looks like in SqlServer:

 


Considering the way that this data is being held, I would not feel comfortable putting very much into this PropertyValuesString field. Maybe 100 properties maximum, including all the different uses you have for the Profile before it gets slow. However, the PropertyValuesBinary can hold complex objects of any type. An example of a practical use could be as follows. An enduser has entered data for a therapy assessment for one hour now and is almost finished, but they are called away on an emergency. They forget to save and don't get back until after their session has timed out because this company doesn't want the session timeout longer than 15 minutes for security reasons. A timeout event before the session timeout could save everything to one object's properties. That object could be put into the PropertyValuesbinary to await the user's return to allow them to take off exactly where they left off. I wish I knew (because I'm sure there is good reason, maybe opening time for 2 tables) why Microsoft put all the properties in a text field and then all the values in another text field allowing there to be one table of one row per user. Normal handling of such data would be creation of  a child table with one record for each property linked to the parent record that is in one to one relation with the users by the UserId field. I realize we are serializing here, but I would feel better about the strings being in a database structure versus string structure.

To display generic profile information with the default Profile Provider, here is a front-end .ASPX page:

<%@ Page Language="C#" CompileWith="Profile.aspx.cs" ClassName="Profile_aspx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
  <form id="form1" runat="server">
    <div>
      <asp:GridView ID="gridView1" Runat="server" />
    </div>
  </form>
</body>
</html>

And here is the code-beside:

Imports System.Web.Profile
Partial Class Profile_aspx
 Private Sub Page_Load(sender as object, e as EventArgs)
  If not IsPostBack Then
    gridView1.DataSource = ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All)
    gridView1.DataBind()
  End If
 End Sub
End Class

The above ProfileManager class is used whether you are using MS Access database or MS SQLServer database to get information about current profiles, and you can get other collections like inactive profiles, active profiles, anonymous versus registered users, etc.

 

To get specific listings of your custom properties that are simple types, set datasource equal to the output of the following function:

Private Function GetProfiles() As List(Of ASP.HttpProfile)
  Dim profileInfos As ProfileInfoCollection = ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All)
  Dim profiles As New List(Of ASP.HttpProfile)
  For Each info As ProfileInfo In profileInfos
    profiles.Add(Profile.GetProfile(info.UserName))
  Next
  Return profiles
End Function

(note the usage of the strongly typed collection: list from system.collection.generic namespace) The "of" keyword indicates what sole type the collection can hold. Don't ask me what ASP.HttpProfile type is since there is no help on it, and it seems to be in a temporary ASP.namespace with only this type in it for this beta1 version.

 

Summary:

So the ASP.NET Configuration inteface allows you to design user properties and this info goes into the web.config file so the Profile Object knows what is defined and where to go to get at it in your chosen provider or data store.

 

So one application of the Profile object is to only use session for authentication/authorization purposes, and do data storage for a user in the Profile Object. So change each:

session("MyVar") = 1

to

profile.MyVar = 1

 

x = session("MyVar")

to

x = profile.MyVar

 

Let's create a ShoppingCart Class and use it in Profile: first an ItemOrder class with a simple version of an order, and then a collection container for it, and then the code using it. Note that if you just declare 2 public fields for productID and Quantity and not method properties, your collection of ItemOrder classes will not automatically bind to the GridView or DataGrid. Also note the 2 contstructors or constructor overload as they come into importance later somewhat.

<Serializable()> Public Class ItemOrder

Sub New()
End Sub

Sub New(ByVal qty As Integer, ByVal product As Integer)
_quantity = qty
_productID = product
End Sub

Private _quantity As Integer
Private _productID As Integer

Public Property Quantity() As Integer
Get
Return _quantity
End Get
Set(ByVal Value As Integer)
_quantity = Value
End Set
End Property

Public Property ProductID() As Integer
Get
Return _productID
End Get
Set(ByVal Value As Integer)
_productID = Value
End Set
End Property

End Class
--------------------------------------------

Imports System.Collections.generic

<Serializable()>Public Class ShoppingCart : Inherits List(Of OrderItem)
End Class

--------------------------------------------
Imports System.Web.Profile

Partial Class viewprofile_aspx

Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  If Not IsPostBack Then
    Profile.Cart = New ShoppingCart
  End If
End Sub

Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
  Dim item As New ItemOrder(quantity.Text, product.Text)
  Profile.Cart.Add(item)
  GridView1.DataSource = Profile.Cart
  GridView1.DataBind()
End Sub

End Class

--------------------------------------------
<profile defaultProvider="Developer2 Data">
  <properties>
    <add name="Cart" type="MyNamespace.ShoppingCart" serializeAs="Binary" allowAnonymous="true" />
  </properties>
</profile>
 

Note the little code for ShoppingCart since it inherits List dedicated to only ItemOrder objects. All we need is the inherited Add/Remove/Count etc methods for a cart. Note then that only 2 lines of code add an item to the cart with the button click. And then 2 lines bind the result to the GridView1 control. Note both custom objects must have the attribute for Serializable affixed since this information must be serialized by the Profile Object. There are 2 methods of serialization with the default as XML serialization. I have selected Binary serialization in the above web.config entry at the suggestion of Dino Esposito from his book linked below (page 111). He was using (it seems) pre-Beta1 code and I had no trouble with collections and Profile with either serialization type, except for automatic referencing.

 

In beta1, if you create your custom classes in the code folder it is a dynamically compiled file, but the dynamically generated assembly is not automatically referenced. Therefore currently in beta1 you will get intellisense for your new cart after putting it into the Website menu's ASP.NET configuration option's create profile property area, but you will get a reference error. Currently you will have to manually compile your own classes to a dll that you place into the bin folder under your c:\websites\app\.  I placed my 2 .vb files into the bin folder and used the following compile statement in a batch=.bat file:

 

cd "c:\websites\profile\bin\"

C:\WINDOWS\Microsoft.NET\Framework\v2.0.40607\vbc /out:ShoppingCart.dll /target:library /optimize /recurse:*.vb /r:system.dll,system.data.dll,system.enterpriseservices.dll,system.xml.dll

 

The shopping cart now worked fine, showing the added items in the GridView1 control piling up. When I took the isserializeAs="Binary" out so the the default would be used, I got an error indicating that ItemOrder Class did not have an empty default constructor. I added such and things worked again. The other constructor is simply added so that I can create an ItemOrder instance with one line of code. I very much am hoping that by beta2 the custom type referencing will be automatic, and if you note the one line in the page load event, I wish the profile property allowed me to check that I want a New instance of my type put into the profile custom property so I don't have to do that line like the simple types do.

 

I think the real potential for website statefulness must be measured by packing two complex classes like a ShoppingCart and a SafetyNet discussed above and then stress testing it with Microsoft Application Center, but that must wait until the RTM=Release to Manufacture version is available. Could your website out-personalize Amazon soon?

Links:


 

Books:  (I own the following books below and therefore can attest that their quality is good on Version2 new features)

1. A First Look at ASP.NET v 2.0
by Alex Homer, et al (Paperback)
Avg. Customer Rating: 4.5 out of 5 stars
2. Introducing ASP.NET 2.0
by Dino Esposito; (Paperback)
Avg. Customer Rating: 3.0 out of 5 stars