1

I am developing a web application in ASP.NET MVC5.

Like all basic web applications it also has a login page where a user can authenticate himself. Once authenticated I want to store a couple of user-related items in the Session so I don't have to query the database every time to reconstruct the authenticated user.

After having read Mark Seemann's book about Dependency Injection I want to loosely couple all my layers and make sure that everything can easily be replaced.

At the moment my SessionProvider is by default using the Session object, but maybe in the future this could change to another type of storage mechanism.

The approach I have taken is by using Ambient Context which he explained with the TimeProvider example, but I am wondering if this is the right approach for this functionality and if it is thread safe (also for unit testing).

Is my solution proper or how would you implement such a mechanism? This has been in my head for days now so who can help me define the best solution?

Thanks!

public abstract class SessionProvider
{
    private static SessionProvider _current;

    static SessionProvider()
    {
        _current = new DefaultSessionProvider();
    }

    public static SessionProvider Current
    {
        get { return _current; }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException();
            }
            _current = value;
        }
    }

    public abstract string UserName { get; set; }
}

My local default:

public class DefaultSessionProvider : SessionProvider
{
    public override string UserName
    {
        get { return (string) HttpContext.Current.Session["username"]; }
        set { HttpContext.Current.Session["username"] = value; }
    }
}

So I have access in my entire solution to my SessionProvider, whether this is a real session object or a database-driven storage mechanism...

SessionProvider.Current.UserName = "myUserName";
Jules
  • 7,148
  • 6
  • 26
  • 50
  • As you can read in Mark Seemann's book, the Ambient Context is HARDLY EVER the right solution. I would say that it is NOT the right solution, untill proven otherwise. – Steven Apr 20 '15 at 20:38
  • Yes hardly.. But here it would pollute the API if I'd have to pass it to every service. It's only used to query data, not to write it. A proper local default exists (DefaultSessionProvider) and it's guaranteed available. And in a way the Session on itself could be seen as an Ambient Context as well I think... What solution would you suggest then to store and retrieve session data? – Jules Apr 22 '15 at 11:43
  • If you need to "pass it to every service", you have a different problem. In that case it is very likely you are talking about a cross-cutting concern. Cross-cutting concerns should be separated from your normal code, for instance using interception, decoration or other aspect-oriented programming techniques. – Steven Apr 22 '15 at 12:00
  • Yes maybe you have a valid point and probably I won't have to pass it to every service. The main reason I need to have access to the session for is to validate the user's login in an authorization attribute. So rather than accessing directly the Session variable I'd prefer to go through a provider so I could possibly replace it easily later on. The second reason is that whenever I want to log something (anywhere in the application) I'd want to have access to some user data as it's vital information to have in the logs. So basically I'm just stuck on how to develop that properly. Suggestions? – Jules Apr 24 '15 at 11:25
  • If you are logging from anywhere in the application, make sure that [you're not logging too much](https://stackoverflow.com/a/9915056/264697). Besides, doesn't seem that every class that logs should need user information to be able to log. It should be your [ILogger implementation](https://stackoverflow.com/a/5646876/264697) that takes a dependency on an `IUserContext` of some sort and extends the logged information with user name and time. The implementation for `IUserContext` can use the session directly. – Steven Apr 24 '15 at 11:53

1 Answers1

0

Once authenticated I want to store a couple of user-related items in the Session so I don't have to query the database every time to reconstruct the authenticated user.

Well, it looks like you're working on some sort of caching mechanism. It doesn't really matter if it's in a Session or in Redis cache, or any other type of cache. And this cache is key-value storage. I would create cache interface, something like that:

interface ICache
{
     object this[string key] {get; set;}
}

And create concrete classes. SessionCache in your case:

public SessionCache : ICache
{
    private IHttpSessionState _session;
    public SessionCache(IHttpSessionState session)
    {
        _session = session;
    }

    // ICache implementation goes here...
}

So you'll narrow down the problem to dependency-inject Session object to concrete class (SessionCache). With Ninject you can do something like:

.WithConstructorArgument("session",ninjectContext=>HttpContext.Session);

And after that you can finally make your controllers dependent on ICache.

In your unit tests project you can create another ICache concrete class, something like DummyCache with in-memory cache. So you can test your controllers without sticking to Session object.

Roman Pushkin
  • 5,639
  • 3
  • 40
  • 58