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

19 Comments

  • just a little comment: try using more floats and &lt;label&gt; and w3c stuffs please

  • Hi El,

    I just updated the sample to use instead of tables.

    Thanks,

    Scott

  • 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.

  • Scott:

    Have you tried uniting the .NET 2.0 Profile provider and the new Web Application Project model within the same application? &nbsp;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

  • 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

  • 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

  • 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!

  • 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

  • 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

    ??

  • 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!

  • 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

  • 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

  • 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

  • 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 in codebehind file.

  • 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

  • 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.

  • 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

  • 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

  • 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

Comments have been disabled for this content.