Better way of doing strongly-typed ASP.NET MVC sessions
Asked Answered
I

5

21

I am developing an ASP.NET MVC project and want to use strongly-typed session objects. I have implemented the following Controller-derived class to expose this object:

public class StrongController<_T> : Controller
    where _T : new()
{
    public _T SessionObject
    {
        get
        {
            if (Session[typeof(_T).FullName] == null)
            {
                _T newsession = new _T();
                Session[typeof(_T).FullName] = newsession;
                return newsession;
            }
            else
                return (_T)Session[typeof(_T).FullName];
        }
    }

}

This allows me to define a session object for each controller, which is in line with the concept of controller isolation. Is there a better/more "correct" way, perhaps something that is officially supported by Microsoft?

Inarch answered 10/11, 2009 at 20:14 Comment(5)
WHat will happen if you pass the same type to more than one controller? One session will overwrite the other one?Shoelace
No, they'll both have the same type name, and thus the same session key. The session objects won't be replaced, they'll just be the same object across both controllers.Inarch
Answer added below that doesn't require base controllers and that can even access session in view code as well.Smith
Why do you use _T? Why not just T?Shores
@ColeJohnson No particular reason, at least not that I can remember.Inarch
P
18

This way other objects won't have access to this object (e.g. ActionFilter). I do it like this:

public interface IUserDataStorage<T>
{
   T Access { get; set; }
}

public class HttpUserDataStorage<T>: IUserDataStorage<T>
  where T : class
{
  public T Access
  {
     get { return HttpContext.Current.Session[typeof(T).FullName] as T; }
     set { HttpContext.Current.Session[typeof(T).FullName] = value; }
  }
}

Then, I can either inject IUserDataStorage into controller's constructor, or use ServiceLocator.Current.GetInstance(typeof(IUserDataStorage<T>)) inside ActionFilter.

public class MyController: Controller
{
   // automatically passed by IoC container
   public MyController(IUserDataStorage<MyObject> objectData)
   {
   }
}

Of course for cases when all controllers need this (e.g. ICurrentUser) you may want to use property injection instead.

Potential answered 10/11, 2009 at 20:55 Comment(4)
I like this substantially more. I forgot about HttpContext.Inarch
How does this works when you use unity for dependency injection The current type, Main.Services.IUserDataStorage`1[sipObjects.HandyGuy], is an interface and cannot be constructed. Are you missing a type mapping?Syrup
Found something here msdn.microsoft.com/en-us/library/ff660936(v=pandp.20).aspxSyrup
objectData.Access is always null in MyController constructor. why is thatSyrup
D
5

This might be better for what you want. I would just create an extension method that can access your session. The added benefit to the extension method is that you no longer have to inherit from a controller, or have to inject a dependency that really isn't necessary to begin with.

public static class SessionExtensions {
  public static T Get<T>(this HttpSessionBase session, string key)  {
     var result;
     if (session.TryGetValue(key, out result))
     {
        return (T)result;
     }
     // or throw an exception, whatever you want.
     return default(T);
   }
 }


public class HomeController : Controller {
    public ActionResult Index() {
       //....

       var candy = Session.Get<Candy>("chocolate");

       return View(); 
    }

}
Dope answered 5/2, 2013 at 18:33 Comment(0)
H
2

http://codingsmith.co.za/a-better-way-of-working-with-httpcontext-session-in-mvc/ (apologies for the colours on my blog was tooling around with themes and just havent fixed it yet)

public interface ISessionCache
{
  T Get<T>(string key);
  void Set<T>(string key, T item);
  bool contains(string key);
  void clearKey(string key);
  T singleTon<T>(String key, getStuffAction<T> actionToPerform);
}


public class InMemorySessionCache : BaseSessionCache
{
    Dictionary<String, Object> _col;
    public InMemorySessionCache()
    {
        _col = new Dictionary<string, object>();
    }

    public T Get<T>(string key)
    {
        return (T)_col[key];
    }

    public void Set<T>(string key, T item)
    {
        _col.Add(key, item);
    }

    public bool contains(string key)
    {
        if (_col.ContainsKey(key))
        {
            return true;
        }
        return false;
    }

    public void clearKey(string key)
    {
        if (contains(key))
        {
            _col.Remove(key);
        }
    }
}



public class HttpContextSessionCache : BaseSessionCache
{
   private readonly HttpContext _context;

  public HttpContextSessionCache()
   {
      _context = HttpContext.Current;
   }

  public T Get<T>(string key)
   {
      object value = _context.Session[key];
      return value == null ? default(T) : (T)value;
   }

  public void Set<T>(string key, T item)
   {
      _context.Session[key] = item;
   }

   public bool contains(string key)
   {
       if (_context.Session[key] != null)
       {
          return true;
       }
       return false;
   }
   public void clearKey(string key)
   {
       _context.Session[key] = null;
   }
}

i came up with that a few years ago and it works fine. same basic idea as everyone else i guess, why microsoft dont just implement this as standard eludes me.

Higgs answered 26/7, 2013 at 9:28 Comment(0)
S
2

I generally use this for a session key and then explicitly add objects as needed. The reason for this is it's a clean way to do it and I find that you want to keep the number of objects in session to a minimum.

This particular approach brings together forms authentication and user session into one place so you can add objects and forget about it. The argument could be made that it is a big verbose, but it does prevent any double up and you shouldn't have too many objects in session.

The following can exist in a core library or wherever you want.

    /// <summary>
    /// Provides a default pattern to access the current user in the session, identified
    /// by forms authentication.
    /// </summary>
    public abstract class MySession<T> where T : class
    {
        public const string USERSESSIONKEY = "CurrentUser";

        /// <summary>
        /// Gets the object associated with the CurrentUser from the session.
        /// </summary>
        public T CurrentUser
        {
            get
            {
                if (HttpContext.Current.Request.IsAuthenticated)
                {
                    if (HttpContext.Current.Session[USERSESSIONKEY] == null)
                    {
                        HttpContext.Current.Session[USERSESSIONKEY] = LoadCurrentUser(HttpContext.Current.User.Identity.Name);
                    }
                    return HttpContext.Current.Session[USERSESSIONKEY] as T;
                }
                else
                {
                    return null;
                }
            }
        }

        public void LogOutCurrentUser()
        {
            HttpContext.Current.Session[USERSESSIONKEY] = null;
            FormsAuthentication.SignOut();
        }

        /// <summary>
        /// Implement this method to load the user object identified by username.
        /// </summary>
        /// <param name="username">The username of the object to retrieve.</param>
        /// <returns>The user object associated with the username 'username'.</returns>
        protected abstract T LoadCurrentUser(string username);
    }

}

Then implement this in the following class namespaced to the root of your project (I usually put it in a code folder on mvc projects):

public class CurrentSession : MySession<PublicUser>
{
    public static CurrentSession Instance = new CurrentSession();

    protected override PublicUser LoadCurrentUser(string username)
    {
        // This would be a data logic call to load a user's detail from the database
        return new PublicUser(username);
    }

    // Put additional session objects here
    public const string SESSIONOBJECT1 = "CurrentObject1";
    public const string SESSIONOBJECT2 = "CurrentObject2";

    public Object1 CurrentObject1
    {
        get
        {
            if (Session[SESSIONOBJECT1] == null)
                Session[SESSIONOBJECT1] = new Object1();

            return Session[SESSIONOBJECT1] as Object1;
        }
        set
        {
            Session[SESSIONOBJECT1] = value;
        }
    }

    public Object2 CurrentObject2
    {
        get
        {
            if (Session[SESSIONOBJECT2] == null)
                Session[SESSIONOBJECT2] = new Object2();

            return Session[SESSIONOBJECT2] as Object2;
        }
        set
        {
            Session[SESSIONOBJECT2] = value;
        }
    }
}

FINALLY The big advantage of explicitly declaring what you want in session is that you can reference this absolutely anywhere in your mvc application including the views. Just reference it with:

CurrentSession.Instance.Object1
CurrentSession.Instance.CurrentUser

Again a little less generic than other approaches, but really really clear what's going on, no other rigging or dependancy injection and 100% safe to the request context.

On another note, the dicionary approaches are cool, but you still end up with strings all over the place to reference stuff. You could rig it with enums or something, but I prefer the strong typing and set and forget of the above approach.

Smith answered 28/7, 2013 at 7:1 Comment(0)
K
2

Yes, it's years after this question was asked and there are other ways to do this... but in case anyone else shows up looking for something that combines the approaches above into an appealing one stop shop (at least one that appealed to my team and I...) Here's what we use.

 public enum SessionKey { CurrentUser, CurrentMember, CurrentChart, CurrentAPIToken, MemberBanner }

 public static class SessionCache {

    public static T Get<T>(this HttpSessionStateBase session, SessionKey key)
    {
        var value = session[key.ToString()];
        return value == null ? default(T) : (T) value;
    }

    public static void Set<T>(this HttpSessionStateBase session, SessionKey key, T item)
    {
        session[key.ToString()] = item;
    }

    public static bool contains(this HttpSessionStateBase session, SessionKey key)
    {
        if (session[key.ToString()] != null)
            return true;
        return false;
    }

    public static void clearKey(this HttpSessionStateBase session, SessionKey key)
    {
        session[key.ToString()] = null;
    }
}

Then in your controllers you can do your thing with your session variables in a more strongly typed way.

// get member
var currentMember = Session.Get<Member>(SessionKey.CurrentMember);
// set member
Session.Set<Member>(SessionKey.CurrentMember, currentMember);
// clear member
Session.ClearKey(SessionKey.CurrentMember);
// get member if in session
if (Session.Contains(SessionKey.CurrentMember))
{
     var current = Session.Get<Member>(SessionKey.CurrentMember);
}

Hope this helps someone!

Keenakeenan answered 19/1, 2017 at 17:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.