Search code examples
asp.net-mvcasp.net-mvc-3session-state

Where should I attach a custom user-context Session wrapper in ASP.NET MVC3?


I have read many posts on Session-scoped data in MVC, but I am still unclear where is the right place to include a custom Session wrapper into the solution.

I want to get the Username of the current user from the IPrincipal, load additional information about that User and store it in the Session. Then I want to access that User data from the Controller and the View.

None of the following approaches seem to fit what I want to do.

Option 1 : Access the Session collection directly

Everyone seems to agree this is a bad idea, but honestly it seems like the simplest thing that works. However, it doesn't make the User available to the view.

public class ControllerBase : Controller {
   public ControllerBase() : this(new UserRepository()) {}
   public ControllerBase(IUserRepository userRepository) {
      _userRepository = userRepository;
   }
   protected IUserRepository _userRepository = null;
   protected const string _userSessionKey = "ControllerBase_UserSessionKey";
   protected User {
      get { 
         var user = HttpContext.Current.Session[_userSessionKey] as User;
         if (user == null) {
            var principal = this.HttpContext.User;
            if (principal != null) {
               user = _userRepository.LoadByName(principal.Identity.Name);
               HttpContext.Current.Session[_userSessionKey] = user;
            }
         }
         return user;
      }
   }
}

Option 2: Injecting the Session into the class constructor forum post

This option seems pretty good, but I am still not sure how to attach it to the Controller and the View. I could new-it-up in the Controller, but shouldn't it be injected as a dependency?

public class UserContext {
   public UserContext() 
       : this(new HttpSessionStateWrapper(HttpContext.Current.Session), 
              new UserRepository()) { } 

   public UserContext(HttpSessionStateBase sessionWrapper, IUserRepository userRepository) { 
      Session = sessionWrapper;
      UserRepository = userRepository; 
   } 

   private HttpSessionStateBase Session { get; set; }
   private IUserRepository UserRepository{ get; set; }

   public User Current { 
      get {
         //see same code as option one
      }
   }
}

Option 3 : Use Brad Wilson's StatefulStorage class

In his presentation Brad Wilson features his StatefulStorage class. It is a clever and useful set of classes which include interfaces and uses constructor injection. However, it seems to lead me down the same path as Option 2. It uses interfaces, but I couldn't use the Container to inject it because it relies on a static factory. Even if I could inject it, how does it get passed to the View. Does every ViewModel have to have a base class with a setable User property?

Option 4 : Use something similar to the Hanselman IPrincipal ModelBinder

I could add the User as a parameter to the Action method and use a ModelBinder to hydrate it from the Session. This seems like a lot of overhead to add it everywhere it is needed. Plus I would still have to add it to the ViewModel to make it available to the View.

public ActionResult Edit(int id, 
   [ModelBinder(typeof(IPrincipalModelBinder))] IPrincipal user)
{ ... }

I feel like I am overthinking this, but it also seems like there should be an obvious place to do this sort of thing. What am I missing?


Solution

  • My approach to Session:

    Cover Session with interface:

    public interface ISessionWrapper
    {
        int SomeInteger { get; set; }
    }
    

    Implement interface using HttpContext.Current.Session:

    public class HttpContextSessionWrapper : ISessionWrapper
    {
        private T GetFromSession<T>(string key)
        {
            return (T) HttpContext.Current.Session[key];
        }
    
        private void SetInSession(string key, object value)
        {
            HttpContext.Current.Session[key] = value;
        }
    
        public int SomeInteger
        {
            get { return GetFromSession<int>("SomeInteger"); }
            set { SetInSession("SomeInteger", value); }
        }
    }
    

    Inject into Controller:

    public class BaseController : Controller
    {
        public ISessionWrapper SessionWrapper { get; set; }
    
        public BaseController(ISessionWrapper sessionWrapper)
        {
            SessionWrapper = sessionWrapper;
        }
    }
    

    Ninject dependency:

    Bind<ISessionWrapper>().To<HttpContextSessionWrapper>()
    

    You can pass some commonly used information using ViewData when you want to use it in master page and using view model in specific views.