in my app I'd like to add functionality for admins to go to the specific screen and make certain controllers/methods available for certain roles.
Right now I'm using a build-in role check like
[Authorize(Roles = "APUL_Admin")]
So I changed that to be [AuthorizeExtended()] and I'm implementing it like that:
public class AuthorizeExtended : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
// Point of interest **HERE**
return true;
}
}
which is all pretty standard.
At this moment (HERE see above) from HttpContextBase I know user's roles, and controller and method. And I can go to the DB and make sure those roles has access to this controller/action.
Here is my problem:
I don't want to go to the database for every request since it is slow and it is a lot of overhead for DB. What's the best way to deal with that? Cache it? I'm looking for implementation details.
Any help is appreciated. Thank you!
Yes, the cache is what you need to avoid duplicated requests to the DB. Here is the basic implementation:
internal class CacheKey
{
public string Role { get; set; }
public string Controller { get; set; }
public string Method { get; set; }
public override bool Equals(Object obj)
{
CacheKey cmp = obj as CacheKey;
if (cmp == null)
{
return false;
}
return Role == cmp.Role && Controller == cmp.Controller && Method == cmp.Method;
}
public override int GetHashCode()
{
// Overflow is fine, just wrap
unchecked
{
int hash = 17;
hash = hash * 23 + Role.GetHashCode();
hash = hash * 23 + Controller.GetHashCode();
hash = hash * 23 + Method.GetHashCode();
return hash;
}
}
}
public class AuthorizeExtended : AuthorizeAttribute
{
private static ConcurrentDictionary<CacheKey, bool> cache = new ConcurrentDictionary<CacheKey, bool>();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
// Point of interest **HERE**
// Looking up in the cache
var cacheKey = new CacheKey
{
Role = role,
Controller = controller,
Method = method,
};
bool authorized;
if (cache.TryGetValue(cacheKey, out authorized))
{
return authorized;
}
// Make DB call and get value for authorized
// ...
// Store 'authorized' value in the cache
cache.TryAdd(cacheKey, authorized);
return authorized;
}
}
Related
The Data Access Layer is currently a repetition of 3 function: Create, Get, Set.
On a few Dlo type : Foo, Bar , FooBar.
Where Foo and FooBar have the same implementation and Bar has a more complexe one.
public static bool CreateFooBar(FooBarDlo newFooBar)
{
bool result = false;
using (var db = new FooModelDBcontext())
{
db.FooBars.Add(newFooBar);
result = db.SaveChanges() > 0;
}
return result;
}
public static FooBarDlo GetCustomer(int idFooBar)
{
FooBarDlo result;
using (var db = new FooModelDBcontext())
{
result = db.FooBars.FirstOrDefault(x => x.Id == idFooBar);
}
return result;
}
public static bool SetCustomer(FooBarDlo newFooBar)
{
bool result = false;
using (var db = new FooModelDBcontext())
{
var temp = db.FooBars.SingleOrDefault(x => x.Id == newFooBar.Id);
db.Entry(temp).CurrentValues.SetValues(newFooBar);
result = db.SaveChanges() > 0;
}
return result;
}
How can those be refactor, while keeping the specificities of the Bar implementation ?
There are several ways to go about it.
You could provide a base class that takes generics as a parameter with all virtual methods (pseudocode)
public abstract class DbLayer<T> {
public virtual T Get(int Id) {
// default implementation here
// but virtual allows overriding
}
public virtual T Create(T obj) {
// default implementation here
// but virtual allows overriding
}
}
public class FooBarDlo: DbLayer {
public override FooBarDlo Get(int Id) {
// override Get handling
}
}
But if I were you, I'd find a pre-built database layer on CodeProject and go with that.
I have a very simple scenario. I want to decorate my controllers/actions with a custom authorization attribute. Authorization should be granted if any of the attributes is valid. For example,
[MyAuth(1)]
[MyAuth(2)]
public class MyController : Controller
{
...
}
I cannot combine the parameters into a single authorization attribute. The above example is a simplified example, only.
If either attribute authorizes the user, I want the user to be authorized. I assumed that ActionFilterAttribute or AuthorizeAttribute would have the means to see what other filters have been executed and are waiting to be executed, but no such luck.
How can I accomplish this? Since the attributes don't seem to have any awareness, maybe an HttpModule? A custom ControllerActionInvoker?
I managed to get this to work last night. My solution is below. The attribute is pretty standard and I've trimmed the actual authorization parts. The interesting stuff happens in HasAssignedAcccessActionInvoker.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class RequiresAssignedAccess : AuthorizeAttribute
{
public int AccessType { get; private set; }
public int IdType { get; private set; }
public int IdValue { get; private set; }
public int Level { get; private set; }
public RequiresAssignedAccess(int accessType, int idType, int idValue, int level)
{
...
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!base.AuthorizeCore(httpContext))
return false;
bool retval = ...
return retval;
}
}
HasAssignedAcccessActionInvoker inherits from the standard action invoker, but I overrode the InvokeAuthorizationFilters method to add the authorization logic we need. The standard invoker just spins through the authorization filters and if any of them returns a result, it breaks the loop.
public class HasAssignedAcccessActionInvoker : ControllerActionInvoker
{
protected override AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
{
AuthorizationContext authCtx = new AuthorizationContext(controllerContext, actionDescriptor);
/*
* If any of the filters are RequiresAssignedAccess, default this to false. One of them must authorize the user.
*/
bool hasAccess = !filters.Any(f => f is RequiresAssignedAccess);
foreach (IAuthorizationFilter current in filters)
{
/*
* This sets authorizationContext.Result, usually to an instance of HttpUnauthorizedResult
*/
current.OnAuthorization(authCtx);
if (current is RequiresAssignedAccess)
{
if (authCtx.Result == null)
{
hasAccess = true;
}
else if (authCtx.Result is HttpUnauthorizedResult)
{
authCtx.Result = null;
}
continue;
}
if (authCtx.Result != null)
break;
}
if (!hasAccess && authCtx.Result == null)
authCtx.Result = new HttpUnauthorizedResult();
return authCtx;
}
}
I had to look at MVC's internals with ILSpy to figure this out. For reference, this is the overridden version of that method:
protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
{
AuthorizationContext authorizationContext = new AuthorizationContext(controllerContext, actionDescriptor);
foreach (IAuthorizationFilter current in filters)
{
current.OnAuthorization(authorizationContext);
if (authorizationContext.Result != null)
{
break;
}
}
return authorizationContext;
}
Lastly, to wire this up and make everything possible, our controllers inherit from BaseController, which now returns the new invoker.
public class BaseController : Controller
{
protected override IActionInvoker CreateActionInvoker()
{
return new HasAssignedAcccessActionInvoker();
}
}
As far as I know, you cannot chain [Authorize] attributes in the manner that you want because they all have to pass (AND) and not (OR) behavior. However, the combining of the items into one does not cause you to have to do some magic string manipulation, regardless of the number of parameters that you need to pass to it. You can define your own set of parameters that are available to the Authorize attribute.
public class SuperCoolAuthorize : AuthorizationAttribute
{
public string Parameter1{get;set;}
public string Parameter2{get;set;}
public int Parameter3{get;set;}
public string Parameter4{get;set;}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// your custom behaviour
}
}
And on your controller/action method
[Authorize(Parameter1 = "Foo", Parameter2 = "Bar", Parameter3 = 47, Parameter4 = string.Empty)
public ActionResult MyControllerAction(){
...
}
A great post on some other considerations on custom Authorizing attributes I came across in helping to formulate this answer.
public class AuthUserAttribute : AuthorizeAttribute {
public string[] SecurityGroups;
public string Groups { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext) {
bool valid = false;
var user = UserInformation.Current;
if (user.SecurityGroups.Select(x => x).Intersect(this.SecurityGroups).Any()) {
valid = true;
}
if (user.SecurityGroups.Select(x => x).Intersect(new string[] { "IT Administrators" }).Any()) {
valid = true;
}
return valid;
}
public override void OnAuthorization(AuthorizationContext filterContext) {
if (!this.AuthorizeCore(filterContext.HttpContext)) {
if (UserInformation.Current.SecurityGroups.Count == 0) {
filterContext.Result = new RedirectResult(string.Format("/oa?ReturnUrl={0}", filterContext.HttpContext.Request.RawUrl));
}
else {
filterContext.Result = new RedirectResult(string.Format("/oa/user/permissions?ReturnUrl={0}", filterContext.HttpContext.Request.RawUrl));
}
}
else {
base.OnAuthorization(filterContext);
}
}
}
then I decorate with
[AuthUser(SecurityGroups = new string[] { "Data1", "Data2" })]
public ActionResult ForYourEyesOnly() {
}
We'll see if anyone catches the Bond reference. LOL
Our MvcSitemap has some DynamicNodeProviders implemented.
We want these to be unique per session. But it appears they are unique per user.
So if a user logs into two different browsers, or computers, they currently share the same sitemap.
We do not want this.
But I can't seem to figure out how to get it to use the User/Session combination for uniqueness.
Is there a way to make this work?
Option 1:
Implement your own ICacheProvider based on session state and inject it using DI.
using System;
using System.Collections.Generic;
using MvcSiteMapProvider.Web.Mvc;
using MvcSiteMapProvider.Caching;
using System.Web;
public class SessionStateCacheProvider<T>
: ICacheProvider<T>
{
public SessionStateCacheProvider(
IMvcContextFactory mvcContextFactory
)
{
if (mvcContextFactory == null)
throw new ArgumentNullException("mvcContextFactory");
this.mvcContextFactory = mvcContextFactory;
}
private readonly IMvcContextFactory mvcContextFactory;
protected HttpContextBase Context
{
get
{
return this.mvcContextFactory.CreateHttpContext();
}
}
#region ICacheProvider<ISiteMap> Members
public bool Contains(string key)
{
return (Context.Session[key] != null);
}
public Caching.LazyLock Get(string key)
{
return (LazyLock)Context.Session[key];
}
public bool TryGetValue(string key, out Caching.LazyLock value)
{
value = this.Get(key);
if (value != null)
{
return true;
}
return false;
}
public void Add(string key, LazyLock item, ICacheDetails cacheDetails)
{
// NOTE: cacheDetails is normally used to set the timeout - you might
// need to roll your own method for doing that.
Context.Session[key] = item;
}
public void Remove(string key)
{
Context.Session.Remove(key);
}
public event EventHandler<MicroCacheItemRemovedEventArgs<T>> ItemRemoved;
#endregion
// NOTE: Normally this is called by a callback from the cache when an item exprires.
// It is required to ensure there is no memory leak because a sitemap has circular references
// that need to be broken explicitly. You need to work out how to call this when the user's session
// expires.
protected virtual void OnCacheItemRemoved(MicroCacheItemRemovedEventArgs<T> e)
{
if (this.ItemRemoved != null)
{
ItemRemoved(this, e);
}
}
}
Then inject it like this (StructureMap example shown):
// Setup cache
SmartInstance<CacheDetails> cacheDetails;
this.For<ICacheProvider<ISiteMap>>().Use<SessionStateCacheProvider<ISiteMap>>();
var cacheDependency =
this.For<ICacheDependency>().Use<NullCacheDependency>();
cacheDetails =
this.For<ICacheDetails>().Use<CacheDetails>()
.Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
.Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
.Ctor<ICacheDependency>().Is(cacheDependency);
Option 2:
Append the user name to the siteMapCacheKey in a custom ISiteMapCacheKeyGenerator, and inject it via DI:
public class SessionBasedSiteMapCacheKeyGenerator
: ISiteMapCacheKeyGenerator
{
public UserBasedSiteMapCacheKeyGenerator(
IMvcContextFactory mvcContextFactory
)
{
if (mvcContextFactory == null)
throw new ArgumentNullException("mvcContextFactory");
this.mvcContextFactory = mvcContextFactory;
}
protected readonly IMvcContextFactory mvcContextFactory;
#region ISiteMapCacheKeyGenerator Members
public virtual string GenerateKey()
{
var context = mvcContextFactory.CreateHttpContext();
var builder = new StringBuilder();
builder.Append("sitemap://");
builder.Append(context.Request.Url.DnsSafeHost);
builder.Append("/?sessionId=");
builder.Append(context.Session.SessionID);
return builder.ToString();
}
#endregion
}
Inject it like this (StructureMap example):
this.For<ISiteMapCacheKeyGenerator>().Use<SessionBasedSiteMapCacheKeyGenerator>();
Note that using an external DI container is required.
Please see my open question here and explain to me why you would want to do this on GitHub, as it renders most of the features useless: https://github.com/maartenba/MvcSiteMapProvider/issues/16#issuecomment-22229604
I'm trying to secure my MVC routes from a set of users that meet a set of criteria. Since MVC seems to use attributes quite a bit and Steven Sanderson uses one for security extensibility in his pro MVC book I started heading down this route, but I'd like to define the rule contextually based on the action I am applying it to.
Some actions are for employees only, some aren't.
Some actions are for company1 only, some aren't.
So I was thinking this type of usage...
[DisableAccess(BlockUsersWhere = u => u.Company != "Acme")]
public ActionResult AcmeOnlyAction()
{
...
}
[DisableAccess(BlockUsersWhere = u => u.IsEmployee == false)]
public ActionResult EmployeeOnlyAction()
{
...
}
Looks pretty clean to me and is really pretty easy to implement, but I get the following compiler error:
'BlockUsersWhere' is not a valid named attribute argument because it is not a valid attribute parameter type
Apparently you can not use a Func as an attribute argument. Any other suggestions to get around this issue or something else that provides the simple usage we've come to love in our MVC projects?
Necros' suggestion would work, however you would have to invoke his SecurityGuard helper in the body of every action method.
If you would still like to go with the declarative attribute-based approach (which has the advantage that you can apply the attribute to the whole Controller) you could write your own AuthorizeAttribute
public class CustomAuthorizeAttribute : AuthorizeAttribute {
public bool EmployeeOnly { get; set; }
private string _company;
public string Company {
get { return _company; }
set { _company = value; }
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
return base.AuthorizeCore(httpContext) && MyAuthorizationCheck(httpContext);
}
private bool MyAuthorizationCheck(HttpContextBase httpContext) {
IPrincipal user = httpContext.User;
if (EmployeeOnly && !VerifyUserIsEmployee(user)) {
return false;
}
if (!String.IsNullOrEmpty(Company) && !VerifyUserIsInCompany(user)) {
return false;
}
return true;
}
private bool VerifyUserIsInCompany(IPrincipal user) {
// your check here
}
private bool VerifyUserIsEmployee(IPrincipal user) {
// your check here
}
}
Then you would use it as follows
[CustomAuthorize(Company = "Acme")]
public ActionResult AcmeOnlyAction()
{
...
}
[CustomAuthorize(EmployeeOnly = true)]
public ActionResult EmployeeOnlyAction()
{
...
}
Since you can only use constants, types or array initializers in attribute parameters, they probably won't do, or at least the won't be as flexible.
Alternatively, you could use something similar I came up with when solving this problem.
This is the API:
public static class SecurityGuard
{
private const string ExceptionText = "Permission denied.";
public static bool Require(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
return expression.Eval();
}
public static bool RequireOne(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
return expression.EvalAny();
}
public static void ExcpetionIf(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
if(expression.Eval())
{
throw new SecurityException(ExceptionText);
}
}
}
public interface ISecurityExpression
{
ISecurityExpression UserWorksForCompany(string company);
ISecurityExpression IsTrue(bool expression);
}
Then create an expression builder:
public class SecurityExpressionBuilder : ISecurityExpression
{
private readonly List<SecurityExpression> _expressions;
public SecurityExpressionBuilder()
{
_expressions = new List<SecurityExpression>();
}
public ISecurityExpression UserWorksForCompany(string company)
{
var expression = new CompanySecurityExpression(company);
_expressions.Add(expression);
return this;
}
public ISecurityExpression IsTrue(bool expr)
{
var expression = new BooleanSecurityExpression(expr);
_expressions.Add(expression);
return this;
}
public bool Eval()
{
return _expressions.All(e => e.Eval());
}
public bool EvalAny()
{
return _expressions.Any(e => e.Eval());
}
}
Implement the security expressions:
internal abstract class SecurityExpression
{
public abstract bool Eval();
}
internal class BooleanSecurityExpression : SecurityExpression
{
private readonly bool _result;
public BooleanSecurityExpression(bool expression)
{
_result = expression;
}
public override bool Eval()
{
return _result;
}
}
internal class CompanySecurityExpression : SecurityExpression
{
private readonly string _company;
public CompanySecurityExpression(string company)
{
_company = company;
}
public override bool Eval()
{
return (WhereverYouGetUser).Company == company;
}
}
You can add as many custom expressions as you need. The infrastructure is a bit complicated, but then usage is really simple:
public ActionResult AcmeOnlyAction()
{
SecurityGuard.ExceptionIf(s => s.UserWorksForCompany("Acme"));
}
You can also chain the expression, and use it as a condition in view fro example (using SecurityGuard.Require()).
Sry for long post, hope this helps.
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?
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.
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();
}
}
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.
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.
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!