Click here to Skip to main content
Click here to Skip to main content

Multiple Profiles with the Membership and Profile API

By , 10 Mar 2008
Rate this:
Please Sign up or sign in to vote.

screen_shot_small.JPG

Introduction

The Membership feature of ASP.NET does just that: makes it better and easier. The Membership feature provides secure credential storage with simple, easy-to-use APIs. Rather than requiring you to repeatedly develop infrastructure features for authenticating users, it is now part of the platform. More importantly, it's a pluggable part of the platform through the new provider pattern, allowing you to easily extend the system (e.g., to add support for LDAP or existing corporate user account systems).

A profile API can store arbitrary properties about both authenticated and anonymous users visiting a website (for example, their ZIP code, gender, theme preferences, and so on). So, in this article, I will explain how to implement multiple profiles of a user using the Profile API. Any user can maintain different types of profiles and properties under those profile types in a structured way by using this procedure or system. There are also options for retrieving these profile info in different ways. This will help in effective searching.

Important note: Before you run the “demo project”, please follow the step of step solution to setup the database schema of the membership and profile API and change the initial catalog (“database name”) of the connection string in the web.config file.

Background

I was working on a project that was responsible for central authentication and profile keeping. There were some requirements that a user can keep as many properties as he/she wants and keep the properties in a structured way that a user can claim any of his/her profile property at any time. At that time, I did some R&D and got the best solution with the Membership and Profile API.

Using the Code

Here is the step by step solution to create and maintain multiple profiles of a user using the Profile API:

  1. Configure ASP.NET 2.0 Application Services to use SQL Server 2000 or SQL Server 2005 which will help us to setup the new ASP.NET Membership, Role Management, Profile, and Personalization services. Please follow the URL here to configure ASP.NET 2.0 application services. ScottGu has provided a very good article for this.
  2. Create a blank website using Visual Studio or any other Visual IDE.
  3. Now, you need to add/change some configuration settings in the web.config file under the <system.web> </system.web> tag of a newly open application. This configuration is required for the Membership and Profile API.
  4. Configure Membership

    <membership defaultProvider="MembershipTestSqlProvider" userIsOnlineTimeWindow="30">
        <providers>
            <add name="MembershipTestSqlProvider"
                 type="System.Web.Security.SqlMembershipProvider, System.Web,
                 Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                 connectionStringName="MembershipTestConnectionString" 
                 enablePasswordRetrieval="false" enablePasswordReset="true"
                 requiresQuestionAndAnswer="false" applicationName="/"
                 requiresUniqueEmail="false" passwordFormat="Hashed" 
                 minRequiredPasswordLength="6"
                 passwordStrengthRegularExpression="" 
                 minRequiredNonalphanumericCharacters="0"/>
        </providers>
    </membership>

    In the above block, you need to add a provider for enabling the Membership API in your application. I just added a default provider in my example, "System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", but if you want, you can add your own or a modified provider. Another compulsory parameter is connectionStringName. Just provide your database connection string name, which is available in your web.config file. Other parameters are optional (use the according to your requirements).

    Configure Profile

    <profile defaultProvider="ProfileTestSqlProvider" enabled="true">
        <providers>
            <add name="ProfileTestSqlProvider"
                 type="System.Web.Profile.SqlProfileProvider,System.Web,
                 Version=2.0.3600.0, Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a" 
                 connectionStringName="MembershipTestConnectionString" applicationName="/"
                 description="Stores and retrieves personalization data from 
                 the local Microsoft SQL Server database"/>
        </providers>
            
        <properties>
            <group name="UsersPersonalInfo">
                <add name="properties_block" type=
                "System.Collections.Generic.Dictionary`2[System.string,System.object]" 
                serializeAs="Binary"/>
            </group>
            <group name="UsersOfficialInfo">
                <add name="properties_block" type=
                    "System.Collections.Generic.Dictionary`2[System.string,System.object]" 
                    serializeAs="Binary"/>
            </group>
        </properties>
    </profile>

    Similarly, in the case of the Profile API, you need to add a provider with the connectionStringName and applicationName parameters. Additionally, you need to add another tag called <properties> </properties>. This tag contains groups, and under each group, add a dictionary to keep the properties of a profile. You can define as many groups as you want. These groups represent different types of profiles. There are two groups in my example which represent two profile types, UsersPersonalInfo and UsersOfficialInfo. This means, each user can keep, at best, two types of profiles in this example. But, under each group or profile type, you can define as many properties as you want of a user, in the Dictionary as a ([key],[value]) pair.

  5. Create a class file called ProfileManagementHelper.cs and add some methods to the file. The methods are given below:

Methods for Setting a Profile

public string SetProfile(string groupName,string userName,
       Dictionary<string,object> userProfile)
{
    if(Membership.GetUser(userName) == null)
                Membership.CreateUser(userName,"1234567");
        try
        {
           ProfileCommon pc = new ProfileCommon();
           ProfileCommon existing_pc = pc.GetProfile(userName);
           existing_pc.GetProfileGroup(groupName).SetPropertyValue(
               "properties_block",userProfile);
                
           existing_pc.Save();
                
        }
        catch(Exception e)
        {
           return "error!!" + e;
        }
       
    return "success";
}

The above method is responsible for setting the profile for a particular user and group/profile type. There is another parameter of type Dictionary; this parameter contains the profile properties as ([key],[value]) pairs. You must fill up the dictionary with the properties in the application layer. This method first checks that the given user already exists or not. If not, create the user first by using Membership.CreateUser(string UserName,string password). Here, I provide a dummy password for all users. Because, the password is not that much important for this particular example. After that, create an instance of the ProfileCommon class (using the given UserName), retrieve the profile type for that user, and set the properties by calling SetPropertyValue(string propertyName,object propertyValue). At last, call the Save() method for finally saving to the database.

Methods for Getting a Profile

public Dictionary<string,object> GetProfilesByUser(string userName)
{
    Dictionary<string, object> profileListDictionary = 
        new Dictionary<string, object>();
        
    ProfileCommon pc = new ProfileCommon();
    ProfileCommon existing_pc = pc.GetProfile(userName);

    string []groupNameArray = Enum.GetNames(typeof(ProfileTypeEnum));

    for(int i=0;i<groupNameArray.Length;i++)
    {
        Dictionary<string,object> profileDictionary =
            (Dictionary<string,object>)existing_pc.GetProfileGroup(groupNameArray[i]
            ).GetPropertyValue("properties_block");
            
        if(profileDictionary.Keys.Count > 0)
                profileListDictionary.Add(groupNameArray[i],profileDictionary);
    }

    return profileListDictionary;
}

This method is responsible for retrieving all profiles from the database for a given user and binding them to a dictionary for return. The return dictionary contains GroupName/ProfileTypeName as the key and a dictionary full of properties for the corresponding GroupName/ProfileTypeName as the value.

public Dictionary<string,object> GetProfilesByUser(string userName,
                                 ArrayList propertyNameList)
{
    Dictionary<string, object> profileListDictionary = 
                          new Dictionary<string, object>();
            
    ProfileCommon pc = new ProfileCommon();
    ProfileCommon existing_pc = pc.GetProfile(userName);
            
    string []groupNameArray = Enum.GetNames(typeof(ProfileTypeEnum));
   
    for(int i=0;i<groupNameArray.Length;i++)
    {
        Dictionary<string,object> profileDictionary = 
            (Dictionary<string,object>)existing_pc.GetProfileGroup(
            groupNameArray[i]).GetPropertyValue("properties_block");
                
        if(profileDictionary.Keys.Count > 0)
        {
              propertyDepth = "";
              Dictionary<string, object>       selectedPropertiesDictionary = 
                  new  Dictionary<string,object>();
                    
              selectedPropertiesDictionary = FindProperties(profileDictionary,
                  selectedPropertiesDictionary,propertyNameList);
             
              if(selectedPropertiesDictionary.Keys.Count > 0)
                      profileListDictionary.Add(groupNameArray[i],
                      selectedPropertiesDictionary);
         }
     }
            
     return profileListDictionary;
}

This is an overload of the previous method. It contains an extra parameter called propertyNameList of type ArrayList. This method is responsible for retrieving only those properties of a given user which are present in propertyNameList.

public Dictionary<string,object> GetProfilesByGroup(string groupName)
{
    Dictionary<string, object> profileListDictionary = 
        new Dictionary<string, object>();
            
    ProfileCommon pc = new ProfileCommon();
           
    MembershipUserCollection userList = Membership.GetAllUsers();
        
    foreach(MembershipUser user in userList)
    {
        ProfileCommon existing_pc = pc.GetProfile(user.UserName);

        Dictionary<string,object> profileDictionary = 
            (Dictionary<string,object>)existing_pc.GetProfileGroup(groupName
            ).GetPropertyValue("properties_block");
                
        if(profileDictionary.Keys.Count > 0)
            profileListDictionary.Add(user.UserName,profileDictionary);
            
    }
            
    return profileListDictionary;
}

The above method helps to retrieve that particular profile of all users which is provided as the groupName in this method’s parameter. The return dictionary contains the UserName as the key and a dictionary full of properties for the corresponding User as the value.

public Dictionary<string,object> GetProfilesByGroup(string groupName, 
                  ArrayList propertyNameList)
{
    Dictionary<string, object> profileListDictionary = new Dictionary<string, object>();
            
    ProfileCommon pc = new ProfileCommon();
           
    MembershipUserCollection userList = Membership.GetAllUsers();
            
    foreach(MembershipUser user in userList)
    {
        ProfileCommon existing_pc = pc.GetProfile(user.UserName);

        Dictionary<string,object> profileDictionary = 
            (Dictionary<string,object>)existing_pc.GetProfileGroup(
            groupName).GetPropertyValue("properties_block");
                
                if(profileDictionary.Keys.Count > 0)
                {
                propertyDepth = "";

            Dictionary<string, object>   selectedPropertiesDictionary = 
                new Dictionary<string,object>();

            selectedPropertiesDictionary = FindProperties(profileDictionary,
                selectedPropertiesDictionary,propertyNameList);
             
                      if(selectedPropertiesDictionary.Keys.Count > 0)
                            profileListDictionary.Add(user.UserName,
                            selectedPropertiesDictionary);
                }
            
     }
            
     return profileListDictionary;
}

This is an overload of the previous method. It contains an extra parameter called propertyNameList of type ArrayList. This method is responsible for retrieving only those properties of a given groupName of all users which are present in propertyNameList.

public Dictionary<string,object> GetProfileByUserGroup(string userName,string groupName)
{
    ProfileCommon pc = new ProfileCommon();
        ProfileCommon existing_pc = pc.GetProfile(userName);
              
    Return (Dictionary<string,object>)existing_pc.GetProfileGroup(
        groupName).GetPropertyValue("properties_block");
}

This method retrieves all the properties of given userName and groupName/profileType. The return dictionary contains the property name as the key and the property value as the value.

public Dictionary<string,object> GetProfileByUserGroup(string  userName,
       string groupName,ArrayList propertyNameList)
{
    Dictionary<string, object> selectedPropertiesDictionary = 
        new Dictionary<string,object>();
            
    ProfileCommon pc = new ProfileCommon();
        ProfileCommon existing_pc = pc.GetProfile(userName);
            
    Dictionary<string,object> profileDictionary = 
        (Dictionary<string,object>)existing_pc.GetProfileGroup(groupName
        ).GetPropertyValue("properties_block");
                
    if(profileDictionary.Keys.Count > 0)
    {
        propertyDepth = "";
        selectedPropertiesDictionary =     
            FindProperties(profileDictionary,
            selectedPropertiesDictionary,propertyNameList);
    }
            
    return selectedPropertiesDictionary;
}

Similarly, the above method is an overload of the previous method. It returns a dictionary with only those properties of a particular user and group/profile type which are present in propertyNameList.

public Dictionary<string,object> GetTotalProfileCollection()
{
    Dictionary<string, object> profileListDictionary = new Dictionary<string, object>();
            
    MembershipUserCollection userList = Membership.GetAllUsers();
            
    foreach(MembershipUser user in userList)
    {
        ProfileCommon pc = new ProfileCommon();

        ProfileCommon existing_pc = pc.GetProfile(user.UserName);                
                
        string []groupNameArray = Enum.GetNames(typeof(ProfileTypeEnum));
       
        for(int i=0;i<groupNameArray.Length;i++)
        {
            Dictionary<string,object> profileDictionary = 
                (Dictionary<string,object>)existing_pc.GetProfileGroup(
                groupNameArray[i]).GetPropertyValue("properties_block");
                    
            if(profileDictionary.Keys.Count > 0)
                profileListDictionary.Add(user.UserName+"|"+groupNameArray[i],
                    profileDictionary);
        }
                
    }
            
    return profileListDictionary;
}

This method is responsible for retrieving all the profiles of all users and groups/profile types. The return dictionary contains user_name|merchant_Group_Name as the key and a dictionary with all the properties as the value.

public Dictionary<string,object> GetTotalProfileCollection(ArrayList propertyNameList)
{
    Dictionary<string, object> profileListDictionary = new Dictionary<string, object>();
            
    MembershipUserCollection userList = Membership.GetAllUsers();
            
    foreach(MembershipUser user in userList)
    {
        ProfileCommon pc = new ProfileCommon();
        ProfileCommon existing_pc = pc.GetProfile(user.UserName);
                
        string []groupNameArray = Enum.GetNames(typeof(ProfileTypeEnum));
       
        for(int i=0;i<groupNameArray.Length;i++)
        {
            Dictionary<string,object> profileDictionary = 
                (Dictionary<string,object>)existing_pc.GetProfileGroup(
                groupNameArray[i]).GetPropertyValue("properties_block");
                    
            if(profileDictionary.Keys.Count > 0)
            {
                propertyDepth = "";

                Dictionary<string, object> selectedPropertiesDictionary = 
                    new Dictionary<string,object>();
                        
                selectedPropertiesDictionary = FindProperties(
                    profileDictionary,selectedPropertiesDictionary,propertyNameList);
                 
                if(selectedPropertiesDictionary.Keys.Count > 0)
                        profileListDictionary.Add(
                        user.UserName+"|"+groupNameArray[i],
                        selectedPropertiesDictionary);
            }
        }
    }
            
    return profileListDictionary;
}

The above method is an overload of the previous method. It retrieves and returns a dictionary with only those properties which are present in the propertyNameList parameter.

public Dictionary<string,object> FindProperties(
       Dictionary<string,object> profileDictionary,Dictionary<string, 
       object> selectedPropertiesDictionary,ArrayList propertyNameList)
{
    foreach(object key in profileDictionary.Keys)
    {
        object value = profileDictionary[key.ToString()];
                
        if(value.GetType().Equals(typeof(
            System.Collections.Generic.Dictionary<string,object>)))
        {
            propertyDepth += key.ToString()+"|";

            FindProperties((Dictionary<string, object>)value,
                selectedPropertiesDictionary,propertyNameList);

            propertyDepth = propertyDepth.Substring(0,propertyDepth.LastIndexOf(
                key.ToString()+"|"));
        }
        else
        {
            if(propertyNameList.Contains(key.ToString()))
                   selectedPropertiesDictionary.Add(propertyDepth + 
                   key.ToString(),profileDictionary[key.ToString()]);
        }
     }
            
     return selectedPropertiesDictionary;
}

The above method finds the mentioned properties from any depth of the dictionary in a recursive way and adds to a new result dictionary. After completing the procedure, return the resultant dictionary.

Methods to Remove/Delete Profile

public bool RemoveProfilesByUser(string userName)
{
    Return ProfileManager.DeleteProfile(userName);
}

Responsible to completely remove/delete a specific user's all types of profiles from the database.

public bool RemoveProfileByUserGroup(string userName,string groupName)
{
    ProfileCommon pc = new ProfileCommon();
        ProfileCommon existing_pc = pc.GetProfile(userName);
        
    try{
        Dictionary<string,object> profile = 
            (Dictionary<string,object>)existing_pc.GetProfileGroup(
            groupName).GetPropertyValue("properties_block");  
        profile.Clear();
        if(!SetProfile(groupName,userName,profile).Equals("success"))
             return false;
    }
    catch(Exception e)
    {
        return false;
    }
            
    return true;
}

The above method is responsible to remove all the properties and values from a particular user’s profile type, but an empty instance exists. This means an empty profile exists in the database for that particular user.

public bool RemoveProfilePropertiesByUserGroup(string userName,
       string groupName,ArrayList propertyNameList)
{
    Dictionary<string, object> existPropertiesDictionary = 
        new Dictionary<string,object>();
            
    ProfileCommon pc = new ProfileCommon();
        ProfileCommon existing_pc = pc.GetProfile(userName);
            
    Dictionary<string,object> profileDictionary = 
        (Dictionary<string,object>)existing_pc.GetProfileGroup(
        groupName).GetPropertyValue("properties_block");
                
    if(profileDictionary.Keys.Count > 0)
    {
        propertyDepth = "";
        existPropertiesDictionary =DeleteProperties(profileDictionary,propertyNameList;
    }
            
    if(!SetProfile(groupName,userName,existPropertiesDictionary).Equals("success"))
        return false;

    return true;
}

The above method removes only the given properties from a particular user’s profile type.

public Dictionary<string,object> DeleteProperties(
       Dictionary<string,object> profileDictionary,ArrayList propertyNameList)
{
    string []keyCollection = new string[profileDictionary.Keys.Count];
    int j=0;
            
    foreach(object key in profileDictionary.Keys)
              keyCollection[j++] = key.ToString();

    for(int i=0;i<keyCollection.Length;i++)
    {
        string key = keyCollection[i];
        object value = profileDictionary[key];

        if(propertyNameList.Contains(key))
             profileDictionary.Remove(key);
        else if(value.GetType().Equals(typeof(
            System.Collections.Generic.Dictionary<string,object>)))
        {
             propertyDepth += key+"|";

             DeleteProperties((Dictionary<string, object>)value,propertyNameList);

             propertyDepth = propertyDepth.Substring(
                 0,propertyDepth.LastIndexOf(key.ToString()+"|"));
        }
     }
            
     return profileDictionary;
}

This method helps to find the given properties of propertyNameList from any depth of the dictionary in a recursive way and deletes those properties.

Points of Interest

After you set a profile into the database for a particular user and group, if you want, you can manually observe them in the aspnet_Profile table to see that the profile is really inserted or not. In this scenario, you will see the row is inserted successfully with all the field values except the PropertyValuesString field. Don’t worry, this field actually contains the value, but for some reasons, the SQL Server graphical interface is unable to show that string.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Razwan Kader
Software Developer (Senior) somewhere in...
Bangladesh Bangladesh
Working in a Nordic company, developing some really interesting solutions for GSM Mobile Service Providers in Bangladesh. I work as a senior software developer there. Developed framework of challenging web based application. Build and manage a complete dynamic accounting and inventory management system which is tag based software. Enjoy learning about latest technology. Main working arenas are asp.net 2.0/3.5.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberJamie Llewellyn13-Jun-12 2:17 
Questionthread safety and concurrency Pinmembernhm tanveer hossain khan (hasan)25-Mar-08 23:19 
GeneralRe: thread safety and concurrency PinmemberRazwan Kader28-Mar-08 2:26 
Hi hasan,
 
The question that you asked is quite irrelevant with this article. Because, this article specially cover profile API and how to implement multiple profile with the help of profile API instead of concurrency control or thread safe programming. And I tried to provide the sample example in such a way that is only focus on the functionalities of multiple profiles instead of thread safe programming.
 
Not a big problem if you really interested about the thread safe programming in .NET, please go through the below link and apply wherever need at your application.
 
http://msdn2.microsoft.com/en-us/library/aa479030.aspx
 
http://www.yoda.arachsys.com/csharp/singleton.html
 
Important note: In general, ASP.NET goes to great lengths to prevent developers from having to write thread-safe code. HTTP handlers and HTTP modules, for example, are instanced on a per-request (per-thread) basis, so they don't have to be thread-safe unless they access shared state.
 
Hope so, you have already got your question’s answer. Enjoy codeproject’s articles. Smile | :)
 
Razwan Kader

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140421.2 | Last Updated 10 Mar 2008
Article Copyright 2008 by Razwan Kader
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid