ASP.Net会话中的静态单例

Static Singleton In ASP.Net Session

我一直在读ASP.NET中关于单例的内容,我看到了各种实现和建议。我尝试在这之后对我的实现进行建模:https://stackoverflow.com/…asp-net-singleton

这是我的问题:我想要一个我正在实例化的对象,以维持当前会话的生命,但不能在会话之间共享。例如,如果两个用户同时登录,我希望他们各自"拥有"一个全局对象的实例。下面是我的实现。这是正确的方法吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class AppGlobal
{
    #region Contructors

    public AppGlobal() { }

    public static AppGlobal Instance
    {
        get
        {
            HttpSessionState session = HttpContext.Current.Session;

            if (session["AppGlobalInstance"] == null)
            {
                session["AppGlobalInstance"] = new AppGlobal();
            }

            return (AppGlobal)session["AppGlobalInstance"];
        }
    }

    #endregion

    #region Public Properties

    public User UserObject { get; set; }
    public Campaign CampaignObject { get; set; }
    public List<int> SelectedContactIDs = new List<int>();
    public List<int> UnsubmittedContactIDs = new List<int>();
    public List<int> SubmittedContactIDs = new List<int>();
    public List<int> ProcessedContactIDs = new List<int>();

    #endregion

    #region Public Instance Methods

    public void ClearCampaign()
    {
        CampaignObject = null;
        UnsubmittedContactIDs.Clear();
        SubmittedContactIDs.Clear();
        ProcessedContactIDs.Clear();
        SelectedContactIDs.Clear();
    }
    public void LoadCampaign(int campaignID)
    {
        //Ensure that old data is overwritten
        MailCampaignManagerEntities db = new MailCampaignManagerEntities();

        db.Campaigns.MergeOption = System.Data.Objects.MergeOption.OverwriteChanges;

        //Clear the campaign and associated data
        ClearCampaign();

        //Set campaign object in AppGlobal
        this.CampaignObject = db.Campaigns.SingleOrDefaultasp.net(x => x.CampaignID == campaignID);

        //Populate Contact Status Lists
        this.UnsubmittedContactIDs.AddRange(from x in this.CampaignObject.CampaignContacts
                                                 where x.ContactSubmissionID == null
                                                 select x.CampaignContactID);

        this.SubmittedContactIDs.AddRange(from x in this.CampaignObject.CampaignContacts
                                               where x.ContactSubmissionID != null
                                               select x.CampaignContactID);

        this.ProcessedContactIDs.AddRange(from x in this.CampaignObject.CampaignContacts
                                               where x.ContactSubmissionID != null
                                               && x.ContactSubmission.DateProcessed != null
                                               select x.CampaignContactID);
    }

    #endregion

    #region Public Static Methods

    public static void WriteLogEntry(int? campaignID, int? contactSubmissionID, int? scheduledDropID, int? userID, string activityDescription)
    {
        ActivityLog activityLog = new ActivityLog();
        activityLog.CampaignID = campaignID;
        activityLog.ContactSubmissionID = contactSubmissionID;
        activityLog.ScheduledDropID = scheduledDropID;
        activityLog.UserID = userID;
        activityLog.Text = activityDescription;
        activityLog.CreatedDate = DateTime.Now;

        using (MailCampaignManagerEntities db = new MailCampaignManagerEntities())
        {
            db.ActivityLogs.AddObject(activityLog);
            db.SaveChanges();
        }
    }

    #endregion
}

实现通常应该是"好的",但是…

您应该根据配置的sessionstatemodule将对象标记为[Serializable]。Web场或Web花园通常使用InProc之外的模块,它们使用序列化来存储会话状态。否则,对象看起来可以序列化,因此没有问题。

您可能需要检查当前是否有任何会话,或者您可以获得一个NullReferenceException。这可能意味着应用程序配置错误,或者生命周期中的调用太早。

由于在检查和设置会话变量的过程中存在竞争条件,应用程序可能会为单个会话分配两次AppGlobal对象。我不认为这是一个问题,但如果你想包括更多的花哨的东西,这是需要记住的。为了防止出现这种情况,您可以这样使用lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AppGlobal
{
   private static object _syncRoot = new object();

   public static AppGlobal Instance
   {
       get
       {
           HttpSessionState session = HttpContext.Current.Session;

           lock (_syncRoot)
           {
               if (session["AppGlobalInstance"] == null)
               {
                   session["AppGlobalInstance"] = new AppGlobal();
               }
           }

           return (AppGlobal)session["AppGlobalInstance"];
       }
    }    
}

如果要在对象中存储禁止序列化的内容,并且需要支持其他sessionstatemodules,可以将实例存储在使用经典单例模式的集合中(这里是一个很好的实现)。同时行动可能是个不错的选择。作为密钥,您可以使用一些您在会话中存储的独特的东西,比如guid。当会话以任何方式结束时,需要从集合中删除该条目。


会话对象本质上来自于单例对象的字典。因此,当您引用会话对象时,您已经从场景后面的单例模式中获益。因此,不需要通过将会话对象放入单例模式来重新发明控制盘。


Session中的对象已经是唯一的,这意味着同一个键引用了对象的一个实例。


这看起来像一个有效的单例模式。在会话中,它看起来也是一个非常大的对象。如果有很多用户,可能会遇到性能和内存问题。