Search code examples
c#design-patternssingletonstack-overflowlazy-evaluation

StackOverflow exception (fundamental architecture flaw)


I'm using a lazy singleton following Jon Skeet's great pattern here.

The purpose of this object is to provide references to all of the other methods in the application.

For example, GetTheme(Context.Current.User.Id) gets the theme for the current user, as do a plethora of other methods.

The issue I'm running into is how do I deal with changes in state when the object is already instantiated?

Namely, if a user comes to the website and isn't logged in, the Context object is used during the creation of the new user.

However, after logging in, the User object is null, because it was already instantiated.

I've tried to deal with this in the following way, making the public property reference a private method that checks for a null reference, and tries to determine if it should indeed be null.

Unfortunately, this turns into an infinite loop and crashes every time.

I have previously tried making the user object itself lazy, but for some strange reason it doesn't instantiate when called and remains null.

What I'm looking for is, how do I make the User property of my Lazy Singleton evaluate itself when called, and instantiate itself if it's null, but capable of being populated?

The conditions being, the MVC Global User object property User.Identity.Name is not null, and is passed into the session at load to be pulled in the model, and that a user exists in the database using the username as a key.

public sealed class Context
{
    public static Context Current { get { return lazy.Value; } }
    private static readonly Lazy<Context> lazy = 
      new Lazy<Context>(() => new Context());
    public UserMeta User { get { return _meta(); } }

    private Context()
    {
        Deployment = GetCurrentDeploymentType();
        Device = (Device)HttpContext.Current.Session["CurrentDevice"];
    }

    private UserMeta _meta()
    {
        //If the current object is null, 
        //but the user has been authenticated, populate the object
        if (Current.User == null && 
          !string.IsNullOrEmpty((string)HttpContext.Current.Session["UserEmail"]))
        {
            //check that the user is in the database first
            var _userTry = 
              Sql.Read.UserByEmail((string)HttpContext.Current.Session["UserEmail"]);
            if (_userTry == null)
            {
                return new UserMeta(
                 new UserMeta((string)HttpContext.Current.Session["UserEmail"]));
            }
            return null;
        }
        //If the Current Instance already has a populated User Object, 
        //just use that
        else if (Current.User != null)
            return Current.User;
        else
            return null;
    }
}

Solution

  • If you use a private field, it will solve the problem

    public sealed class Context
    {
        private UserMeta _user = null;
    
        public static Context Current { get { return lazy.Value; } }
        private static readonly Lazy<Context> lazy = new Lazy<Context>(() => new Context());
        public UserMeta User 
        { 
            get 
            {
                if (_user == null) _user =_meta(); 
                return _user;
            }
        }
    
        private Context()
        {
    
        private UserMeta _meta()
        {
          if (!string.IsNullOrEmpty((string)HttpContext.Current.Session["UserEmail"]))
          {
              //check that the user is in the database first
              var _userTry = 
                Sql.Read.UserByEmail((string)HttpContext.Current.Session["UserEmail"]);
              if (_userTry == null)
              {
                return new UserMeta(
                 new UserMeta((string)HttpContext.Current.Session["UserEmail"]));
              }
              return null;
          }
        }