會有這樣的題目主要是因為業主的要求(廢話)。明確的說是這樣的,同一時間同一帳號只能在一個地方登入,之後登入的可以把前面登入的剔除。這個機制其實之前我並沒有想到要怎麼解,或是說沒想到不用資料庫的方式要怎麼解…(超遜的啦!)。不過認真想過後才發現這個機制其實不會太難,或是說我的解法其實很簡單,而且應該也可以用在MVC架構裡。
研究方法#
ASP.NET提供Session讓我們可對工作狀態進行管理,可以讓我們跨越多個要求儲存與瀏覽器工作階段關連的訊息,Session提供Key-value pairs的方式來存取這些資料。雖然我們很常拿Session來儲存使用者的資訊,像是使用者的帳號或是一些其他資訊,不過Session是每個使用者各自有的,這裡面的資訊並不會共享,因此利用Session來達成需求顯然是不行。我們需要的應用系統的全域變數,或是資料結構來對整個系統的使用者進行管理。這樣的需求我想全域應用程式類別(預設檔名為Global.asax)就是我這邊提出的解答!
當ASP.NET應用程式收到第一個要求的時候會先使用ApplicationManager建立應用程式定義域(應用程式定義域可以隔離應用程式間的全域變數,而且能夠允許每個應用程式個別進行卸載。)。在所有核心物件初始化後會藉由建立System.Web.HttpApplication類別的實體來執行應用程式。若應用程式中有Global.asax(繼承自HttpApplication)則會改用衍生自HttpApplication的Global.asax來建立執行應用程式的實體。而當處理要求的時候則會執行下述事件:(注意!這邊僅列達成需求出最主要的兩個)
1
2
3
4
5
6
7
8
9
| ///當要求ASP.NET應用程式中的第一個資源(如:網頁)時呼叫,
///這個方法在應用程式生命週期中只會被呼叫一次。
///我們讓全域變數在這邊進行宣告。
Application_Start(object sender, EventArgs e) { }
///這個方法在MSDN裡面的解釋有點怪,不過在應用程式生命週期中該事件發生也是只有一次,
///就是當IIS重啟或是應用程式正常關閉時就會發起該事件。
///我們讓全域變數所用到的資源在這裡釋放。
Application_End(object sender, EventArgs e) { }
|
開始撰寫時我們要先準備一下這個管理使用者的容器類別:
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
| /// 系統中記錄目前線上全部使用者的識別號的容器。
public sealed class SystemUserPool
{
private static ConcurrentDictionary<string, string> _UserPool =
new ConcurrentDictionary<string, string>();
/// 記錄系統中新登入的使用者。
/// userNumber = 登入者的帳號。
/// userIp = 登入者的 IP 位址。
/// 使用者登入成功與否。
public Boolean AddUser(String userNumber, String userIp)
{
return _UserPool.TryAdd(userNumber, userIp);
}
/// 剔除系統中指定的使用者。
/// userNumber = 使用者的帳號。
/// 剔除成功與否。
public Boolean DeleteUser(String userNumber)
{
string str = String.Empty;
return _UserPool.TryRemove(userNumber, out str);
}
/// 列出目前系統中在線上的使用者。
/// 使用者列表。
public List<string> ListAllUser()
{
return _UserPool.Keys.ToList<string>();
}
/// 判斷使用者是否仍在線上。
/// userNumber = 欲查明的使用者。
/// 若在線上則為 true,反之則為 false。
public Boolean IsOnLine(String userNumber)
{
return _UserPool.Keys.Contains(userNumber);
}
/// 判斷使用者是否從同一個地方登入。
/// userNumber = 使用者帳號。
/// userIp = 使用者目前 IP 位址。
/// 若 userIp 與系統中使用者的 IP 位址一致表示從同一個地方登入。
public Boolean IsTheSameIP(String userNumber, String userIp)
{
String orignalIP = String.Empty;
if (_UserPool.TryGetValue(userNumber, out orignalIP))
{
if (orignalIP == userIp)
return true;
return false;
}
return false;//若沒能取得,表示使用者已經離線。
}
/// 更新使用者的 IP 位址。
/// userNumber = 使用者帳號。
/// userIP = 使用者新的 IP 位址。
public void UpdateUserIP(String userNumber, String userIP)
{
if (!IsOnLine(userNumber))
return;
_UserPool[userNumber] = userIP;
}
/// 把系統使用者儲蓄池清空。
public void ClearUserPool()
{
_UserPool.Clear();
}
}
|
在Application_Start中就這樣寫:
1
2
3
4
5
6
7
8
9
10
| void Application_Start(object sender, EventArgs e)
{
Application["UserPool"] = new SystemUserPool();
}
void Application_End(object sender, EventArgs e)
{
var pool = Application["UserPool"] as SystemUserPool;
if (pool != null)
pool.ClearUserPool();
}
|
以上就是我這次所提出來的解決方案!不過其實最常加在這邊的應該是記錄系統狀態的Log啦~只是Log並不在這次範圍內,以後有機會再說囉~
參考文獻#
HttpSessionState
ASP.NET應用程式生命週期 IIS 5.0/IIS6.0 IIS7.0
ApplicationManager
HttpApplication
ASP.NET生命週期概觀 –> 強烈推薦寫 ASP.NET Web Form的人可以去看看「網頁生命週期的其他考量」裡面的事件圖