SessionSQLServerHandler.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. //
  2. // System.Web.Compilation.SessionStateItemCollection
  3. //
  4. // Authors:
  5. // Marek Habersack <[email protected]>
  6. //
  7. // (C) 2009 Novell, Inc (http://novell.com/)
  8. //
  9. // Code based on samples from MSDN
  10. //
  11. // Database schema found in ../ASPState.sql
  12. //
  13. // Permission is hereby granted, free of charge, to any person obtaining
  14. // a copy of this software and associated documentation files (the
  15. // "Software"), to deal in the Software without restriction, including
  16. // without limitation the rights to use, copy, modify, merge, publish,
  17. // distribute, sublicense, and/or sell copies of the Software, and to
  18. // permit persons to whom the Software is furnished to do so, subject to
  19. // the following conditions:
  20. //
  21. // The above copyright notice and this permission notice shall be
  22. // included in all copies or substantial portions of the Software.
  23. //
  24. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  25. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  26. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  27. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  28. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  29. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  30. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  31. //
  32. #if NET_2_0
  33. using System;
  34. using System.Collections.Generic;
  35. using System.Collections.Specialized;
  36. using System.Configuration.Provider;
  37. using System.Data;
  38. using System.Data.Common;
  39. using System.IO;
  40. using System.Reflection;
  41. using System.Web;
  42. using System.Web.Configuration;
  43. using System.Web.Hosting;
  44. namespace System.Web.SessionState
  45. {
  46. sealed class SessionSQLServerHandler : SessionStateStoreProviderBase
  47. {
  48. static readonly string defaultDbFactoryTypeName = "Mono.Data.Sqlite.SqliteFactory, Mono.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756";
  49. SessionStateSection sessionConfig = null;
  50. string connectionString;
  51. Type providerFactoryType;
  52. DbProviderFactory providerFactory;
  53. int sqlCommandTimeout;
  54. DbProviderFactory ProviderFactory {
  55. get {
  56. if (providerFactory == null) {
  57. try {
  58. providerFactory = Activator.CreateInstance (providerFactoryType) as DbProviderFactory;
  59. } catch (Exception ex) {
  60. throw new ProviderException ("Failure to create database factory instance.", ex);
  61. }
  62. }
  63. return providerFactory;
  64. }
  65. }
  66. public string ApplicationName {
  67. get; private set;
  68. }
  69. public override void Initialize (string name, NameValueCollection config)
  70. {
  71. if (config == null)
  72. throw new ArgumentNullException ("config");
  73. if (String.IsNullOrEmpty (name))
  74. name = "SessionSQLServerHandler";
  75. if (String.IsNullOrEmpty (config["description"])) {
  76. config.Remove ("description");
  77. config.Add ("description", "Mono SQL Session Store Provider");
  78. }
  79. ApplicationName = HostingEnvironment.ApplicationVirtualPath;
  80. base.Initialize(name, config);
  81. sessionConfig = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
  82. connectionString = sessionConfig.SqlConnectionString;
  83. string dbProviderName;
  84. if (String.IsNullOrEmpty (connectionString) || String.Compare (connectionString, SessionStateSection.DefaultSqlConnectionString, StringComparison.Ordinal) == 0) {
  85. connectionString = "Data Source=|DataDirectory|/ASPState.sqlite;Version=3";
  86. dbProviderName = defaultDbFactoryTypeName;
  87. } else {
  88. string[] parts = connectionString.Split (';');
  89. var newCS = new List <string> ();
  90. dbProviderName = null;
  91. bool allowDb = sessionConfig.AllowCustomSqlDatabase;
  92. foreach (string p in parts) {
  93. if (p.Trim ().Length == 0)
  94. continue;
  95. if (p.StartsWith ("DbProviderName", StringComparison.OrdinalIgnoreCase)) {
  96. int idx = p.IndexOf ('=');
  97. if (idx < 0)
  98. throw new ProviderException ("Invalid format for the 'DbProviderName' connection string parameter. Expected 'DbProviderName = value'.");
  99. dbProviderName = p.Substring (idx + 1);
  100. continue;
  101. }
  102. if (!allowDb) {
  103. string tmp = p.Trim ();
  104. if (tmp.StartsWith ("database", StringComparison.OrdinalIgnoreCase) ||
  105. tmp.StartsWith ("initial catalog", StringComparison.OrdinalIgnoreCase))
  106. throw new ProviderException ("Specifying a custom database is not allowed. Set the allowCustomSqlDatabase attribute of the <system.web/sessionState> section to 'true' in order to use a custom database name.");
  107. }
  108. newCS.Add (p);
  109. }
  110. connectionString = String.Join (";", newCS.ToArray ());
  111. if (String.IsNullOrEmpty (dbProviderName))
  112. dbProviderName = defaultDbFactoryTypeName;
  113. }
  114. Exception typeException = null;
  115. try {
  116. providerFactoryType = Type.GetType (dbProviderName, true);
  117. } catch (Exception ex) {
  118. typeException = ex;
  119. providerFactoryType = null;
  120. }
  121. if (providerFactoryType == null)
  122. throw new ProviderException ("Unable to find database provider factory type.", typeException);
  123. sqlCommandTimeout = (int)sessionConfig.SqlCommandTimeout.TotalSeconds;
  124. }
  125. public override void Dispose ()
  126. {
  127. }
  128. public override bool SetItemExpireCallback (SessionStateItemExpireCallback expireCallback)
  129. {
  130. return false;
  131. }
  132. public override void SetAndReleaseItemExclusive (HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
  133. {
  134. DbCommand cmd;
  135. DbCommand deleteCmd = null;
  136. DbParameter param;
  137. string sessItems = Serialize((SessionStateItemCollection)item.Items);
  138. DbProviderFactory factory = ProviderFactory;
  139. string appName = ApplicationName;
  140. DbConnection conn = CreateConnection (factory);
  141. DateTime now = DateTime.Now;
  142. DbParameterCollection parameters;
  143. if (newItem) {
  144. deleteCmd = CreateCommand (factory, conn, "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Expires < @Expires");
  145. parameters = deleteCmd.Parameters;
  146. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  147. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
  148. parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now));
  149. cmd = CreateCommand (factory, conn, "INSERT INTO Sessions (SessionId, ApplicationName, Created, Expires, LockDate, LockId, Timeout, Locked, SessionItems, Flags) Values (@SessionId, @ApplicationName, @Created, @Expires, @LockDate, @LockId , @Timeout, @Locked, @SessionItems, @Flags)");
  150. parameters = cmd.Parameters;
  151. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  152. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
  153. parameters.Add (CreateParameter <DateTime> (factory, "@Created", now));
  154. parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)item.Timeout)));
  155. parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
  156. parameters.Add (CreateParameter <int> (factory, "@LockId", 0));
  157. parameters.Add (CreateParameter <int> (factory, "@Timeout", item.Timeout));
  158. parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
  159. parameters.Add (CreateParameter <string> (factory, "@SessionItems", sessItems));
  160. parameters.Add (CreateParameter <int> (factory, "@Flags", 0));
  161. } else {
  162. cmd = CreateCommand (factory, conn, "UPDATE Sessions SET Expires = @Expires, SessionItems = @SessionItems, Locked = @Locked WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
  163. parameters = cmd.Parameters;
  164. parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)item.Timeout)));
  165. parameters.Add (CreateParameter <string> (factory, "@SessionItems", sessItems));
  166. parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
  167. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  168. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
  169. parameters.Add (CreateParameter <int> (factory, "@Lockid", (int)lockId));
  170. }
  171. try
  172. {
  173. conn.Open();
  174. if (deleteCmd != null)
  175. deleteCmd.ExecuteNonQuery();
  176. cmd.ExecuteNonQuery();
  177. } catch (Exception ex) {
  178. throw new ProviderException ("Failure storing session item in database.", ex);
  179. } finally {
  180. conn.Close();
  181. }
  182. }
  183. public override SessionStateStoreData GetItem (HttpContext context, string id, out bool locked, out TimeSpan lockAge,
  184. out object lockId, out SessionStateActions actionFlags)
  185. {
  186. return GetSessionStoreItem (false, context, id, out locked, out lockAge, out lockId, out actionFlags);
  187. }
  188. public override SessionStateStoreData GetItemExclusive (HttpContext context, string id, out bool locked,out TimeSpan lockAge,
  189. out object lockId, out SessionStateActions actionFlags)
  190. {
  191. return GetSessionStoreItem (true, context, id, out locked, out lockAge, out lockId, out actionFlags);
  192. }
  193. private SessionStateStoreData GetSessionStoreItem (bool lockRecord, HttpContext context, string id, out bool locked,
  194. out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
  195. {
  196. SessionStateStoreData item = null;
  197. lockAge = TimeSpan.Zero;
  198. lockId = null;
  199. locked = false;
  200. actionFlags = 0;
  201. DbProviderFactory factory = ProviderFactory;
  202. DbConnection conn = CreateConnection (factory);
  203. string appName = ApplicationName;
  204. DbCommand cmd = null;
  205. DbDataReader reader = null;
  206. DbParameter param;
  207. DbParameterCollection parameters;
  208. DateTime expires;
  209. string serializedItems = String.Empty;
  210. bool foundRecord = false;
  211. bool deleteData = false;
  212. int timeout = 0;
  213. DateTime now = DateTime.Now;
  214. try {
  215. conn.Open();
  216. if (lockRecord) {
  217. cmd = CreateCommand (factory, conn, "UPDATE Sessions SET Locked = @Locked, LockDate = @LockDate WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Expires > @Expires");
  218. parameters = cmd.Parameters;
  219. parameters.Add (CreateParameter <bool> (factory, "@Locked", true));
  220. parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
  221. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  222. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
  223. parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now));
  224. if (cmd.ExecuteNonQuery() == 0)
  225. locked = true;
  226. else
  227. locked = false;
  228. }
  229. cmd = CreateCommand (factory, conn, "SELECT Expires, SessionItems, LockId, LockDate, Flags, Timeout FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
  230. parameters = cmd.Parameters;
  231. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  232. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
  233. reader = cmd.ExecuteReader (CommandBehavior.SingleRow);
  234. while (reader.Read()) {
  235. expires = reader.GetDateTime (reader.GetOrdinal ("Expires"));
  236. if (expires < now) {
  237. locked = false;
  238. deleteData = true;
  239. } else
  240. foundRecord = true;
  241. serializedItems = reader.GetString (reader.GetOrdinal ("SessionItems"));
  242. lockId = reader.GetInt32 (reader.GetOrdinal ("LockId"));
  243. lockAge = now.Subtract (reader.GetDateTime (reader.GetOrdinal ("LockDate")));
  244. actionFlags = (SessionStateActions) reader.GetInt32 (reader.GetOrdinal ("Flags"));
  245. timeout = reader.GetInt32 (reader.GetOrdinal ("Timeout"));
  246. }
  247. reader.Close();
  248. if (deleteData) {
  249. cmd = CreateCommand (factory, conn, "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
  250. parameters = cmd.Parameters;
  251. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  252. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
  253. cmd.ExecuteNonQuery();
  254. }
  255. if (!foundRecord)
  256. locked = false;
  257. if (foundRecord && !locked) {
  258. lockId = (int)lockId + 1;
  259. cmd = CreateCommand (factory, conn, "UPDATE Sessions SET LockId = @LockId, Flags = 0 WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
  260. parameters = cmd.Parameters;
  261. parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
  262. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  263. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
  264. cmd.ExecuteNonQuery();
  265. if (actionFlags == SessionStateActions.InitializeItem)
  266. item = CreateNewStoreData (context, (int)sessionConfig.Timeout.TotalMinutes);
  267. else
  268. item = Deserialize (context, serializedItems, timeout);
  269. }
  270. } catch (Exception ex) {
  271. throw new ProviderException ("Unable to retrieve session item from database.", ex);
  272. } finally {
  273. if (reader != null)
  274. reader.Close ();
  275. conn.Close();
  276. }
  277. return item;
  278. }
  279. string Serialize (SessionStateItemCollection items)
  280. {
  281. var ms = new MemoryStream ();
  282. var writer = new BinaryWriter (ms);
  283. if (items != null)
  284. items.Serialize (writer);
  285. writer.Close();
  286. return Convert.ToBase64String (ms.ToArray ());
  287. }
  288. SessionStateStoreData Deserialize (HttpContext context, string serializedItems, int timeout)
  289. {
  290. var ms = new MemoryStream (Convert.FromBase64String (serializedItems));
  291. var sessionItems = new SessionStateItemCollection ();
  292. if (ms.Length > 0) {
  293. var reader = new BinaryReader(ms);
  294. sessionItems = SessionStateItemCollection.Deserialize (reader);
  295. }
  296. return new SessionStateStoreData (sessionItems, SessionStateUtility.GetSessionStaticObjects (context), timeout);
  297. }
  298. public override void ReleaseItemExclusive (HttpContext context, string id, object lockId)
  299. {
  300. DbProviderFactory factory = ProviderFactory;
  301. DbConnection conn = CreateConnection (factory);
  302. DbCommand cmd = CreateCommand (factory, conn,
  303. "UPDATE Sessions SET Locked = 0, Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
  304. DbParameterCollection parameters = cmd.Parameters;
  305. parameters.Add (CreateParameter <DateTime> (factory, "@Expires", DateTime.Now.AddMinutes(sessionConfig.Timeout.TotalMinutes)));
  306. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  307. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
  308. parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
  309. try {
  310. conn.Open ();
  311. cmd.ExecuteNonQuery ();
  312. } catch (Exception ex) {
  313. throw new ProviderException ("Error releasing item in database.", ex);
  314. } finally {
  315. conn.Close();
  316. }
  317. }
  318. public override void RemoveItem (HttpContext context, string id, object lockId, SessionStateStoreData item)
  319. {
  320. DbProviderFactory factory = ProviderFactory;
  321. DbConnection conn = CreateConnection (factory);
  322. DbCommand cmd = CreateCommand (factory, conn,
  323. "DELETE * FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
  324. DbParameterCollection parameters = cmd.Parameters;
  325. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  326. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
  327. parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
  328. try {
  329. conn.Open ();
  330. cmd.ExecuteNonQuery ();
  331. } catch (Exception ex) {
  332. throw new ProviderException ("Error removing item from database.", ex);
  333. } finally {
  334. conn.Close();
  335. }
  336. }
  337. public override void CreateUninitializedItem (HttpContext context, string id, int timeout)
  338. {
  339. DbProviderFactory factory = ProviderFactory;
  340. DbConnection conn = CreateConnection (factory);
  341. DbCommand cmd = CreateCommand (factory, conn,
  342. "INSERT INTO Sessions (SessionId, ApplicationName, Created, Expires, LockDate, LockId, Timeout, Locked, SessionItems, Flags) Values (@SessionId, @ApplicationName, @Created, @Expires, @LockDate, @LockId , @Timeout, @Locked, @SessionItems, @Flags)");
  343. DateTime now = DateTime.Now;
  344. DbParameterCollection parameters = cmd.Parameters;
  345. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  346. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
  347. parameters.Add (CreateParameter <DateTime> (factory, "@Created", now));
  348. parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)timeout)));
  349. parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
  350. parameters.Add (CreateParameter <int> (factory, "@LockId", 0));
  351. parameters.Add (CreateParameter <int> (factory, "@Timeout", timeout));
  352. parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
  353. parameters.Add (CreateParameter <string> (factory, "@SessionItems", String.Empty));
  354. parameters.Add (CreateParameter <int> (factory, "@Flags", 1));
  355. try {
  356. conn.Open ();
  357. cmd.ExecuteNonQuery ();
  358. } catch (Exception ex) {
  359. throw new ProviderException ("Error creating uninitialized session item in the database.", ex);
  360. } finally {
  361. conn.Close();
  362. }
  363. }
  364. public override SessionStateStoreData CreateNewStoreData (HttpContext context, int timeout)
  365. {
  366. return new SessionStateStoreData (new SessionStateItemCollection (), SessionStateUtility.GetSessionStaticObjects (context), timeout);
  367. }
  368. public override void ResetItemTimeout (HttpContext context, string id)
  369. {
  370. DbProviderFactory factory = ProviderFactory;
  371. DbConnection conn = CreateConnection (factory);
  372. DbCommand cmd = CreateCommand (factory, conn,
  373. "UPDATE Sessions SET Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
  374. DbParameterCollection parameters = cmd.Parameters;
  375. parameters.Add (CreateParameter <DateTime> (factory, "@Expires", DateTime.Now.AddMinutes (sessionConfig.Timeout.TotalMinutes)));
  376. parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
  377. parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
  378. try {
  379. conn.Open ();
  380. cmd.ExecuteNonQuery ();
  381. } catch (Exception ex) {
  382. throw new ProviderException ("Error resetting session item timeout in the database.", ex);
  383. } finally {
  384. conn.Close();
  385. }
  386. }
  387. public override void InitializeRequest (HttpContext context)
  388. {
  389. }
  390. public override void EndRequest(HttpContext context)
  391. {
  392. }
  393. DbConnection CreateConnection (DbProviderFactory factory)
  394. {
  395. DbConnection conn = factory.CreateConnection ();
  396. conn.ConnectionString = connectionString;
  397. return conn;
  398. }
  399. DbCommand CreateCommand (DbProviderFactory factory, DbConnection conn, string commandText)
  400. {
  401. DbCommand cmd = factory.CreateCommand ();
  402. cmd.CommandTimeout = sqlCommandTimeout;
  403. cmd.Connection = conn;
  404. cmd.CommandText = commandText;
  405. return cmd;
  406. }
  407. DbParameter CreateParameter <ValueType> (DbProviderFactory factory, string name, ValueType value)
  408. {
  409. return CreateParameter <ValueType> (factory, name, value, -1);
  410. }
  411. DbParameter CreateParameter <ValueType> (DbProviderFactory factory, string name, ValueType value, int size)
  412. {
  413. DbParameter param = factory.CreateParameter ();
  414. param.ParameterName = name;
  415. Type vt = typeof (ValueType);
  416. if (vt == typeof (string))
  417. param.DbType = DbType.String;
  418. else if (vt == typeof (int))
  419. param.DbType = DbType.Int32;
  420. else if (vt == typeof (bool))
  421. param.DbType = DbType.Boolean;
  422. else if (vt == typeof (DateTime))
  423. param.DbType = DbType.DateTime;
  424. if (size > -1)
  425. param.Size = size;
  426. param.Value = value;
  427. return param;
  428. }
  429. }
  430. }
  431. #endif