Evjen c16.tex V2 - 01/28/2008 2:51pm Page 767 Chapter 16: Membership and Role Management Listing 16-8: Using personalization properties with the CreateUserWizard control VB < %@ Page Language="VB" % > < script runat="server" > Protected Sub CreateUserWizard1_CreatedUser(ByVal sender As Object, _ ByVal e As System.EventArgs) Dim pc As ProfileCommon = New ProfileCommon() pc.Initialize(CreateUserWizard1.UserName.ToString(), True) pc.FirstName = Firstname.Text pc.LastName = Lastname.Text pc.Age = Age.Text pc.Save() End Sub < /script > < html xmlns="http://www.w3.org/1999/xhtml" > < head id="Head1" runat="server" > < title > Creating Users with Personalization < /title > < /head > < body > < form id="form1" runat="server" > < asp:CreateUserWizard ID="CreateUserWizard1" Runat="server" BorderWidth="1px" BorderColor="#FFDFAD" BorderStyle="Solid" BackColor="#FFFBD6" Font-Names="Verdana" LoginCreatedUser="true" OnCreatedUser="CreateUserWizard1_CreatedUser" > < WizardSteps > < asp:WizardStep ID="WizardStep1" Runat="server" Title="Additional Information" StepType="Start" > < table width="100%" >< tr >< td > Firstname: < /td >< td > < asp:TextBox ID="Firstname" Runat="server" >< /asp:TextBox > < /td >< /tr >< tr >< td > Lastname: < /td >< td > < asp:TextBox ID="Lastname" Runat="server" >< /asp:TextBox > < /td >< /tr >< tr >< td > Age: < /td >< td > < asp:TextBox ID="Age" Runat="server" >< /asp:TextBox > < /td >< /tr >< /table > < /asp:WizardStep > < asp:CreateUserWizardStep Runat="server" Title="Sign Up for Your New Account" > < /asp:CreateUserWizardStep > < asp:CompleteWizardStep Runat="server" Title="Complete" > < /asp:CompleteWizardStep > < /WizardSteps > < StepStyle BorderColor="#FFDFAD" Font-Names="Verdana" BackColor="#FFFBD6" BorderStyle="Solid" BorderWidth="1px" >< /StepStyle > < TitleTextStyle Font-Bold="True" BackColor="#990000" Continued 767 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 768 Chapter 16: Membership and Role Management ForeColor="White" >< /TitleTextStyle > < /asp:CreateUserWizard > < /form > < /body > < /html > C# < %@ Page Language="C#" % > < script runat="server" > protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e) { ProfileCommon pc = new ProfileCommon(); pc.Initialize(CreateUserWizard1.UserName.ToString(), true); pc.FirstName = Firstname.Text; pc.LastName = Lastname.Text; pc.Age = Age.Text; pc.Save(); } < /script > With this change to the standard registration process as is defined by a default instance of the CreateUser- Wizard control, your registration system now includes the request for properties stored and retrieved using the ProfileCommon object. Then, using the ProfileCommon.Initialize() method, you initialize the property values for the current user. Next, you set the property values using the strongly typed access to the profile properties available via the ProfileCommon object. After all the values have been set, you use the Save() method to finalize the process. You can define a custom step within the CreateUserWizard control by using the < WizardSteps >element. Within this element, you can construct a series of registration steps in whatever fashion you choose. From the < WizardSteps >section, shown in Listing 16-8, you can see that three steps are defined. The first is the custom step in which the end user’s personalization properties are requested with the < asp: WizardStep > control. Within the < asp:WizardStep >control, a table is laid out and a custom form is created. Two additional steps are defined within Listing 16-7: a step to create the user (using the < asp:Create- UserWizardStep > control) and a step to confirm the creation of a new user (using the < asp:Complete- WizardStep > control). The order in which these steps appear is the order in which they are presented to the end user. After the steps are created the way you want, you can then store the custom properties using the CreateUserWizard control’s CreatedUser() event: Protected Sub CreateUserWizard1_CreatedUser(ByVal sender As Object, _ ByVal e As System.EventArgs) Dim pc As ProfileCommon = New ProfileCommon() pc.Initialize(CreateUserWizard1.UserName.ToString(), True) pc.FirstName = Firstname.Text pc.LastName = Lastname.Text 768 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 769 Chapter 16: Membership and Role Management pc.Age = Age.Text pc.Save() End Sub You are not limited to having a separate step in which you ask for personal bits of information; you can incorporate these items directly into the < asp:CreateUserWizardStep > step itself. An easy way to do this is to switch to the Design view of your page and pull up the smart tag for the CreateUserWizard control. Then click the Customize Create User Step link (shown in Figure 16-5). Figure 16-5 Clicking on the Customize Create User Step details the contents of this particular step within a new < ContentTemplate > section that is now contained within the < asp:CreateUserWizardStep > control. Within the < ContentTemplate > element, you can now see the complete default form used for creating a new user. At this point, you are free to change the form by adding your own sections that request the end user’s personal information. From this detailed form, you can also remove items. For instance, if you are not interested in asking for the security question and answer, you can remove these two items from the form (remember that you must disable the question-and-answer requirement in the member- ship provider definition). By changing this default form, you can completely customize the registration process for your end users (see Figure 16-6). 769 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 770 Chapter 16: Membership and Role Management Figure 16-6 Adding Users Programmatically You are not limited to using only server controls to register or add new users to the membership service. ASP.NET 3.5 provides a Membership API for performing this task programmatically. This is ideal to create your own mechanics for adding users to the service — or if you are modifying a Web application that was created using ASP.NET 1.0/1.1. The Membership API includes the CreateUser() method for adding users to the service. The CreateUser() method includes four possible signatures: Membership.CreateUser(username As String, password As String) Membership.CreateUser(username As String, password As String, email As String) Membership.CreateUser(username As String, password As String, email As String, passwordQuestion As String, passwordAnswer As String, isApproved As Boolean, ByRef status As System.Web.Security.MembershipCreateStatus) Membership.CreateUser(username As String, password As String, email As String, passwordQuestion As String, passwordAnswer As String, isApproved As Boolean, providerUserKey As Object ByRef status As System.Web.Security.MembershipCreateStatus) You can use this method to create users. The nice thing about this method is that you are not required to create an instance of the Membership class; you use it directly. A simple use of the CreateUser() method is illustrated in Listing 16-9. 770 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 771 Chapter 16: Membership and Role Management Listing 16-9: Creating users programmatically VB < %@ Page Language="VB" % > < script runat="server" > Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Try Membership.CreateUser(TextBox1.Text, TextBox2.Text) Label1.Text = "Successfully created user " & TextBox1.Text Catch ex As MembershipCreateUserException Label1.Text = "Error: " & ex.ToString() End Try End Sub < /script > < html xmlns="http://www.w3.org/1999/xhtml" > < head runat="server" > < title > Creating a User < /title > < /head > < body > < form id="form1" runat="server" > < h1 > Create User < /h1 > < p > Username < br / > < asp:TextBox ID="TextBox1" Runat="server" >< /asp:TextBox > < /p > < p > Password < br / > < asp:TextBox ID="TextBox2" Runat="server" TextMode="Password" >< /asp:TextBox > < /p > < p > < asp:Button ID="Button1" Runat="server" Text="Create User" OnClick="Button1_Click" / > < /p > < p > < asp:Label ID="Label1" Runat="server" >< /asp:Label > < /p > < /form > < /body > < /html > C# < %@ Page Language="C#" % > < script runat="server" > protected void Button1_Click(object sender, EventArgs e) { try { Membership.CreateUser(TextBox1.Text.ToString(), TextBox2.Text.ToString()); Label1.Text = "Successfully created user " + TextBox1.Text; } Continued 771 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 772 Chapter 16: Membership and Role Management catch (MembershipCreateUserException ex) { Label1.Text = "Error: " + ex.ToString(); } } < /script > So, use either the CreateUserWizard control or the CreateUser() method found in the Membership API to create users for your Web applications with relative ease. This functionality was possible in the past with ASP.NET 1.0/1.1, but it was a labor-intensive task. With ASP.NET 2.0 or 3.5, you can create users either with a single control or with a single line of code. From this bit of code, you can see that if a problem occurs when creating the user with the CreateUser() method, a MembershipCreateUserException is thrown. In this example, the exception is written to the screen within a Label server control. An example of an exception written to the screen is presented here: Error: System.Web.Security.MembershipCreateUserException: The password-answer supplied is invalid. at System.Web.Security.Membership.CreateUser(String username, String password, String email) at System.Web.Security.Membership.CreateUser(String username, String password) at ASP.default_aspx.Button1_Click(Object sender, EventArgs e) in c: \ Documents and Settings \ BillEvjen \ My Documents \ Visual Studio 2008 \ WebSites \ Membership \ Default.aspx:line 10 You might not want such details sent to the end user. You might prefer to return a simpler message to the end user with something like the following construct: Label1.Text = "Error: " & ex.Message.ToString(); This gives you results as simple as the following: Error: The password-answer supplied is invalid. You can also capture the specific error using the MembershipCreateUserException and returning some- thing that might be a little more appropriate. An example of this is presented in Listing 16-10. Listing 16-10: Capturing the specific MembershipCreateUserException value VB < %@ Page Language="VB" % > < script runat="server" > Protected Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Try Membership.CreateUser(TextBox1.Text, TextBox2.Text) Label1.Text = "Successfully created user " & TextBox1.Text Catch ex As MembershipCreateUserException Select Case ex.StatusCode Case MembershipCreateStatus.DuplicateEmail Label1.Text = "You have supplied a duplicate email address." Case MembershipCreateStatus.DuplicateUserName Label1.Text = "You have supplied a duplicate username." Continued 772 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 773 Chapter 16: Membership and Role Management Case MembershipCreateStatus.InvalidEmail Label1.Text = "You have not supplied a proper email address." Case Else Label1.Text = "ERROR: " & ex.Message.ToString() End Select End Try End Sub < /script > < html xmlns="http://www.w3.org/1999/xhtml" > < head id="Head1" runat="server" > < title > Creating a User < /title > < /head > < body > < form id="form1" runat="server" > < h1 > Create User < /h1 > < p > Username < br / > < asp:TextBox ID="TextBox1" Runat="server" >< /asp:TextBox > < /p > < p > Password < br / > < asp:TextBox ID="TextBox2" Runat="server" TextMode="Password" >< /asp:TextBox > < /p > < p > < asp:Button ID="Button1" Runat="server" Text="Create User" OnClick="Button1_Click" / > < /p > < p > < asp:Label ID="Label1" Runat="server" >< /asp:Label > < /p > < /form > < /body > < /html > C# < %@ Page Language="C#" % > < script runat="server" > protected void Button1_Click(object sender, EventArgs e) { try { Membership.CreateUser(TextBox1.Text, TextBox2.Text); Label1.Text = "Successfully created user " + TextBox1.Text; } catch (MembershipCreateUserException ex) { switch(ex.StatusCode) { case MembershipCreateStatus.DuplicateEmail: Label1.Text = "You have supplied a duplicate email address."; break; case MembershipCreateStatus.DuplicateUserName: Label1.Text = "You have supplied a duplicate username."; Continued 773 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 774 Chapter 16: Membership and Role Management break; case MembershipCreateStatus.InvalidEmail: Label1.Text = "You have not supplied a proper email address."; break; default: Label1.Text = "ERROR: " + ex.Message.ToString(); break; } } } < /script > In this case, you are able to look for the specific error that occurred in the CreateUser process. Here, this code is looking for only three specific items, but the list of available error codes includes the following: ❑ MembershipCreateStatus.DuplicateEmail ❑ MembershipCreateStatus.DuplicateProviderUserKey ❑ MembershipCreateStatus.DuplicateUserName ❑ MembershipCreateStatus.InvalidAnswer ❑ MembershipCreateStatus.InvalidEmail ❑ MembershipCreateStatus.InvalidPassword ❑ MembershipCreateStatus.InvalidProviderUserKey ❑ MembershipCreateStatus.InvalidQuestion ❑ MembershipCreateStatus.InvalidUserName ❑ MembershipCreateStatus.ProviderError ❑ MembershipCreateStatus.Success ❑ MembershipCreateStatus.UserRejected In addition to giving better error reports to your users by defining what is going on, you can use these events to take any actions that might be required. Changing How Users Register with Your Application You determine how users register with your applications and what is required of them by the mem- bership provider you choose. You will find a default membership provider and its applied settings are established within the machine.config file. If you dig down in the machine.config file on your server, you find the code shown in Listing 16-11. Listing 16-11: Membership provider settings in the machine.config file < membership > < providers > < add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="LocalSqlServer" 774 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 775 Chapter 16: Membership and Role Management enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" / > < /providers > < /membership > This section of the machine.config file shows the default membership provider that comes with ASP.NET 3.5 — named AspNetSqlProvider . If you are adding membership providers for server-wide use, add them to this < membership > section of the machine.config file; if you intend to use them for only a specific application instance, you can add them to your application’s web.config file. The important attributes of the SqlMembershipProvider definition include the enable- PasswordRetrieval , enablePasswordReset , requiresQuestionAndAnswer , requiresUniqueEmail ,and PasswordFormat attributes. The following table defines these attributes. Attribute Description enablePasswordRetrieval Defines whether the provider supports password retrievals. This attribute takes a Boolean value. The default value is False .Whenitissetto False , passwords cannot be retrieved although they can be changed with a new random password. enablePasswordReset Defines whether the provider supports password resets. This attribute takes a Boolean value. The default value is True . requiresQuestionAndAnswer Specifies whether the provider should require a question-and-answer combination when a user is created. This attribute takes a Boolean value, and the default value is False . requiresUniqueEmail Defines whether the provider should require a unique e-mail to be specified when the user is created. This attribute takes a Boolean value, and the default value is False .Whensetto True , only unique e-mail addresses can be entered into the data store. passwordFormat Defines the format in which the password is stored in the data store. The possible values include Hashed , Clear ,and Encrypted . The default value is Hashed . Hashed passwords use SHA1, whereas encrypted passwords use Triple-DES encryption. In addition to having these items defined in the machine.config file, you can also redefine them again (thus overriding the settings in the machine.config )inthe web.config file. 775 Evjen c16.tex V2 - 01/28/2008 2:51pm Page 776 Chapter 16: Membership and Role Management Asking for Credentials After you have users that can access your Web application using the membership service provided by ASP.NET, you can then give these users the means to log in to the site. This requires little work on your part. Before you learn about the controls that enable users to access your applications, you should make a few more modifications to the web.config file. Turning Off Access with the < authorization > Element After you make the changes to the web.config file by adding the < authorization > and < forms > elements (Listings 16-1 and 16-2), your Web application is accessible to each and every user that browses to any page your application contains. To prevent open access, you have to deny unauthenticated users access to the pages of your site. Denying unauthenticated users access to your site is illustrated in Listing 16-12. Listing 16-12: Denying unauthenticated users < ?xml version="1.0" encoding="utf-8"? > < configuration > < system.web > < authentication mode="Forms" / > < authorization > < deny users="?" / > < /authorization > < /system.web > < /configuration > Using the < authorization > and < deny > elements, you can deny specific users access to your Web application — or (as in this case) simply deny every unauthenticated user (this is what the question mark signifies). Now that everyone but authenticated users has been denied access to the site, you want to make it easy for viewers of your application to become authenticated users. To do so, use the Login server control. Using the Login Server Control The Login server control enables you to turn unauthenticated users into authenticated users by allowing them to provide login credentials that can be verified in a data store of some kind. In the examples so far, you have used Microsoft SQL Server Express Edition as the data store, but you can just as easily use the full-blown version of Microsoft’s SQL Server (such as Microsoft’s SQL Server 7.0, 2000, 2005, or 2008). The first step in using the Login control is to create a new Web page titled Login.aspx . This is the default page to which unauthenticated users are redirected to obtain their credentials. Remember that you can change this behavior by changing the value of the < forms > element’s loginUrl attribute in the web.config file. The Login.aspx page simply needs an < asp:Login > control to give the end user everything he needs to become authenticated, as illustrated in Listing 16-13. 776 . functionality was possible in the past with ASP. NET 1.0/1.1, but it was a labor-intensive task. With ASP. NET 2.0 or 3. 5, you can create users either with a single control or with a single line of code. From. are established within the machine.config file. If you dig down in the machine.config file on your server, you find the code shown in Listing 16-11. Listing 16-11: Membership provider settings in the machine.config. items defined in the machine.config file, you can also redefine them again (thus overriding the settings in the machine.config )inthe web.config file. 7 75 Evjen c16.tex V2 - 01/28/2008 2 :51 pm Page 776 Chapter