开发者

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

开发者 https://www.devze.com 2023-02-12 01:28 出处:网络
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 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 presenta开发者_C百科tion 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.


I would strongly recommend passing anything you need in the view down via the controller. That way, the decision on exactly what data the view should render stays with the controller. In order to make that as easy as possible, creating an abstract ViewModelWithUserBase class that has a settable User property really isn't a bad idea. An option is to create an interface IViewModelWithUser, and re-implement the User property every time (or combine with the base class, but you would have the option to re-implement instead of inheriting the base class if that makes things easier in some corner cases).

As far as populating this property, it can probably be done easily with an action filter. Utilizing the OnActionExecuted method you can test if the model passed to the view implements your base class (or interface), and then fill the property with the correct IPrincipal object if appropriate. This has the advantage that since action filters aren't executed in unit tests, you can use the HttpContext.Current.Session dependent code from your option 1 in your action filter, and still have a testable interface on the controller.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号