1

What is the nicest pattern you have seen or used for accessing the current logged on user in an Asp.NET application?

Current user refers to the business object that represents a user in your system, be it a membership user object or a custom class. The user object should be easy to reach within places like global (httpapplication object), web forms, controllers, webhandlers etc. It is probably be sent to the back end layers of the project and sometimes injected in other business objects depending on your application.

Requirements:

  • Working nicely in both MVC and WebForms.
  • The pattern should be able to work with either aspnet membership classes or own classes being "users".
  • Clear and obvious way of dealing with when not being logged in.
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Carl R
  • 8,104
  • 5
  • 48
  • 80
  • 4
    And what is wrong with the built-in membership/role providers? – Josh Nov 26 '10 at 15:54
  • Nothing is wrong with them. I use them a lot. But it's how to access user objecs, be it membership objecs or custom objecs that is the question. :) – Carl R Nov 26 '10 at 16:17

2 Answers2

2

I am actually quite happy with what's already built-in ASP.NET:

if (User.Identity.IsAuthenticated)
{
    string currentlyLoggedUsername = User.Identity.Name;
    // TODO: Now given this username you could query your repository
    // or the Membership provider to fetch additional user information.
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • I see, but it doesn't solve the requirements for accessing a custom User object. – Carl R Nov 26 '10 at 16:15
  • There are different techniques of implementing a custom User object. Actually it's quite easy to have a custom user object, what is more difficult is to decide where do you want to store the additional information. Are you going to query your data store on each request to fetch it from there or could you store it in the `userData` part of the authentication cookie? – Darin Dimitrov Nov 26 '10 at 16:19
  • I'm not asking for one solution. I'm not trying to solve a specific problem. – Carl R Nov 26 '10 at 16:22
  • 1
    Yes but you are asking about a *nicest pattern*. Without a clearly defined requirements it's a bit subjective to talk about nicest pattern, don't you think? – Darin Dimitrov Nov 26 '10 at 16:24
  • Well what a person thinks is always subjective isn't it? I think however it would be safe to assume that the user object will be fetched from a data store, cache or similar between calls. Say that you want to be able to query this object for username, id, company, name, roles, maybe having some custom properties etc. What you would need to somewhere display the logged in persons name, to use it for fine grained authorization in parts of your system, for logging and clear error messages for developers giving support etc... – Carl R Nov 26 '10 at 16:31
  • 2
    Personally I use models and repositories to fetch those models. So I could have a User model containing all the necessary information about a user and a repository which will allow me to fetch this information from a data store given a username. You could also implement a custom membership provider which would do this job instead of using a repository. – Darin Dimitrov Nov 26 '10 at 16:34
  • @Carl R, you should probably edit your question with these new requirements. – John Farrell Nov 26 '10 at 16:45
  • Darin, the problem with doing this with data accessors everywhere is that you end up fetching the same object from the database over and over again. The idea is to do it once per call and have it easily available. Simply using DI and injecting the object everywhere using request context as lifetime is one way. But I don't want to answer my own question, I'm just looking for nice clear thought out patterns for accessing the object representing the current user. – Carl R Nov 26 '10 at 16:59
  • You are correct in pointing out that fetching it from the database on each request could be expensive. What I do in this case is to use the built-in cache. The benefit of this approach compared to storing it in-memory (if you used a DI container) is that Cache object is extensible and it could be distributed when working for example in a web farm for better scalability. – Darin Dimitrov Nov 26 '10 at 17:03
  • 2
    @Carl R - More requirements. Great. Please think out your question a little more. It is overly broad and now your adding performance considerations and now the DI framework needs to know about it. Please also clarify what you mean by WebForms and MVC compatibility. Will you accept an answer that uses a BasePage class in webforms and a base controller in MVC? Does the code have to be exactly the same? Define "Nice" while your at it. What does that mean? The code cooks you breakfast in the morning? – John Farrell Nov 26 '10 at 17:06
  • But you are not encapsulating your if statement, username lookup, cache+repository query and user object declaration into one place? – Carl R Nov 26 '10 at 17:08
  • @Carl R, of course that you are encapsulating it unless you want to repeat it everywhere. – Darin Dimitrov Nov 26 '10 at 17:08
  • @jfar Regarding base classes, yes if you think it's neccesary. I think ideally the code should be the same, but who am I to judge. I'm just asking for what *you* think is nice. To me nice is readable, testable, easy to work with, probably following single responsibility principle. But really, I'm just asking what you like. – Carl R Nov 26 '10 at 17:16
  • @Darin How are you accessing your encapsulated code? Do you instantiate the class containing it everywhere, or are you having some other object owning it, or is it a singleton or monostate? – Carl R Nov 26 '10 at 17:19
  • @Carl R, this would totally depend on the type of application I am developing (whether it's standard ASP.NET or ASP.NET MVC), the requirements of this application (where/when/why I would need those user properties), the .NET framework version, etc... I just can't speak for the general case. I always try to take into account the specifics of the given project I am working on. – Darin Dimitrov Nov 26 '10 at 17:23
2

Here's what I do to store an object instead of a name which I find less useful.

In my account controller:

    public static User CacheUserInSession(User user)
    {
        Contract.Assert(user != null);

        if (user == null) // Not found, name may have changed
        {
            new FormsAuthenticationService().SignOut();
        }
        else
        {
            // Save off the user for later use by other classes
            ContextCache<User>.Set(user, ContextCache.AspNetUserSessionCache);
            ContextCache<int>.Set(user.RowId, ContextCache.UserIdSessionCache);
        }

        return user;
    }

    public static User GetUserFromSession()
    {
        var user = ContextCache<User>.Get(ContextCache.AspNetUserSessionCache);

        return user;
    }

My cache class:

public class ContextCache<T>
{    
    public static void Set(T objectToCache, string key)
    {
        System.Web.HttpContext.Current.Items.Add(key, objectToCache);
    }

    public static T Get(string key)
    {
        return (T)System.Web.HttpContext.Current.Items[key];
    }

}

I call CacheUserInSession from Application_AuthenticateRequest passing in my User object/model which I read from the database.

When I want to retrieve the user, I make this call:

var user = AccountController.GetUserFromSession();

Using the cache wrapper, you can store/retrieve any kind of object you want.

No mess. No fuss.

rboarman
  • 8,248
  • 8
  • 57
  • 87
  • Looks very nice and clean to me. AuthenticateRequest is as early you can be (if not resorting to completely custom schemes) and if your user isn't authenticated he's logged out. :) – Carl R Nov 26 '10 at 18:03
  • There's one slight catch btw, if one is using it in say the global errorhandler, then it would fail for instance in an error on session_end, when context doesn't exist. Still, there's an easy fix in form of an if statement in the Get Set methods in ContextCache. – Carl R Nov 26 '10 at 18:07