|
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
to
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? |