ASP.NET 預設 InProc Session 無法支援 Web Farm。本文實作基於 MongoDB 的自訂 Session State Provider,支援最多 26 台 MongoDB 節點 (A-Z 分區),透過 SessionID 前綴自動路由,完全相容標準 Session 物件操作介面。
<?xml version="1.0" encoding="utf-8"?>
<MongoDBSession>
<DbName>SessionDB</DbName>
<IdentityMap Identity="A">mongodb://localhost</IdentityMap>
<IdentityMap Identity="B">mongodb://localhost</IdentityMap>
<IdentityMap Identity="C">mongodb://localhost</IdentityMap>
<!-- ... 至 Z -->
<IdentityMap Identity="Z">mongodb://localhost</IdentityMap>
</MongoDBSession>
<system.web>
<sessionState mode="Custom"
customProvider="A2DSessionProvider"
sessionIDManagerType="A2DFramework.SessionService.MongoDBSessionIDManager">
<providers>
<add name="A2DSessionProvider" type="A2DFramework.SessionService.MongoDBSessionStateStore" />
</providers>
</sessionState>
</system.web>
public class MongoDBSessionIDManager : SessionIDManager
{
private Random rnd = new Random();
private object oLock = new object();
public override string CreateSessionID(HttpContext context)
{
int index;
lock (oLock) { index = rnd.Next(SessionConfiguration.SessionServerIdentities.Length); }
// 格式: "E.{base64_guid}" 前綴 E 表示對應 IdentityMap E
return string.Format("{0}.{1}", SessionConfiguration.SessionServerIdentities[index], base.CreateSessionID(context));
}
public override string Encode(string id) { return DESEncryptor.Encode(id, SessionConfiguration.DESKey); }
public override string Decode(string id) { return DESEncryptor.Decode(id, SessionConfiguration.DESKey); }
public override bool Validate(string id)
{
string prefix, realId;
if (!Helper.ParseSessionID(id, out prefix, out realId)) return false;
return base.Validate(realId);
}
}
public sealed class MongoDBSessionStateStore : SessionStateStoreProviderBase
{
private SessionStateSection pConfig;
private string pApplicationName;
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
pApplicationName = HostingEnvironment.ApplicationVirtualPath;
var cfg = WebConfigurationManager.OpenWebConfiguration(pApplicationName);
pConfig = (SessionStateSection)cfg.GetSection("system.web/sessionState");
}
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
return new SessionStateStoreData(new SessionStateItemCollection(),
SessionStateUtility.GetSessionStaticObjects(context), timeout);
}
// 建立未初始化項目
public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
{
var session = new MongoDBSessionEntity { /* 初始化屬性 */ };
Helper.GetMongoDBCollection(id).Save(session);
}
// 讀取 (非獨佔)
public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actions);
}
// 讀取 (獨佔)
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actions);
}
// 釋放獨佔鎖定
public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
{
var collection = Helper.GetMongoDBCollection(id);
var query = Query.And(Query.EQ("LockId", int.Parse(lockId.ToString())), Query.EQ("_id", id), Query.EQ("ApplicationName", pApplicationName));
var update = Update.Set("Locked", false).Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));
collection.Update(query, update);
}
// 移除項目
public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
{
var collection = Helper.GetMongoDBCollection(id);
var query = Query.And(Query.EQ("LockId", int.Parse(lockId.ToString())), Query.EQ("_id", id), Query.EQ("ApplicationName", pApplicationName));
collection.Remove(query);
}
// 重設逾期時間
public override void ResetItemTimeout(HttpContext context, string id)
{
var collection = Helper.GetMongoDBCollection(id);
var query = Query.And(Query.EQ("_id", id), Query.EQ("ApplicationName", pApplicationName));
var update = Update.Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));
collection.Update(query, update);
}
// 設定並釋放獨佔
public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
{
var collection = Helper.GetMongoDBCollection(id);
if (newItem)
{
// 移除過期項目後插入新項目
}
else
{
// 更新現有項目
}
}
private SessionStateStoreData GetSessionStoreItem(bool lockRecord, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
// 完整的鎖定檢查、資料讀取、反序列化邏輯
}
}
public static MongoCollection GetMongoDBCollection(string sessionId)
{
IPartitionResolver resolver = new MongoDBSessionPartitionResolver();
string mongoDbConnectionString = resolver.ResolvePartition(sessionId); // 依前綴解析
var client = new MongoClient(mongoDbConnectionString);
var server = client.GetServer();
var db = server.GetDatabase(SessionConfiguration.MongoDBName);
if (!db.CollectionExists(SessionConfiguration.MongoDBCollectionName))
db.CreateCollection(SessionConfiguration.MongoDBCollectionName);
return db.GetCollection(SessionConfiguration.MongoDBCollectionName);
}
protected void btnTest_Click(object sender, EventArgs e)
{
Session["A"] = DateTime.Now;
Session["B"] = 1111111111111;
Session["C"] = "fffffffffffffff";
}
protected void btnGetSession_Click(object sender, EventArgs e)
{
Response.Write(Session["A"] + "
");
Response.Write(Session["B"] + "
");
Response.Write(Session["C"] + "
");
}
protected void btnAbandon_Click(object sender, EventArgs e)
{
Session.Abandon();
}


原始碼整合於 A2D Framework:sourceforge.net/projects/a2dframework/