Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Singleton in MVC 5 session specific?

I have a Singleton model class in my MVC application to determine if the user logging in has authorization/admin (based on memberships to certain AD groups). This model class needs to be a Singleton so that the user's access rights can be established once at first logon and used throughout the session:

public sealed class ApplicationUser
{
    // SINGLETON IMPLEMENTATION
    // from http://csharpindepth.com/articles/general/singleton.aspx#lazy
    public static ApplicationUser CurrentUser { get { return lazy.Value; } }

    private static readonly Lazy<ApplicationUser> lazy = 
        new Lazy<ApplicationUser>(() => new ApplicationUser());

    private ApplicationUser()
    {
        GetUserDetails(); // determine if user is authorized/admin 
    }

    // Public members
    public string Name { get { return name; } }
    public bool IsAuthorized { get { return isAuthorized; } }
    public bool IsAdmin { get { return isAdmin; } }

    // Private members
    // more code
}

The Singleton is instantiated for the first time in my EntryPointController that all other controllers derive from:

public abstract class EntryPointController : Controller
{
    // this is where the ApplicationUser class in instantiated for the first time
    protected ApplicationUser currentUser = ApplicationUser.CurrentUser;        
    // more code
    // all other controllers derive from this
}

This patterns allows me to use ApplicationUser.CurrentUser.Name or ApplicationUser.CurrentUser.IsAuthorized etc all over my application.

However, the problem is this:
The Singleton holds the reference of the very first user that logs in at the launch of the web application! All subsequent users who log in see the name of the earliest logged-in user!

How can I make the Singleton session specific?

like image 358
SNag Avatar asked Feb 09 '23 05:02

SNag


2 Answers

I think you are looking for the Multiton pattern, where each instance is linked to a key.

An example from here

http://designpatternsindotnet.blogspot.ie/2012/07/multiton.html

using System.Collections.Generic;
using System.Linq;

namespace DesignPatterns
{
    public class Multiton
    {
        //read-only dictionary to track multitons
        private static IDictionary<int, Multiton> _Tracker = new Dictionary<int, Multiton> { };

        private Multiton()
        {
        }

        public static Multiton GetInstance(int key)
        {
            //value to return
            Multiton item = null;

            //lock collection to prevent changes during operation
            lock (_Tracker)
            { 
                //if value not found, create and add
                if(!_Tracker.TryGetValue(key, out item))
                {
                    item = new Multiton();

                    //calculate next key
                    int newIdent = _Tracker.Keys.Max() + 1;

                    //add item
                    _Tracker.Add(newIdent, item);
                }
            }
            return item;
        }
    }
}
like image 54
Kickaha Avatar answered Feb 12 '23 10:02

Kickaha


I got it working with a mixed Singleton-Multiton approach (thanks @Kickaha for the Multiton pointer).

public sealed class ApplicationUser
{
    // SINGLETON-LIKE REFERENCE TO CURRENT USER ONLY

    public static ApplicationUser CurrentUser
    { 
        get 
        { 
            return GetUser(HttpContext.Current.User.Identity.Name); 
        } 
    }

    // MULTITON IMPLEMENTATION (based on http://stackoverflow.com/a/32238734/979621)

    private static Dictionary<string, ApplicationUser> applicationUsers 
                            = new Dictionary<string, ApplicationUser>();

    private static ApplicationUser GetUser(string username)
    {
        ApplicationUser user = null;

        //lock collection to prevent changes during operation
        lock (applicationUsers)
        {
            // find existing value, or create a new one and add
            if (!applicationUsers.TryGetValue(username, out user)) 
            {
                user = new ApplicationUser();
                applicationUsers.Add(username, user);
            }
        }

        return user;
    }

    private ApplicationUser()
    {
        GetUserDetails(); // determine current user's AD groups and access level
    }

    // REST OF THE CLASS CODE

    public string Name { get { return name; } }
    public bool IsAuthorized { get { return isAuthorized; } }
    public bool IsAdmin { get { return isAdmin; } }

    private string name = HttpContext.Current.User.Identity.Name;
    private bool isAuthorized = false;
    private bool isAdmin = false;

    // Get User details
    private void GetUserDetails()
    {
        // Check user's AD groups and determine isAuthorized and isAdmin
    }
}

No changes to my model and controllers.

The current user's object is instantiated in the EntryPointController:

public abstract class EntryPointController : Controller
{
    // this is where the ApplicationUser class in instantiated for the first time
    protected ApplicationUser currentUser = ApplicationUser.CurrentUser;        
    // more code
    // all other controllers derive from this
}

In my model and everywhere else, I can access the current user's properties using ApplicationUser.CurrentUser.Name or ApplicationUser.CurrentUser.IsAuthorized etc.

like image 37
SNag Avatar answered Feb 12 '23 10:02

SNag