Building a custom registration page
using the CreateUserWizard control is a 5 minute task in ASP.NET 2. However what happens if you want to do things slightly differently? You want to use the Email address as the Username, and you also want to store common fields such as Firstname and Lastname in standard tables (rather than have them encrypted in aspnet_profile). The solution is easy but not immediately obvious (not to me anyway). First of all lets walk through a common scenario.
A fairly typical registration form would contain the following fields :-
By default the create user wizard codelessly manages Username, Email, Password, Secret Question/Answer straight out of the box. Sadly, by configuration alone, you cannot change the Wizard to use Email Address as the Username, nor will it recognise Firstname or Lastname. To my mind, these 2 options are so common that Microsoft should have built the functionality straight in.
Thankfully, the solution is still pretty easy - with the use of the ASP.NET Profile System and the profile model. First to support the extra fields: adding the below to the Web.Config file is all that is needed to configure the website's profile system to allow the storage of Firstname, Lastname and HairColour.
<profile defaultProvider="SqlProvider">
<providers>
<clear />
<add name="SqlProvider" type="System.Web.Profile.SqlProfileProvider"
connectionStringName="SqlServices"
applicationName="MyApplication"
description="SqlProfileProvider"/>
</providers>
<properties>
<add name="Firstname" type="string"/>
<add name="Lastname" type="string"/>
<add name="HairColour" type="string"/>
</properties>
</profile>
Now add the FirstName, LastName and HairColour labels and input boxes to the CreateUserWizard template, and then code the following event handler.
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
ProfileCommon p = (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);
p.Firstname = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Firstname")).Text;
p.Lastname = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Lastname")).Text;
p.HairColour = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Haircolour")).Text;
p.Save();
}
This is all that is required to save the new fields to the profile data store, which in this case is SQL Server. So far this all seems fine, until we look at the Profile data model.
The aspnet_Profile stores the data in a pretty weird fashion.
| Field Name |
Values |
PropertyNames |
FirstName:S:0:6:LastName:S:7:6: |
PropertyValuesString |
Andrew Rimmer |
Basically the field value pairs get stored in a way that is easy to extract programmatically but is difficult to query via database queries alone. You can understand why it was done this way, it is completley extensible, and the database schema is fixed. If Marketing decide they want to capture eye colour too, its a quick change to the Wizard HTML and the Web.Config - No database changes required. I believe this is fine for HairColour and EyeColour, and other such questions but what about FirstName and Lastname. For any system of size I would never consider storing this data in such a retarded way. Thankfully some clever guys have come up with alternative providers to get around this problem.
Meet the SqlTableProfileProvider and the SqlStoredProcedureProfileProvider. These 2 providers have been developer by Hao Kung - a software engineer at Microsoft. They are pretty simple and effective.
Basically the SqlTableProfileProvider allows you to map Profile properties to database columns, whilst the SqlStoredProcedureProfileProvider allows you to map Profile properties to Stored Procedure parameters. This means that you can store Profile properties anywhere within your data model - in clear fields. I think for Firstname and Lastname this is a must, and possibly for quite alot of other fields depending on your circumstances.
The beauty of the Provider model really shines when it comes to integrating this new functionality. The only changes that are required are the inclusion of the providers into the project, and then an update to the Web.Config. BOOM! But is there a snag? Have we compromised by changing our Profile Provider. If we start adding new fields to the Registration form, will we have to extend our database schema? The answer is No, or more accurately the answer is 'it is up to you'. You see, the Profile provider model allows you to choose which provider to use for which field. This allows you granular control over how your profile is to be stored.
For my example above, I would definintely want to store Firstname and Lastname as clear fields in my database, but I wouldn't be bothered about storing Haircolour in the clear, as this isn't a core business field.
So we need to configure the Profile system to use multiple profile providers, and this is how to do it.
First create a table to store the Firstname and Lastname.
CREATE TABLE [dbo].[UserProfile](
[UserID] [uniqueidentifier] NOT NULL,
[Firstname] [varchar](50) NULL,
[Lastname] [varchar](50) NULL,
[LastUpdatedDate] [datetime] NULL,
CONSTRAINT [PK_UserProfile] PRIMARY KEY CLUSTERED
( [UserID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Now update the configuration...
<profile defaultProvider="SqlProvider">
<providers>
<clear />
<add name="SqlProvider"
type="System.Web.Profile.SqlProfileProvider"
connectionStringName="SqlServices"
applicationName="MyApplication"
description="SqlProfileProvider" />
<add name="TableProfileProvider"
type="Microsoft.Samples.SqlTableProfileProvider"
connectionStringName="SqlServices"
table="UserProfile"
applicationName="MyApplication"/>
</providers>
<properties>
<add name="Firstname" defaultValue="[null]"
customProviderData="Firstname;varchar"
provider="TableProfileProvider"/>
<add name="Lastname" defaultValue="[null]"
customProviderData="Lastname;varchar"
provider="TableProfileProvider"/>
<add name="HairColour" type="string"/>
</properties>
</profile>
And that is it. We now store important fields where they belong - in normal database fields, whilst retaining the extensibility of the Profile system by allowing non-essential fields to be munged into the aspnet_profile table.