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?
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.