Home > Uncategorized > Integrating Customized Roles, Membership

Integrating Customized Roles, Membership

December 30, 2006 Leave a comment Go to comments
There is a lot of material on ASP.NET 2.0 Membership Provider, some about Roles, and even less on Profile providers. My goal in this short piece is to provide an example of how to pull them all together. We’ll use the Membership Provider for authentication, a Role provider for Authorization, and we’ll add a custom Profile example to show how custom user Profile data can be stored on a per-user basis, even for anonymous users, and then automatically migrated to their full User profile when they "sign up" on the site.

To do this, I’ll use a previously "fleshed out" framework from this article, "Dynamic Display of Uploaded Content in Master Pages" as the basis for the "pseudo site". You can set this up with any SQL Server database you want, including either a new or existing one. Plus, I’ll include a SQL Script that will show you how to install Membership, Role and Profile required database information on a hosted site, and even an ASP.NET "SetUpSite.Aspx" page that does it programmatically. Finally, I’ll include some nice "Admin" pages, courtesy of Peter Kellner, to allow Administrators to work with users and roles online, and point you to some additional resources that have become available.

 

In short, you’ll have some basic example code and controls to do just about anything you would expect to need to perform in ASP.NET 2.0 for a site, using all three providers – Membership, Role, and Profile. So, let’s get started.

The first thing we want to do before we even open the sample solution is to enable our database for the providers. There are actually three separate ways you can do this:

1) Run ASPNET_REGSQL and follow the prompts. Of course, this requires that you have command-prompt access to the machine on which your site will be deployed.

2) Run ASP.NET_REGSQL on your own sample database locally, and EXPORT all the sql script necessary to create the tables, views and stored procedures on a remote site. Again, this assumes you have the ability to access a remote hosted site’s SQL Server online and execute a rather large SQL Script on it. A sample script is included in the "SQL" Folder in the download.

3) Do it programmatically: Make a "Setup.aspx" page that uses the System.Web.Management utility method:

Management.SqlServices.Install("server", "USERNAME", "PASSWORD", "databasename", SqlFeatures.All)

This does everything that ASPNET_REGSQL does. You’d think they would make it more obvious that you can do this, given the large number of sites that are hosted by commercial shared hosting companies, but no, they decided to push ASPNET_REGSQL as if everybody everywhere automatically has access to it. Go figure. There is a sample page "SetUpASPNetDatabase.aspx" in the sample solution where you can simply fill in the above parameters in a form, and press a button to set up your database.

So, now we have our database (either SQL Server 2000 / MSDE or SQL Server 2005 / SQLExpress) set up for Membership, Roles and Profiles. The rest is just figuring out what code and what controls to use to make everything work. Fortunately, ASP.NET 2.0 provides a group of controls that handle most of these tasks out of the box and can also be highly customized beyond their default behaviors.

The easiest way to review this process is to go through it in stages with the sample application open in Visual Studio.NET 2005. So, unzip the sample into your favorite folder, and double-click on the "ArticleMaster.sln" solution file to bring it all up. This is configured as a File-based Web Site solution, so no IIS settings are required.

The sample database for this exercise is "Articles" so go ahead and create a new database of that name now, and then run the "TablesSprocAndData.sql" Sql script in the "SQL" subfolder against it. This creates the table and stored procs that were used in the original article referenced above, so that we don’t need to "reinvent the wheel". Next, if you haven’t set up your new database for Membership, Roles and Profiles, you can either execute the stock "ASPNETREGSQLFULL.sql" script that I have prepared as in "2" above, or you can "View In Browser" on the SetupASPNETDatabase.aspx page in the solution to see how the option "3" works. Fill in Server, Username, password and database names, and press the "Set Up Database" button, and the SqlServices.Install Method will be run. Finally, make sure the connection strings in the web.config actually work for your specific environment, otherwise you won’t get very far.

At this point, all your infrastructure for the sample is set up, and we are ready to look at some code. If you look at the Default.aspx page, it has a Repeater as in the original article, to display content, and sports a LoginStatus control pointing to "~/Login.aspx" as its login/ logout page action. Now let’s move to the Login.aspx page.

You can see that Login.aspx has only a Login Control. The property settings on this control are where we can take advantage of our Membership Provider automatically. In this case, our MembershipProvider is set to "DefaultMembershipProvider", and whatever we have in our web.config for that takes over. In my codebehind for this control, I have only the following:

 protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
    {
        string userName = Login1.UserName.ToString();
        string password = Login1.Password.ToString();
        if (Membership.ValidateUser(userName, password))
        {
            if (Request.QueryString["ReturnUrl"] != null)
            {           
                FormsAuthentication.RedirectFromLoginPage(userName,true);
            }
            else
            {
                FormsAuthentication.SetAuthCookie(userName, true);
                Response.Redirect("~/Default.aspx");
            }
        }
        else
        {
         lblResults.Visible = true;
       lblResults.Text = "Unsuccessful login. Please re-enter your information and try again.";
         if ((Membership.GetUser(userName) != null) && 
(Membership.GetUser(userName).IsLockedOut == true)) lblResults.Text += " Your account has been locked out."; } }

So, I am using the Login Control to use the Membership Provider’s "ValidateUser" method, with Forms Authentication, which is already set up in the web.config.

Obviously, if you aren’t a "member" yet, you’ll want to click the "Create Account" link on the Login Control, and this will take you to the "Admin.aspx" page, where we have a CreateUserWizard, a ChangePassword, and a PasswordRecovery control. All the settings on these are highly configurable; in most cases you will not need to write any code to use them. In my CreateUserWizard Control, I am using the following codebehind:

 protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
	{
		if(!Roles.RoleExists("user")) Roles.CreateRole("user");
		string username = this.CreateUserWizard1.UserName;
		Roles.AddUserToRole(username, "user");		
	}    

If the User Role doesn’t exist yet (first time the app is ever used) we create one, and then we Add this user to the "user" role. Done! The rest of the CreateUserWizard process doesn’t require any custom code – its automatic, based on the property settings of the control. You can check for and add additional roles here if you like.

Now let’s take a look at some Profile Features. Start the application, but do not log in or create an account. Go to the Profile page, and fill in your Profile data. Then, press the Get Profile Values button to see that your Profile data has been saved as an anonymous user. You can close your browser, and come back again, and see that we are indeed holding Profile data for you, even as an anonymous user. Now, create a new account for yourself and log in. Then, go to the Profile page again, and you will see that we have migrated your anonymous data to your new "real member" Profile. This is done in the new "OnMigrateAnonymous" event which fires in Global.asax:

 public void Profile_OnMigrateAnonymous(Object sender, ProfileMigrateEventArgs args)
    {
        ProfileCommon anonymousProfile;
        anonymousProfile =Profile.GetProfile(args.AnonymousID);
        if (Profile.LastActivityDate == DateTime.MinValue)
        {
            Profile.UserDetails.Address = anonymousProfile.UserDetails.Address;
            Profile.UserDetails.City = anonymousProfile.UserDetails.City;
           
            Profile.UserDetails.State = anonymousProfile.UserDetails.State;
            Profile.UserDetails.Zip = anonymousProfile.UserDetails.Zip;
            Profile.UserDetails.Email = anonymousProfile.UserDetails.Email;
            Profile.UserDetails.Phone = anonymousProfile.UserDetails.Phone;
            Profile.UserDetails.GetNewsletter = anonymousProfile.UserDetails.GetNewsletter;
            Profile.Save();
        }        
        // delete the anonymous user data, user is no longer anonymous
        ProfileManager.DeleteProfile(anonymousProfile.UserName);
        //delete the anonymous cookie so this event no longer fires for a logged-in user:
        AnonymousIdentificationModule.ClearAnonymousIdentifier();
    }	  	  
	  

Now, let’s take a look at the web.config setup for all this, and how it works:

p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin-right:0in;margin-bottom:10.0pt;margin-left:0in;line-height:115%;font-size:11.0pt;font-family:’Calibri","sans-serif’;}
.MsoPapDefault
{margin-bottom:10.0pt;line-height:115%;}
@page Section1
{size:8.5in 11.0in;margin:1.0in 1.0in 1.0in 1.0in;}
div.Section1
{page:Section1;}

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> 
<
connectionStrings>
   <remove name="LocalSqlServer" />
  <add name="LocalSqlServer" connectionString="server=localhost;database=Articles;uid=sa;pwd="
    
providerName="System.Data.SqlClient" />
  </connectionStrings>
 <appSettings>
    <add key="articleFolder" value="Articles" />
  <add key="connectionString" value="server=localhost;database=Articles;uid=sa;pwd="/>
 </appSettings >
   <system.web>
 <anonymousIdentification enabled="true"/>
 <!– profile–>
<profile>
 <properties>
 <group name ="UserDetails">
   <add name="State"  type="System.String" allowAnonymous="true" />
   <add name="Email" type="System.String" allowAnonymous="true" />
   <add name="Address" type="System.String" allowAnonymous="true" />
   <add name="City" type="System.String" allowAnonymous="true" />
   <add name="Zip"  type="System.String" allowAnonymous="true" />
   <add name="Phone"  type="System.String" allowAnonymous="true" />
   <add name="GetNewsletter" type="System.Boolean" allowAnonymous="true" />
 </group>  
 </properties>
</profile>

<!– membership provider –>
 <roleManager enabled="true" cacheRolesInCookie="true" createPersistentCookie="true" >
    <providers>
       <add applicationName="/" connectionStringName="LocalSqlServer" name="DefaultRoleProvider" type="System.Web.Security.SqlRoleProvider" />
    </providers>
 </roleManager>
 <membership>
    <providers>

       <add connectionStringName="LocalSqlServer" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" requiresUniqueEmail="true" passwordFormat="Clear" minRequiredPasswordLength="3" minRequiredNonalphanumericCharacters="0" passwordStrengthRegularExpression="" name="DefaultMembershipProvider" type="System.Web.Security.SqlMembershipProvider" />
    </providers>
 </membership
         <compilation debug="true">       
 </compilation>
 <authentication mode="Forms">
   <forms name=".SITE1"
                 loginUrl="Login.aspx"
                 protection="All"
                 timeout="30"
                 path="/"
                 requireSSL="false"
                 slidingExpiration="true"
                 defaultUrl="Default.aspx"
enableCrossAppRedirects="true"/>
 </authentication>
 </system.web
 <location path="SamplePages">
 <system.web>
   <authorization>     
     <allow roles="Administrator" />  
     <deny users="*"/>
   </authorization>
 </system.web>
 </location>
</configuration>

In the beginning, I start with the ConnectionStrings section, removing the Default LocalSqlServer Provider (which defaults to SQLExpress) so that I can add back in my own, specifying the connection string and database.

The appSettings section is just a holdover for backwards – compatibility with the original app.

Next, I specify that anonymousIdentification is enabled. This is what lets us store Profile Data for the anonymous users, and migrate it to their permanent Profile when they join the site. Then, I have my profile section where I actually set up the profile items I want to store. If you look in the APP_CODE folder of the solution, you’ll see the CustomProfile.cs class I’ve created and how easy it is to override the ProfileBase class. In particular, notice that I’ve decorated each item with the [System.Web.Profile.SettingsAllowAnonymous(true)] attribute.

Next, I specify the Membership Provider, and it this case I am using the DefaultProvider. Next, the Role provider. Finally I have my Forms Authentication setup. The last entry is a location path block to allow only Administrators access to the SamplePages folder. Originally I was going to write up a page to manage Members and Roles, but then I found Peter Kellner’s nice workup and sample on this, so in the interest of my credo "Don’t reinvent the wheel", I’ve included those pages in the app. You can, of course, comment that section out temporarily to allow you access so you can make yourself an Administrator after you log into your own site! There is another one in the SamplePages folder, just in case.

By the way, for those Master Mechanics who are interested in getting their hands greasy, Scott Guthrie recently announced the download of the sample Provider Toolkit: "The ASP.NET Provider Toolkit provides a full source code implementation for how you can build a set of ASP.NET 2.0 providers for the new Membership, Role Managmenet, Health Monitoring and Personalization features." If you would like to see an implementation I did for the SQLite database, check out this article.

Finally, in the interest of not getting flamed unnecessarily, be advised that this is not a website — it is just a sample application that was designed solely to illustrate how to pull together the various providers and make them work together. As such, I wouldn’t recommend using the solution as the basis for a real web site, but rather, as a test bed to play around with and get used to how the various parts work.

I hope this effort at bringing together examples of how the Membership, Roles and Profile providers work, in a single article, has been useful to you.

Categories: Uncategorized
  1. No comments yet.
  1. No trackbacks yet.

Leave a comment