Asp.net 實現Session分布式儲存(Redis,Mongodb,Mysql等) sessionState Custom


  對於asp.net 程序員來說,Session的存儲方式有InProc、StateServer、SQLServer和Custom,但是Custom確很少有人提及。但Custom確實最好用,目前最實用和最靈活一種方式,因為Custom可以實現各種情況的Session存儲,特別對於大型網站尤為重要,解決Session丟失和Session效率的最佳實現方式,也是實現單點登錄的最佳途徑。 對於InProc、StateServer和SQLServer各自優缺點,網上一大堆,這里就不做詳解。

 

重要的兩點

 1、首先是關於Session存儲,Session的存儲並不是我們想象的,當設置Session時立即往數據容器里插入或者修改數據,當獲取Session的值時就是立即去數據容器里獲取值,這種理解是錯誤的(我之前就是這樣理解的)。后來想想完全沒必要這樣操作,而且會大大影響效率。Asp.net的Session實現方式是每次請求前獲取數據,當請求邏輯代碼結束的時候在設置Session的值,所以說Session對數據容器的修改簡單的意義只有兩次,這個可能就要牽扯到管道流了。

2、第二個就是關於Asp.net網站異步的問題,當我們不設置Session的狀態為只讀時,我們每個用戶的請求其實都是同步的,也就說每個用戶請求網站同時只能有一個請求響應。理解這個就可以理解后面實現方法中需要鎖的概念了。

 

SessionStateStoreProviderBase

  SessionStateStoreProviderBase是asp.net框架為我們提供的一個用來存儲Session提供程序所需的成員(就是實現這個類),我們知道的InProc、SQLServer、StateServer都是實現了這個抽象類。繼承這個類需要有多個抽象方法需要實現。關於這多個方法沒必要每個都實現,我們只關注我們需要實現的即可。說簡單點就是對Session數據的增刪改查(CRUD)。關於SessionStateStoreProviderBase的詳情可以參考https://msdn.microsoft.com/zh-cn/library/system.web.sessionstate.sessionstatestoreproviderbase(v=vs.100).aspx

 

成員

說明

InitializeRequest 方法

采用當前請求的 HttpContext 實例作為輸入,並執行會話狀態存儲提供程序必需的所有初始化操作。

EndRequest方法

采用當前請求的 HttpContext 實例作為輸入,並執行會話狀態存儲提供程序必需的所有清理操作。

Dispose 方法

釋放會話狀態存儲提供程序不再使用的所有資源。

GetItemExclusive 方法

采用當前請求的 HttpContext 實例和當前請求的 SessionID 值作為輸入。從會話數據存儲區中檢索會話的值和信息,並在請求持續期間鎖定數據存儲區中的會話項數據。GetItemExclusive 方法設置幾個輸出參數值,這些參數值將數據存儲區中當前會話狀態項的狀態通知給執行調用的 SessionStateModule

如果數據存儲區中未找到任何會話項數據,則 GetItemExclusive 方法將 locked 輸出參數設置為 false,並返回 null。這將導致SessionStateModule 調用 CreateNewStoreData 方法來為請求創建一個新的 SessionStateStoreData 對象。

如果在數據存儲區中找到會話項數據但該數據已鎖定,則 GetItemExclusive 方法將 locked 輸出參數設置為 true,將 lockAge輸出參數設置為當前日期和時間與該項鎖定日期和時間的差,將 lockId 輸出參數設置為從數據存儲區中檢索的鎖定標識符,並返回 null。這將導致 SessionStateModule 隔半秒后再次調用 GetItemExclusive 方法,以嘗試檢索會話項信息和獲取對數據的鎖定。如果 lockAge 輸出參數的設置值超過 ExecutionTimeout 值,SessionStateModule 將調用 ReleaseItemExclusive 方法以清除對會話項數據的鎖定,然后再次調用 GetItemExclusive 方法。

如果 regenerateExpiredSessionId 屬性設置為 true,則 actionFlags 參數用於其 Cookieless 屬性為 true 的會話。actionFlags 值設置為 InitializeItem (1) 則指示會話數據存儲區中的項是需要初始化的新會話。通過調用CreateUninitializedItem 方法可以創建會話數據存儲區中未初始化的項。如果會話數據存儲區中的項已經初始化,則actionFlags 參數設置為零。

如果提供程序支持無 Cookie 會話,請將 actionFlags 輸出參數設置為當前項從會話數據存儲區中返回的值。如果被請求的會話存儲項的 actionFlags 參數值等於 InitializeItem 枚舉值 (1),則 GetItemExclusive 方法在設置 actionFlags out 參數之后應將數據存儲區中的值設置為零。

GetItem 方法

除了不嘗試鎖定數據存儲區中的會話項以外,此方法與 GetItemExclusive 方法執行的操作相同。GetItem 方法在EnableSessionState 屬性設置為 ReadOnly 時調用。

SetAndReleaseItemExclusive 方法

采用當前請求的 HttpContext 實例、當前請求的 SessionID 值、包含要存儲的當前會話值的 SessionStateStoreData 對象、當前請求的鎖定標識符以及指示要存儲的數據是屬於新會話還是現有會話的值作為輸入。

如果 newItem 參數為 true,則 SetAndReleaseItemExclusive 方法使用提供的值將一個新項插入到數據存儲區中。否則,數據存儲區中的現有項使用提供的值進行更新,並釋放對數據的任何鎖定。請注意,只有與提供的 SessionID 值和鎖定標識符值匹配的當前應用程序的會話數據才會更新。

調用 SetAndReleaseItemExclusive 方法后,SessionStateModule 調用 ResetItemTimeout 方法來更新會話項數據的過期日期和時間。

ReleaseItemExclusive 方法

采用當前請求的 HttpContext 實例、當前請求的 SessionID 值以及當前請求的鎖定標識符作為輸入,並釋放對會話數據存儲區中的項的鎖定。在調用 GetItem 或 GetItemExclusive 方法,並且數據存儲區指定被請求項已鎖定,但鎖定時間已超過ExecutionTimeout 值時會調用此方法。此方法清除鎖定,釋放該被請求項以供其他請求使用。

RemoveItem方法

采用當前請求的 HttpContext 實例、當前請求的 SessionID 值以及當前請求的鎖定標識符作為輸入,並刪除數據存儲區中與提供的 SessionID 值、當前應用程序和提供的鎖定標識符相匹配的數據存儲項的會話信息。此方法在 Abandon 方法被調用時調用。

CreateUninitializedItem方法

采用當前請求的 HttpContext 實例、當前請求的 SessionID 值以及當前請求的鎖定標識符作為輸入,並向會話數據存儲區添加一個 actionFlags 值為 InitializeItem 的未初始化項。

如果 regenerateExpiredSessionId 屬性設置為 true,則 CreateUninitializedItem 方法用於無 Cookie 會話,這將導致遇到過期會話 ID 時,SessionStateModule 會生成一個新的 SessionID 值。

生成新的 SessionID 值的過程需要瀏覽器重定向到包含新生成的會話 ID 的 URL。在包含過期的會話 ID 的初始請求期間,會調用CreateUninitializedItem 方法。SessionStateModule 獲取一個新的 SessionID 值來替換過期的會話 ID 之后,它會調用CreateUninitializedItem 方法以將一個未初始化項添加到會話狀態數據存儲區中。然后,瀏覽器重定向到包含新生成的SessionID 值的 URL。如果會話數據存儲區中存在未初始化項,則可以確保包含新生成的 SessionID 值的重定向請求被視為新的會話,而不會被誤認為是對過期會話的請求。

會話數據存儲區中未初始化的項與新生成的 SessionID 值關聯,並且僅包含默認值,其中包括到期日期和時間以及與 GetItem 和GetItemExclusive 方法的 actionFlags 參數相對應的值。會話狀態存儲區中的未初始化項應包含一個與 InitializeItem 枚舉值 (1) 相等的 actionFlags 值。此值由 GetItem 和 GetItemExclusive 方法傳遞給 SessionStateModule,並針對SessionStateModule 指定當前會話是新會話。然后,SessionStateModule 將初始化該新會話,並引發 Session_OnStart 事件。

CreateNewStoreData 方法

采用當前請求的 HttpContext 實例和當前會話的 Timeout 值作為輸入,並返回帶有空 ISessionStateItemCollection 對象的新的 SessionStateStoreData 對象、一個 HttpStaticObjectsCollection 集合和指定的 Timeout 值。使用GetSessionStaticObjects 方法可以檢索 ASP.NET 應用程序的 HttpStaticObjectsCollection 實例。

SetItemExpireCallback 方法

采用引用 Global.asax 文件中定義的 Session_OnEnd 事件的委托作為輸入。如果會話狀態存儲提供程序支持 Session_OnEnd事件,則設置對 SessionStateItemExpireCallback 參數的局部引用,並且此方法返回 true;否則,此方法返回 false。

 

 由於本機只有SQLServer作為數據存儲,所有就用SqlServer作為代碼演示,其原理都一樣的,直接改成Redis或者其他的就可以。

表結構

 1 CREATE TABLE [dbo].[ASPStateTempSessions](
 2     [SessionId] [nvarchar](88) NOT NULL,--唯一的SessionId
 3     [Created] [datetime] NOT NULL,--Session的創建時間
 4     [Expires] [datetime] NOT NULL,--過期的時間
 5     [LockDate] [datetime] NOT NULL,--鎖定的時間
 6     [LockId] [int] NOT NULL,--鎖定的Id
 7     [Timeout] [int] NOT NULL,--過期時間(分鍾)
 8     [Locked] [bit] NOT NULL,--是否鎖定
 9     [SessionItem] [nvarchar](max) NULL,--Session數據
10     [Flags] [int] NOT NULL,--標記是否是初始化數據
11  CONSTRAINT [PK_ASPStateTempSessions] PRIMARY KEY CLUSTERED 
12     (
13         [SessionId] ASC
14     ) 
15 )  

代碼實現

  1  /// <summary>
  2     /// 自定義Session實現方式
  3     /// </summary>
  4     public class MyCustomSessionStateStoreProvider : SessionStateStoreProviderBase
  5     { 
  6         /// <summary>
  7         /// 獲取配置文件的設置的默認超時時間
  8         /// </summary>
  9         private static TimeSpan _expiresTime; 
 10 
 11         /// <summary>
 12         /// 獲取Web.config 在sessionState設置的超時時間
 13         /// </summary>
 14         static MyCustomSessionStateStoreProvider()
 15         {
 16             System.Web.Configuration.SessionStateSection sessionStateSection = (System.Web.Configuration.SessionStateSection)System.Configuration.ConfigurationManager.GetSection("system.web/sessionState");
 17             _expiresTime = sessionStateSection.Timeout;
 18         }
 19 
 20         /// <summary>
 21         /// 創建新的存儲數據
 22         /// </summary>
 23         /// <param name="context"></param>
 24         /// <param name="timeout"></param>
 25         /// <returns></returns>
 26         public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
 27         {
 28             return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout);
 29         }
 30 
 31         /// <summary>
 32         /// 創建未初始化的項,就是初始化Session數據
 33         /// </summary>
 34         /// <param name="context"></param>
 35         /// <param name="id"></param>
 36         /// <param name="timeout"></param>
 37         public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
 38         {
 39             using (SessionStateEF db = new SessionStateEF())
 40             {
 41                 var session = new ASPStateTempSessions
 42                 {
 43                     Created = DateTime.Now,
 44                     Expires = DateTime.Now.AddMinutes(timeout),
 45                     Flags = (int)SessionStateActions.InitializeItem,
 46                     LockDate = DateTime.Now,
 47                     Locked = false,
 48                     SessionId = id,
 49                     LockId = 0,
 50                     Timeout = timeout
 51                 };
 52                 db.ASPStateTempSessions.Add(session);
 53                 db.SaveChanges();
 54             }
 55         }
 56 
 57         /// <summary>
 58         /// 釋放鎖定的項,就是把鎖定的Session的鎖的狀態清除掉
 59         /// </summary>
 60         /// <param name="context"></param>
 61         /// <param name="id"></param>
 62         /// <param name="lockId"></param>
 63         public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
 64         {
 65             using (SessionStateEF db = new SessionStateEF())
 66             {
 67                 var session = db.ASPStateTempSessions.Find(id);
 68                 if (session == null)
 69                 {
 70                     return;
 71                 }
 72 
 73                 // 把locked設置為false
 74                 session.Locked = false;
 75                 session.Expires = DateTime.Now + _expiresTime;
 76                 db.SaveChanges();
 77             }
 78 
 79         }
 80 
 81         /// <summary>
 82         /// 刪除Session,會在Session.Abandon()的時候調用
 83         /// </summary>
 84         /// <param name="context"></param>
 85         /// <param name="id"></param>
 86         /// <param name="lockId"></param>
 87         /// <param name="item"></param>
 88         public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
 89         {
 90             using (SessionStateEF db = new SessionStateEF())
 91             {
 92                 var session = db.ASPStateTempSessions.Find(id);
 93                 if (session == null)
 94                 {
 95                     return;
 96                 }
 97 
 98                 db.ASPStateTempSessions.Remove(session);
 99                 db.SaveChanges();
100             }
101         }
102 
103         /// <summary>
104         /// 設置超時時間
105         /// </summary>
106         /// <param name="context"></param>
107         /// <param name="id"></param>
108         public override void ResetItemTimeout(HttpContext context, string id)
109         {
110             using (SessionStateEF db = new SessionStateEF())
111             {
112                 var session = db.ASPStateTempSessions.Find(id);
113                 if (session == null)
114                 {
115                     return;
116                 }
117                 session.Expires = DateTime.Now + _expiresTime;
118                 db.SaveChanges();
119             }
120         }
121 
122         /// <summary>
123         /// 新建或者釋放鎖定的項並設置Session的值
124         /// </summary>
125         /// <param name="context"></param>
126         /// <param name="id"></param>
127         /// <param name="item"></param>
128         /// <param name="lockId"></param>
129         /// <param name="newItem"></param>
130         public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
131         {
132             using (SessionStateEF db = new SessionStateEF())
133             {
134                 // 判斷是否是新建,如果是新建則和CreateUninitializedItem不同在於Timeout和有初始值。
135                 if (newItem)
136                 {
137                     var session = new ASPStateTempSessions
138                     {
139                         Created = DateTime.Now,
140                         Expires = DateTime.Now.AddMinutes(item.Timeout),
141                         Flags = (int)SessionStateActions.None,
142                         LockDate = DateTime.Now,
143                         Locked = false,
144                         SessionId = id,
145                         LockId = 0,
146                         Timeout = item.Timeout,
147                         SessionItem = Serialize((SessionStateItemCollection)item.Items)
148                     };
149                     db.ASPStateTempSessions.Add(session);
150                     db.SaveChanges();
151                 }
152                 else// 釋放鎖定的項並設置Session的值
153                 {
154                     var session = db.ASPStateTempSessions.FirstOrDefault(i => i.SessionId == id);
155                     if (session == null)
156                     {
157                         return;
158                     }
159 
160                     session.Expires = DateTime.Now.AddMinutes(item.Timeout);
161                     session.Locked = false;
162                     session.LockId = Convert.ToInt32(lockId);
163                     session.SessionItem = Serialize((SessionStateItemCollection)item.Items);
164                     db.SaveChanges();
165                 }
166             }
167         }
168 
169         /// <summary>
170         /// 獲取項,這個方式主要是把Session狀態設置為只讀狀態時調用。
171         /// </summary>
172         /// <param name="context"></param>
173         /// <param name="id"></param>
174         /// <param name="locked"></param>
175         /// <param name="lockAge"></param>
176         /// <param name="lockId"></param>
177         /// <param name="actions"></param>
178         /// <returns></returns>
179         public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
180         {
181             return DoGet(false, context, id, out locked, out lockAge, out lockId, out actions);
182         }
183 
184         /// <summary>
185         /// 獨占獲取項,除了Session狀態為只讀時調用
186         /// </summary>
187         /// <param name="context"></param>
188         /// <param name="id"></param>
189         /// <param name="locked"></param>
190         /// <param name="lockAge"></param>
191         /// <param name="lockId"></param>
192         /// <param name="actions"></param>
193         /// <returns></returns>
194         public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
195         {
196             return DoGet(true, context, id, out locked, out lockAge, out lockId, out actions);
197         }
198 
199         /// <summary>
200         /// 獲取Session的值
201         /// </summary>
202         /// <param name="isExclusive"></param>
203         /// <param name="context"></param>
204         /// <param name="id"></param>
205         /// <param name="locked"></param>
206         /// <param name="lockAge"></param>
207         /// <param name="lockId"></param>
208         /// <param name="actions"></param>
209         /// <returns></returns>
210         public SessionStateStoreData DoGet(bool isExclusive, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
211         {
212             using (SessionStateEF db = new SessionStateEF())
213             {
214                 // 設置初始值
215                 var item = default(SessionStateStoreData);
216                 lockAge = TimeSpan.Zero;
217                 lockId = null;
218                 locked = false;
219                 actions = 0;
220 
221                 // 如果數據存儲區中未找到任何會話項數據,則GetItemExclusive 方法將 locked 輸出參數設置為false,並返回 null。
222                 // 這將導致 SessionStateModule調用 CreateNewStoreData 方法來為請求創建一個新的SessionStateStoreData 對象。
223                 var session = db.ASPStateTempSessions.Find(id);
224                 if (session == null)
225                 {
226                     return null;
227                 }
228 
229                 // 判斷session是否是ReadOnly 模式,不是readonly模式得判斷是否鎖住
230                 if (isExclusive)
231                 {
232                     // 如果在數據存儲區中找到會話項數據但該數據已鎖定,則GetItemExclusive 方法將 locked 輸出參數設置為true,
233                     // 將 lockAge 輸出參數設置為當前日期和時間與該項鎖定日期和時間的差,將 lockId 輸出參數設置為從數據存儲區中檢索的鎖定標識符,並返回 nul
234                     if (session.Locked)
235                     {
236                         locked = true;
237                         lockAge = session.LockDate - DateTime.Now;
238                         lockId = session.LockId;
239                         return null;
240                     }
241                 }
242 
243                 // 判斷是否過期
244                 if (session.Expires < DateTime.Now)
245                 {
246                     db.ASPStateTempSessions.Remove(session);
247                     return null;
248                 }
249 
250                 // 處理值
251                 lockId = lockId == null ? 0 : (int)lockId + 1;
252                 session.Flags = (int)SessionStateActions.None;
253                 session.LockId = Convert.ToInt32(lockId);
254 
255                 // 獲取timeout
256                 var timeout = actions == SessionStateActions.InitializeItem ? _expiresTime.TotalMinutes : session.Timeout;
257 
258                 // 獲取SessionStateItemCollection 
259                 SessionStateItemCollection sessionStateItemCollection = null;
260 
261                 // 獲取Session的值
262                 if (actions == SessionStateActions.None && !string.IsNullOrEmpty(session.SessionItem))
263                 {
264                     sessionStateItemCollection = Deserialize((session.SessionItem));
265                 }
266 
267                 item = new SessionStateStoreData(sessionStateItemCollection ?? new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), (int)timeout);
268 
269                 return item;
270 
271             }
272 
273         }
274 
275 
276         #region 序列化反序列化Session的值 
277         /// <summary>
278         /// 反序列化Session的數據
279         /// </summary>
280         /// <param name="item"></param>
281         /// <returns></returns>
282         public SessionStateItemCollection Deserialize(string item)
283         {
284             MemoryStream stream = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(item));
285             SessionStateItemCollection collection = new SessionStateItemCollection();
286             if (stream.Length > 0)
287             {
288                 BinaryReader reader = new BinaryReader(stream);
289                 collection = SessionStateItemCollection.Deserialize(reader);
290             }
291             return collection;
292         } 
293 
294         /// <summary>
295         /// 序列化Session的數據
296         /// </summary>
297         /// <param name="items"></param>
298         /// <returns></returns>
299         public string Serialize(SessionStateItemCollection items)
300         {
301             MemoryStream ms = new MemoryStream();
302             BinaryWriter writer = new BinaryWriter(ms);
303             if (items != null)
304                 items.Serialize(writer);
305             writer.Close();
306             return System.Text.Encoding.ASCII.GetString(ms.ToArray());
307         }
308 
309         #endregion 
310 
311 
312         public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
313         {
314             return true;
315         }
316         public override void InitializeRequest(HttpContext context)
317         {
318         } 
319         public override void EndRequest(HttpContext context)
320         {
321         } 
322         public override void Dispose()
323         {
324         }
325 
326     }

最后配置web.config system.web/sessionState

1  <sessionState mode="Custom"  customProvider="mySessionProvider">
2       <providers>
3         <add name="mySessionProvider"  type="CustomSessionState.MyCustomSessionStateStoreProvider,CustomSessionState"/>
4       </providers>
5     </sessionState>

 

Github地址

https://github.com/Emrys5/Asp.net-CustomSessionState

 

最后的最后,求推薦

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM