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:
- 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.
- Create a blank website using Visual Studio or any other Visual IDE.
- 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.
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.
- 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.