CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Brendan Tompkins [MVP]

Blog First. Ask Questions Later.

A Scalable ASP.NET Expanded User Properties Model

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: 

  1. Create your Partner Code Provider class that implements IExpandedUserInfoProvider
  2. 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)
  3. Add a new enumerated value to the ExpandedUserInfoType enumeration
  4. 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



About Brendan Tompkins

Brendan has been programming with .NET since the first public beta and is owner and operator of Port Technology Services, a consultancy company providing .NET application development services to the Maritime industry. In July, 2007, he was awarded the Microsoft MVP award for ASP.NET. He's also a proud co-founder of failed .COM startup Intrinsigo, and has had a hand in the failure of numerous other businesses. He currently runs CodeBetter.Com and Devlicio.us, and lives in Norfolk, Virgina with his wife Tiara and son Ian.

View Brendan's profile on LinkedIn

Check out Devlicio.us!

Our Sponsors

Free Tech Publications