views:

68

answers:

2

I want to be able to access custom properties for an authenticated user like UserId and FirstName without querying the database each time. I found this site through a post on Stack Overflow and I like the approach - but I use IoC / repositories and decided not to try and get global.asax to communicate with the database for fear that it would be incompatible with the repository pattern.

Instead, I created an interface to CustomPrincipal and I use IoC (Castle) to create an instance and pass it to the controllers (and subsequently to my base controller).

The base controller uses methods I created in CustomPrincipal to achieve the same task that the blog author addressed in global.asax. Namely, CustomPrincipal is initialized from the database or cache and assigned to HttpContext.Current.User.

My controllers/views can then reference the properties as follows...

((ICustomPrincipal)(HttpContext.Current.User)).FirstName;

It works, but I'm sensing some code smells. First and foremost, if I reference HttpContext from the controllers I've killed my unit testing. I'm thinking about modifying my CustomPrincipal object to return the above value (such that I can mock it in my unit tests) but I'm wondering if this is a workaround as opposed to a good solution.

Am I going about this the right way? Are there minor tweaks I could do to make it a robust solution or should I start from scratch with FormsAuthenticationTicket or something to that effect?

Thanks!

+2  A: 

What about creating an ICustomPrincipalManager interface?

public interface ICustomPrincipalManager 
{
    ICustomPrincipal Current {get;}
}

It can be implemented by a class that accesses HttpContext, the database, caching, or whatever, but you could also mock the interface for unit testing. Your controllers would use the IoC framework to get your ICustomPrincipalManager, and then access information like this:

_customPrincipalManager.Current.FirstName
StriplingWarrior
That sounds like a great idea - I'm going to play around with that to see if I can make it work. Thanks!
Mayo
No problem. Let me know if you run into any snags.
StriplingWarrior
+1  A: 

I wanted to throw out an alternative idea just so people looking for this information can have some choices.

I went searching for a viable FormsAuthenticationTicket example and found that NerdDinner does a pretty decent job adding custom properties without impacting unit testing.

In my case, I modified my LogOn routine (which I was already mocking in my unit tests) to create a FormsAuthenticationTicket. NerdDinner encrypts the ticket and adds it as a cookie, but I am also able to add the encrypted ticket to cache like the original proposal. I also replaced the single UserData property with a JSON serialized object representing all of my custom properties.

CustomIdentityDTO dto = new CustomIdentityDTO { 
   UserId = userId, FirstName = firstName, LastName = lastName };
JavaScriptSerializer serializer = new JavaScriptSerializer();

FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
  1, // version
  username,
  DateTime.Now, // creation
  DateTime.Now.AddMinutes(30), // expiration
  false, // not persistent
  serializer.Serialize(dto));

string encTicket = FormsAuthentication.Encrypt(authTicket);
//HttpContext.Current.Response.Cookies.Add(...)
HttpContext.Current.Cache.Add(username, encTicket, ...

Then I retrieve the encrypted ticket (either from cache or cookies) in global.asax through a PostAuthenticateRequest handler much like NerdDinner (for cookie) or the blogger's proposal (for cache).

NerdDinner implements IIdentity instead of IPrincipal. References to the custom fields in the code are as follows:

((CustomIdentity)Page.User.Identity).FirstName // from partial view

((CustomIdentity)User.Identity).FirstName // from controller

After working with both methods, I find that NerdDinner's approach works very well. After switching over I haven't encountered much in the way of obstacles.

Mayo