Tip/Trick: Gathering Custom User Registration Information

Problem

You are building a web-site that allows customers to register new users on the site.  As part of the registration process, you naturally want to allow them to create a username and password.  You also want them to enter additional personalization/registration data like their address, zipcode, gender, age, etc. and associate it with the newly created account.  You want to create an intuitive wizard-like navigation UI that enables customers to easily manage this workflow.

Solution

ASP.NET 2.0 now provides a built-in control -- <asp:createuserwizard> -- that provides developers with an easy way to create user registration workflows for their site.  The <asp:createuserwizard> control provides built-in UI to enable an end-user to choose a username and password.  The <asp:createuserwizard> control also allows developers to specify additional "custom steps" of information to gather.   

These custom steps can be defined using additional wizardstep templates defined within the <asp:createuserwizard> control itself -- and so can contain any custom UI the developer wants.  For example:

<asp:WizardStep ID="CreateUserWizardStep0" runat="server">
    <div class="title">
        Billing Information
    </div>
    <div class="address">
       <span>Billing Address:</span>
       <asp:TextBox ID="BillingAddress" MaxLength="50" runat="server"  />
       <asp:RequiredFieldValidator ControlToValidate="BillingAddress" ErrorMessage="Address required!" runat="server"/>
    </div>
</asp:WizardStep>

The CreateUserWizard control will then automatically add "next"/"previous" navigation UI to enable an end-user to skip forward and back throughout the registration process.

The CreateUserWizard control exposes a "CreatedUser" event that you can handle within your page to add logic to retrieve the information collected within the various WizardSteps, and store it within whatever database or profile store you want.  This event fires after the user has been created within the ASP.NET Membership system.  However, the user is not yet logged into the ASP.NET site at this point (they won't be logged in until the page redisplays).  So to obtain the username of the newly created username you should access the CreateUserWizard.UserName property.

For example:

Sub CreateUserWizard1_CreatedUser(Sender as Object, E as EventArgs) Handles CreateUserWizard.CreatedUser

   ' Obtain a reference to the "BillingAddress" textbox in the first step of the wizard

   Dim BillingAddress as TextBox

   BillingAddress = CreateUserWizardStep0.ContentTemplateContainer.FindControl("BillingAddress")

 

  ' Todo: Store the BillingAddress.Text value in a database or profile store

End Sub

Two possible places you could perist this custom user information are:

1) Within a custom database table that you create and define.  You could replace the above "todo" statement in the CreatedUser event handler to insert this data into the database table using whatever data API you prefer (for example: ADO.NET or a Strongly Typed Table Adapter). 

2) Within the new ASP.NET 2.0 Profile system.  The ASP.NET Profile system provides a way to automatically persist additional properties/values about a user in a persistent store, and provides a strongly typed API that enables you to easly set/retrieve them (so for example you could just write Profile.BillingAddress to access the value).  The ASP.NET Profile system is by default mapped against an XML blob-like column within a database (which makes it easy to setup).  Alternatively, you can use the ASP.NET SQL Table Profile Provider to map the Profile API against a schematized SQL table or set of SPROCs.  This gives you the nice strongly typed Profile API against a regular SQL table (which makes data-mining easier).

To learn more about how to use the <asp:createuserwizard> control and download sample code that uses it I'd recommend reviewing these articles:

Customizing the CreateUserWizard Control: This article was published in July of 2006, and provides a good walkthrough of the customization capabilities of the CreateUserWizard control, and how to store custom user properties directly within a database.

How to add a Login, Roles, and Profile system to an ASP.NET 2.0 app in 24 lines of code: This is a sample I put together towards the end of 2005 that shows how to integrate these three features in ASP.NET 2.0 to create a custom registration and profile management system.

CreateUserWizard Samples within the ASP.NET QuickStart tutorials: This page provides a number of samples (in both VB and C#) that you can use to understand how to use the CreateUserWizard control better.

Profiles in ASP.NET 2.0: K. Scott Allen posted this nice article which does a good job providing an overview of the new ASP.NET 2.0 Profile API.

ASP.NET 2.0 Membership, Roles, Forms Authentication and Security Resources: This blog post of mine contains a ton of ASP.NET security informationa and useful links.  I'd recommend reviewing it to explore more about ASP.NET security.

Credits

Erich Peterson and K. Scott Allen for the nice articles I listed above.

Hope this helps,

Scott

Published Wednesday, July 5, 2006 9:54 PM by ScottGu

Comments

# re: Tip/Trick: Gathering Custom User Registration Information

Thursday, July 6, 2006 3:08 PM by el xiii

just a little comment: try using more floats and <label> and w3c stuffs please

# re: Tip/Trick: Gathering Custom User Registration Information

Thursday, July 6, 2006 4:00 PM by ScottGu

Hi El,

I just updated the sample to use <divs> instead of tables.

Thanks,

Scott

# re: Tip/Trick: Gathering Custom User Registration Information

Thursday, July 6, 2006 5:54 PM by Mikeon

I have found working with most of the standard templated controls a pain in the .... .

In case of the Wizard control the problem is with no strongly typed access to the controls and what this means is obvious: from the lack of compile time errors to refactoring problems etc.

I have also found that in most cases it is better to use the ASP.NET 1.1 approach and put the controls on the page inside some panel and handle navigation by hand.

I mean it. It will save you a lot of time.

# re: Tip/Trick: Gathering Custom User Registration Information

Saturday, July 8, 2006 2:45 PM by John Greek

Scott:

Have you tried uniting the .NET 2.0 Profile provider and the new Web Application Project model within the same application?  This combination has not worked for me, as the code generated Profile object is never auto-generated. Please share details or provide to a link with information.

Thanks, John

# re: Tip/Trick: Gathering Custom User Registration Information

Sunday, July 9, 2006 12:21 PM by ScottGu

Hi John,

There is actually a plug-in available that enables strongly typed Profile's for WAP project types.  You can download it from here: http://www.gotdotnet.com/workspaces/workspace.aspx?id=406eefba-2dd9-4d80-a48c-b4f135df4127

Hope this helps,

Scott

# re: Tip/Trick: Gathering Custom User Registration Information

Sunday, July 9, 2006 12:32 PM by ScottGu

I have a comment I accidentally deleted I think (I thought I approved it -- but can't seem to find it now and so might have hit the wrong button) asking for how to retrieve the control from the template using C# instead of VB.  Here is the code-snippet:

TextBox BillingAddress = (TextBox) CreateUserWizardStep0.ContentTemplateContainer.FindControl("BillingAddress");

string address = BillingAddress.Text;

Hope this helps,

Scott

# re: Tip/Trick: Gathering Custom User Registration Information

Wednesday, July 12, 2006 2:10 PM by Ralph Rivas
I can't get this to work as I need it to. What I am doing is adding a drop down list which is dynamically filled (e.g. select a state or country) but I cannot get a reference to the custom control I placed on the wizard no matter what I do. When I use the immediate windows to scour through the tree, I never find the control I created. For that matter, setting the ID on the step does not appear in intellisense at all, at least not in any main load events. Am I missing something because all this time I could have whipped up a registration page 3 times over. thanks!

# re: Tip/Trick: Gathering Custom User Registration Information

Wednesday, July 12, 2006 7:06 PM by Bill Burrows
I would like to reinforce "Mikeon"'s comment about the difficulty working with the template controls. Statements like: BillingAddress = CreateUserWizardStep0.ContentTemplateContainer.FindControl("BillingAddress") and TextBox BillingAddress = (TextBox) CreateUserWizardStep0.ContentTemplateContainer.FindControl("BillingAddress"); are way too low level. There needs to be a better way of exposing the contents (IMHO). bill

# re: Tip/Trick: Gathering Custom User Registration Information

Wednesday, July 26, 2006 8:59 AM by John B
Why are you using BillingAddress = CreateUserWizardStep0.ContentTemplateContainer.FindControl("BillingAddress") to obtain the value of the BillingAddress textbox and not using the following: Dim BillAddress as string = BillingAddress.Text ??

# This property cannot be set for anonymous users.

Wednesday, August 23, 2006 10:05 AM by Rick Stephens
I'm going nuts trying to combine the createuserwizard and WAP. I'm using the plugin found at gotdotnet, and yet as soon as I try to set profile properties such as first name and last name to the default provider, I get "This property cannot be set for anonymous users." The goal is to create a user, set profile info, and log them in. It seems like such a common task, and I have it working in a website project fine. HELP!

# re: Tip/Trick: Gathering Custom User Registration Information

Saturday, August 26, 2006 3:13 AM by ScottGu

Hi Rick,

Can you send me an email describing the problem more?  I can then loop you in with the guy who wrote the gotdotnet profile plugin to help.

Thanks,

Scott

# re: Tip/Trick: Gathering Custom User Registration Information

Friday, September 22, 2006 4:04 AM by saad
I tried this code and one thing i found out was that you could get the billing address using only: string address = BillingAddress.Text; as well

# re: Tip/Trick: Gathering Custom User Registration Information

Thursday, November 16, 2006 10:37 PM by jonnyO

Hi

Okay everyting works fine - I can create a UserId related Membership, Profile, User and tbl_UserDetails (extra fields) table record  but... I'm trying to RETURN SCOPE_IDENTITY the new record "ID" from the tbl_Details INSERT stored procedure and save it as a Profile value (to be used instead of the UserId).  I can't figure out how to do this.  Any ideas?

Thanks

Imports Telerik.WebControls

Imports SITE.DAL.DBAccess

Imports System.Data

Imports System.Data.SqlClient

Partial Class Site_Pages_Registration

   Inherits System.Web.UI.Page

   Protected WithEvents Update_Date As WebControls.HiddenField

   Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

       If Session("Record") IsNot Nothing Then

           CreateUserWizard1.MoveTo(CompleteWizardStep1)

       End If

   End Sub

   Protected Sub CreateUserWizard1_CreatedUser(ByVal sender As Object, ByVal e As System.EventArgs) Handles CreateUserWizard1.CreatedUser

       Dim user As MembershipUser = Membership.GetUser(CreateUserWizard1.UserName)

       If user Is Nothing Then

           Throw New ApplicationException("Can't find the user.")

       End If

       Dim DetailsInsert As SqlDataSource = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("SQLDSInsert"), SqlDataSource)

       Dim UserId As Guid = DirectCast(user.ProviderUserKey, Guid)

       Session("NewUserId") = UserId

       Dim Details_FirstName As TextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_FirstName"), TextBox)

       Dim Details_LastName As TextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_LastName"), TextBox)

       Dim Details_MI As TextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_MI"), TextBox)

       Dim Details_Title As TextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Title"), TextBox)

       Dim Details_Company As TextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Company"), TextBox)

       Dim Details_1Comm As Telerik.WebControls.RadMaskedTextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_1Comm"), Telerik.WebControls.RadMaskedTextBox)

       Dim Details_1CommType As Telerik.WebControls.RadComboBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_1CommType"), Telerik.WebControls.RadComboBox)

       Dim Details_2Comm As Telerik.WebControls.RadMaskedTextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_2Comm"), Telerik.WebControls.RadMaskedTextBox)

       Dim Details_2CommType As Telerik.WebControls.RadComboBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_2CommType"), Telerik.WebControls.RadComboBox)

       Dim Details_Address As TextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Address"), TextBox)

       Dim Details_City As TextBox = CType(CreateUserWizardStep0.ContentTemplateContainer.FindControl("Details_City"), TextBox)

       Dim Details_State As Telerik.WebControls.RadComboBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_State"), Telerik.WebControls.RadComboBox)

       Dim Details_Zip As Telerik.WebControls.RadMaskedTextBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Zip"), Telerik.WebControls.RadMaskedTextBox)

       Dim Details_UserTypeId As Telerik.WebControls.RadComboBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_UserTypeId"), Telerik.WebControls.RadComboBox)

       Dim Details_ReceiveEmail As CheckBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_ReceiveEmail"), CheckBox)

       DetailsInsert.Insert()

       Dim UserType As Telerik.WebControls.RadComboBox = CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_UserTypeId"), Telerik.WebControls.RadComboBox)

       Session("UserType") = UserType.SelectedValue.ToString

   End Sub

   Protected Sub SQLDSInsert_Inserting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceCommandEventArgs)

       Dim user As MembershipUser = Membership.GetUser(CreateUserWizard1.UserName)

       e.Command.Parameters("@UserId").Value = user.ProviderUserKey

       e.Command.Parameters("@RETURN_VALUE").Direction = ParameterDirection.ReturnValue

       Dim Record As Integer = e.Command.Parameters("@RETURN_VALUE").Value

       Dim pb As ProfileBase = ProfileBase.Create(user.UserName)

       pb.SetPropertyValue("FirstName", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_FirstName"), TextBox).Text)

       pb.SetPropertyValue("LastName", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_LastName"), TextBox).Text)

       pb.SetPropertyValue("UserName", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("UserName"), TextBox).Text)

       pb.SetPropertyValue("Record", Record.ToString)

       pb.SetPropertyValue("Company", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Company"), TextBox).Text)

       pb.SetPropertyValue("Title", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Title"), TextBox).Text)

       pb.SetPropertyValue("Comm1", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_1Comm"), Telerik.WebControls.RadMaskedTextBox).Text)

       pb.SetPropertyValue("Comm1Type", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_1CommType"), Telerik.WebControls.RadComboBox).SelectedValue)

       pb.SetPropertyValue("Comm2", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_2Comm"), Telerik.WebControls.RadMaskedTextBox).Text)

       pb.SetPropertyValue("Comm2Type", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_2CommType"), Telerik.WebControls.RadComboBox).SelectedValue)

       pb.SetPropertyValue("Address", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Address"), TextBox).Text)

       pb.SetPropertyValue("City", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_City"), TextBox).Text)

       pb.SetPropertyValue("State", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_State"), Telerik.WebControls.RadComboBox).SelectedValue)

       pb.SetPropertyValue("Zip", CType(CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Details_Zip"), Telerik.WebControls.RadMaskedTextBox).Text)

       pb.Save()

   End Sub

   Protected Sub CompleteButton_Click(ByVal sender As Object, ByVal e As System.EventArgs)

       Dim lblRegisterCompleteName As Label = CreateUserWizard1.CompleteStep.FindControl("lblRegisterCompleteName"), Label

       lblRegisterCompleteName.Text = Profile.FirstName & " " & Profile.LastName

       Dim Muser As MembershipUser = Membership.GetUser(CreateUserWizard1.UserName)

       If Muser Is Nothing Then

           Throw New ApplicationException("Can't find the user.")

       End If

       Muser.IsApproved = False

   End Sub

   Protected Sub btnTeamProfile_Click(ByVal sender As Object, ByVal e As System.EventArgs)

       Response.Redirect("Profile.aspx")

   End Sub

   Protected Sub CompleteWizardStep1_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles CompleteWizardStep1.PreRender

       Dim btnTeamProfile As Button = CType(CompleteWizardStep1.ContentTemplateContainer.FindControl("btnTeamProfile"), Button)

       If Session("UserType") = "2" Then

           btnTeamProfile.Visible = True

       Else

           btnTeamProfile.Visible = False

       End If

   End Sub

   Protected Sub SQLDSInsert_Inserted(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceCommandEventArgs)

       Response.Redirect("Registration.aspx")

   End Sub

End Class

# re: Tip/Trick: Gathering Custom User Registration Information

Saturday, November 18, 2006 5:51 PM by Vishant

Hi Scott,

Very Useful information.

I have two troubles though :-)

(1) Validation controls are not firing and causing postback on click of "Next" button. I have also specified ValidationGroup="MyCreateUserWizardStep" but no luck.

(2) As the "Next" button is automatiocally added in <asp:WizardStep ID="CreateUserWizardStep0"...> control, I can not set it's text property. Our site is English and French and I want to localize text of Next button. How can I do it?

Please help.

# re: Tip/Trick: Gathering Custom User Registration Information

Tuesday, November 21, 2006 8:30 PM by Vishant

I solved above problems.

(1) Validation controls are working now. I don't have to set ValidationGroup="MyCreateUserWizardStep"

(2) Now I am setting property StartNextButtonText in <asp:CreateUserWizard/> in codebehind file.

# re: Tip/Trick: Gathering Custom User Registration Information

Tuesday, December 19, 2006 7:03 AM by Amitabh

Hi,

I m trying to use the CAPTCHA validation with the Create User Wizard and trying to trap the validation of the CAPTCHA control in the CreatingUser event. But after that, I'm not able to continue with the user creation or not able to show the error message to the user.

I google a lot, but found nothing that helps me.

Thanks in Advance,

Amitabh

# re: Tip/Trick: Gathering Custom User Registration Information

Monday, January 15, 2007 6:57 AM by Rahul

How do we combine user account creation and additional registration data saving into a single transaction unit? Basically, if I add custom fields into the CreateUserWizardStep, I would like to either save ALL information or none.

Please help.

# re: Tip/Trick: Gathering Custom User Registration Information

Friday, January 19, 2007 10:37 PM by ScottGu

Hi Rahul,

Unfortunately there isn't an easy way to wrap both of them into a single transaction.  They use separate connections - so will complete as two transactions instead of one.  What I'd recommend instead doing is potentially doing a roll-back operation in your code in the case of failure.

Hope this helps,

Scott

# re: Tip/Trick: Gathering Custom User Registration Information

Friday, March 16, 2007 5:33 PM by marmot

hi,Scott:

Can you add extra controls(textbox, label, dropdownlist etc.) to "CreateUserWizard" control without generating extra "CreateUserWizardStep" and save those information in additional table instead of profile system. The solution in article "Customizing the CreateUserWizard Control" can only work for "CreateUserWizard control" with extra "CreateUserWizardStep".

Can you confirm this?

thanks.

marmot

# re: Tip/Trick: Gathering Custom User Registration Information

Monday, March 19, 2007 3:12 AM by ScottGu

Hi Marmot,

You can either create new steps, or alternatively I think you can use the template mechanism built-in to the control to allow you to override the existing step templates.

Thanks,

Scott