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?
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;
}
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With