OBSOLETE CONTENT
The author of
this post has determined that this content is obsolete. Use at your own
risk! Blog posts are a point-in-time snapshot of the blogger's thinking
and should not be assumed to represent this blogger's current opinions.
This post was left up for historical purposes.
Like most of you doing serious ASP.NET development, I've based my security and authentication model around the System.Security.GenericPrincipal object. Pretty simple stuff, on authentication of any web request, I restore a cookie-based principal, and authenticate or require login. This is all standard stuff.
Well, if you are working on a complex web application you may have several different types of users. Examples may be customers, partners, employees, etc. Often times, one or all of these different types of users will have user-type specific information that may be needed throughout the entire application. For an example of this, lets say that partners have a "Partner Code" and employees have an employee #. Now, if you're an employee you obviously don't have a “Partner Code” and vice-versa. Often times, this information is expensive to fetch, so you will want to store this in a cache somewhere. An obvious, brute force method of doing this would be to expand your GenericPrincipal object with a bunch of typed properties to store all the different possible information a user may have. The big problem with this method is that you end up with a big bloated Principal object. Another solution would be to sub-class for each different types. This may sound good, but without multiple inheritance it would be difficult to have a customer that was also a partner.
So this is the general problem that I was faced with here at the work. We have a bunch of different types of users, and they each have different, sometimes shared properties. These properties are often expensive to retrieve and involve calls to legacy data sources. I came up with a design pattern and model that works really well. It's all based on an interface I named IExpandedUserInfoProvider. Here's the interface:
public interface IExpandedUserInfoProvider
{
object GetExpandedInfo(GenericPrincipal user);
ExpandedUserInfoType ExpandedType {get;}
}
This is all pulled together through a class that lazy-loads itself from session. I have a reference to this class in my PageBase, so that it is easy to get to. I called this class ExpandedUserInfo It takes a reference to the session in it's constructor so that you can have this class live in your base web dll.
public class ExpandedUserInfo
{
public const string SESSION_KEY = "EXPANDED_USER_INFO_HASHTABLE";
private HttpSessionState m_session;
public ExpandedUserInfo(
IExpandedUserInfoProvider [] providers,
GenericPrincipal user,
HttpSessionState session)
{
this.m_session = session;
if(!this.IsInitialized) {
foreach(IExpandedUserInfoProvider provider in providers) {
ExpandedUserInfoType providerType = provider.ExpandedType;
object o = provider.GetExpandedInfo(user);
if(o != null)
{
int prevContainedTypes = this.ConatinedTypes;
this.ConatinedTypes = prevContainedTypes |= (int)providerType;
this[providerType] = o;
}
}
this.IsInitialized = true;
}
}
public bool HasType(ExpandedUserInfoType type)
{
return Convert.ToBoolean((int)type & this.ConatinedTypes);
}
public bool IsInitialized
{
get {
return(this.ExpandedUserInfoHash["INITIALIZED"] != null &&
Convert.ToBoolean(this.ExpandedUserInfoHash["INITIALIZED"]));
}
set {
this.ExpandedUserInfoHash["INITIALIZED"] = value;
}
}
public object this [ExpandedUserInfoType type]
{
get { return this.ExpandedUserInfoHash[type.ToString()]; }
set {this.ExpandedUserInfoHash[type.ToString()] = value;}
}
private int ConatinedTypes
{
get
{
return (ExpandedUserInfoHash["ContainedTypes"] == null)
? 0 : (int) ExpandedUserInfoHash["ContainedTypes"];
}
set{ ExpandedUserInfoHash["ContainedTypes"] = value;}
}
private Hashtable ExpandedUserInfoHash
{
get {
if(this.m_session[SESSION_KEY] == null)
this.m_session[SESSION_KEY] = new Hashtable();
return (Hashtable) this.m_session[SESSION_KEY];
}
set{ this.m_session[SESSION_KEY] = value;}
}
}
For each new provider you have, you are going to have to add a type to an enum that keeps track of all of the possible types. The enum is called ExpandedUserInfoType, and mine looks like this:
public enum ExpandedUserInfoType
{
TruckerInfo = 0x01,
ShiplineInfo = 0x02,
BrokerInfo = 0x04,
CompanyInfo = 0x08
}
Our phony example version looks like this:
public enum ExpandedUserInfoType
{
PartnerInfo = 0x01,
CustomerInfo = 0x02,
EmployeeInfo = 0x04
}
You can have as many types as you need to add, just remember that this type is going to be used as a bitflag, so you need to make sure the values you add are bitfields. See my post here for more info on this.
Okay, so how does this all get used? Lets take the Partner Code example. You would need to do the following:
- Create your Partner Code Provider class that implements IExpandedUserInfoProvider
- Optionally create a type that actually holds the partner code (for simplicity I'm going to use a basic string but note that you can return any type)
- Add a new enumerated value to the ExpandedUserInfoType enumeration
- When you need your expanded info, access it through an instance of the ExpandedUserInfo class. Remember, this class will lazy load all of the types it needs.
So, in air-code, here's an implementation example of my PartnerCodeProvider:
public class PartnerCodeProvider : IExpandedUserInfoProvider
{
public object GetExpandedInfo(GenericPrincipal user) {
// Go get your Partner Code Here
return “Expensive Partner Code“;
}
public ExpandedUserInfoType ExpandedType
{
get { return ExpandedUserInfoType.PartnerCode;}
}
}
And here's how to use it. Keep in mind that you can simplify this by keeping a reference to the ExpandedUserInfo class in you page base with a list of all providers you want to support:
ExpandedUserInfo eui = new ExpandedUserInfo(
new IExpandedUserInfoProvider [] {new PartnerCodeProvider()},
this.CurrentUser, this.Session);
// Check to see if this user has this expanded type
if(eui.HasType(ExpandedUserInfoType.PartnerCode))
{
String strPartnerCode = (String)this.CurrentUserExpandedInfo[ExpandedUserInfoType.PartnerCode];
// Now, do something really cool with your Partner Code!
}
That's about it. I hope I didn't leave anything out. If you can get this set up, it's well worth it when later on you realize that you have to scale your user information.
-Brendan