2 // System.Web.Compilation.SessionStateItemCollection
5 // Marek Habersack <mhabersack@novell.com>
7 // (C) 2009-2010 Novell, Inc (http://novell.com/)
10 // Code based on samples from MSDN
12 // Database schema found in ../ASPState.sql
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections.Generic;
35 using System.Collections.Specialized;
36 using System.Configuration.Provider;
38 using System.Data.Common;
40 using System.IO.Compression;
41 using System.Reflection;
43 using System.Web.Configuration;
44 using System.Web.Hosting;
46 namespace System.Web.SessionState
48 sealed class SessionSQLServerHandler : SessionStateStoreProviderBase
50 static readonly string defaultDbFactoryTypeName = "Mono.Data.Sqlite.SqliteFactory, Mono.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756";
52 SessionStateSection sessionConfig;
53 string connectionString;
54 Type providerFactoryType;
55 DbProviderFactory providerFactory;
56 int sqlCommandTimeout;
58 DbProviderFactory ProviderFactory {
60 if (providerFactory == null) {
62 providerFactory = Activator.CreateInstance (providerFactoryType) as DbProviderFactory;
63 } catch (Exception ex) {
64 throw new ProviderException ("Failure to create database factory instance.", ex);
68 return providerFactory;
72 public string ApplicationName {
76 public override void Initialize (string name, NameValueCollection config)
79 throw new ArgumentNullException ("config");
81 if (String.IsNullOrEmpty (name))
82 name = "SessionSQLServerHandler";
84 if (String.IsNullOrEmpty (config["description"])) {
85 config.Remove ("description");
86 config.Add ("description", "Mono SQL Session Store Provider");
88 ApplicationName = HostingEnvironment.ApplicationVirtualPath;
90 base.Initialize(name, config);
91 sessionConfig = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
92 connectionString = sessionConfig.SqlConnectionString;
93 string dbProviderName;
95 if (String.IsNullOrEmpty (connectionString) || String.Compare (connectionString, SessionStateSection.DefaultSqlConnectionString, StringComparison.Ordinal) == 0) {
96 connectionString = "Data Source=|DataDirectory|/ASPState.sqlite;Version=3";
97 dbProviderName = defaultDbFactoryTypeName;
99 string[] parts = connectionString.Split (';');
100 var newCS = new List <string> ();
101 dbProviderName = null;
102 bool allowDb = sessionConfig.AllowCustomSqlDatabase;
104 foreach (string p in parts) {
105 if (p.Trim ().Length == 0)
108 if (p.StartsWith ("DbProviderName", StringComparison.OrdinalIgnoreCase)) {
109 int idx = p.IndexOf ('=');
111 throw new ProviderException ("Invalid format for the 'DbProviderName' connection string parameter. Expected 'DbProviderName = value'.");
113 dbProviderName = p.Substring (idx + 1);
118 string tmp = p.Trim ();
119 if (tmp.StartsWith ("database", StringComparison.OrdinalIgnoreCase) ||
120 tmp.StartsWith ("initial catalog", StringComparison.OrdinalIgnoreCase))
121 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.");
127 connectionString = String.Join (";", newCS.ToArray ());
128 if (String.IsNullOrEmpty (dbProviderName))
129 dbProviderName = defaultDbFactoryTypeName;
134 Exception typeException = null;
137 providerFactoryType = Type.GetType (dbProviderName, true);
138 } catch (Exception ex) {
140 providerFactoryType = null;
143 if (providerFactoryType == null)
144 throw new ProviderException ("Unable to find database provider factory type.", typeException);
146 sqlCommandTimeout = (int)sessionConfig.SqlCommandTimeout.TotalSeconds;
149 public override void Dispose ()
153 public override bool SetItemExpireCallback (SessionStateItemExpireCallback expireCallback)
158 public override void SetAndReleaseItemExclusive (HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
161 DbCommand deleteCmd = null;
162 string sessItems = Serialize((SessionStateItemCollection)item.Items);
163 DbProviderFactory factory = ProviderFactory;
164 string appName = ApplicationName;
165 DbConnection conn = CreateConnection (factory);
166 DateTime now = DateTime.Now;
167 DbParameterCollection parameters;
170 deleteCmd = CreateCommand (factory, conn, "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Expires < @Expires");
171 parameters = deleteCmd.Parameters;
173 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
174 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
175 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now));
177 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)");
178 parameters = cmd.Parameters;
180 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
181 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
182 parameters.Add (CreateParameter <DateTime> (factory, "@Created", now));
183 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)item.Timeout)));
184 parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
185 parameters.Add (CreateParameter <int> (factory, "@LockId", 0));
186 parameters.Add (CreateParameter <int> (factory, "@Timeout", item.Timeout));
187 parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
188 parameters.Add (CreateParameter <string> (factory, "@SessionItems", sessItems));
189 parameters.Add (CreateParameter <int> (factory, "@Flags", 0));
191 cmd = CreateCommand (factory, conn, "UPDATE Sessions SET Expires = @Expires, SessionItems = @SessionItems, Locked = @Locked WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
192 parameters = cmd.Parameters;
194 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)item.Timeout)));
195 parameters.Add (CreateParameter <string> (factory, "@SessionItems", sessItems));
196 parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
197 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
198 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
199 parameters.Add (CreateParameter <int> (factory, "@Lockid", (int)lockId));
205 if (deleteCmd != null)
206 deleteCmd.ExecuteNonQuery();
208 cmd.ExecuteNonQuery();
209 } catch (Exception ex) {
210 throw new ProviderException ("Failure storing session item in database.", ex);
216 public override SessionStateStoreData GetItem (HttpContext context, string id, out bool locked, out TimeSpan lockAge,
217 out object lockId, out SessionStateActions actionFlags)
219 return GetSessionStoreItem (false, context, id, out locked, out lockAge, out lockId, out actionFlags);
222 public override SessionStateStoreData GetItemExclusive (HttpContext context, string id, out bool locked,out TimeSpan lockAge,
223 out object lockId, out SessionStateActions actionFlags)
225 return GetSessionStoreItem (true, context, id, out locked, out lockAge, out lockId, out actionFlags);
228 private SessionStateStoreData GetSessionStoreItem (bool lockRecord, HttpContext context, string id, out bool locked,
229 out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
231 SessionStateStoreData item = null;
232 lockAge = TimeSpan.Zero;
237 DbProviderFactory factory = ProviderFactory;
238 DbConnection conn = CreateConnection (factory);
239 string appName = ApplicationName;
240 DbCommand cmd = null;
241 DbDataReader reader = null;
242 DbParameterCollection parameters;
244 string serializedItems = String.Empty;
245 bool foundRecord = false;
246 bool deleteData = false;
248 DateTime now = DateTime.Now;
253 cmd = CreateCommand (factory, conn, "UPDATE Sessions SET Locked = @Locked, LockDate = @LockDate WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Expires > @Expires");
254 parameters = cmd.Parameters;
256 parameters.Add (CreateParameter <bool> (factory, "@Locked", true));
257 parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
258 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
259 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
260 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now));
262 if (cmd.ExecuteNonQuery() == 0)
268 cmd = CreateCommand (factory, conn, "SELECT Expires, SessionItems, LockId, LockDate, Flags, Timeout FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
269 parameters = cmd.Parameters;
271 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
272 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
274 reader = cmd.ExecuteReader (CommandBehavior.SingleRow);
275 while (reader.Read()) {
276 expires = reader.GetDateTime (reader.GetOrdinal ("Expires"));
284 serializedItems = reader.GetString (reader.GetOrdinal ("SessionItems"));
285 lockId = reader.GetInt32 (reader.GetOrdinal ("LockId"));
286 lockAge = now.Subtract (reader.GetDateTime (reader.GetOrdinal ("LockDate")));
287 actionFlags = (SessionStateActions) reader.GetInt32 (reader.GetOrdinal ("Flags"));
288 timeout = reader.GetInt32 (reader.GetOrdinal ("Timeout"));
293 cmd = CreateCommand (factory, conn, "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
294 parameters = cmd.Parameters;
296 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
297 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
299 cmd.ExecuteNonQuery();
305 if (foundRecord && !locked) {
306 lockId = (int)lockId + 1;
308 cmd = CreateCommand (factory, conn, "UPDATE Sessions SET LockId = @LockId, Flags = 0 WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
309 parameters = cmd.Parameters;
311 parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
312 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
313 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
315 cmd.ExecuteNonQuery();
317 if (actionFlags == SessionStateActions.InitializeItem)
318 item = CreateNewStoreData (context, (int)sessionConfig.Timeout.TotalMinutes);
320 item = Deserialize (context, serializedItems, timeout);
322 } catch (Exception ex) {
323 throw new ProviderException ("Unable to retrieve session item from database.", ex);
334 string Serialize (SessionStateItemCollection items)
337 GZipStream gzip = null;
340 MemoryStream ms = null;
341 BinaryWriter writer = null;
344 ms = new MemoryStream ();
346 if (sessionConfig.CompressionEnabled)
347 output = gzip = new GZipStream (ms, CompressionMode.Compress, true);
351 writer = new BinaryWriter (output);
354 items.Serialize (writer);
360 return Convert.ToBase64String (ms.ToArray ());
369 ((IDisposable)writer).Dispose ();
376 SessionStateStoreData Deserialize (HttpContext context, string serializedItems, int timeout)
378 MemoryStream ms = null;
380 BinaryReader reader = null;
382 GZipStream gzip = null;
385 ms = new MemoryStream (Convert.FromBase64String (serializedItems));
386 var sessionItems = new SessionStateItemCollection ();
390 if (sessionConfig.CompressionEnabled)
391 input = gzip = new GZipStream (ms, CompressionMode.Decompress, true);
396 reader = new BinaryReader (input);
397 sessionItems = SessionStateItemCollection.Deserialize (reader);
405 return new SessionStateStoreData (sessionItems, SessionStateUtility.GetSessionStaticObjects (context), timeout);
414 ((IDisposable)reader).Dispose ();
421 public override void ReleaseItemExclusive (HttpContext context, string id, object lockId)
423 DbProviderFactory factory = ProviderFactory;
424 DbConnection conn = CreateConnection (factory);
425 DbCommand cmd = CreateCommand (factory, conn,
426 "UPDATE Sessions SET Locked = 0, Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
428 DbParameterCollection parameters = cmd.Parameters;
430 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", DateTime.Now.AddMinutes(sessionConfig.Timeout.TotalMinutes)));
431 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
432 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
433 parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
437 cmd.ExecuteNonQuery ();
438 } catch (Exception ex) {
439 throw new ProviderException ("Error releasing item in database.", ex);
445 public override void RemoveItem (HttpContext context, string id, object lockId, SessionStateStoreData item)
447 DbProviderFactory factory = ProviderFactory;
448 DbConnection conn = CreateConnection (factory);
449 DbCommand cmd = CreateCommand (factory, conn,
450 "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
452 DbParameterCollection parameters = cmd.Parameters;
453 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
454 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
455 parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
459 cmd.ExecuteNonQuery ();
460 } catch (Exception ex) {
461 throw new ProviderException ("Error removing item from database.", ex);
467 public override void CreateUninitializedItem (HttpContext context, string id, int timeout)
469 DbProviderFactory factory = ProviderFactory;
470 DbConnection conn = CreateConnection (factory);
471 DbCommand cmd = CreateCommand (factory, conn,
472 "INSERT INTO Sessions (SessionId, ApplicationName, Created, Expires, LockDate, LockId, Timeout, Locked, SessionItems, Flags) Values (@SessionId, @ApplicationName, @Created, @Expires, @LockDate, @LockId , @Timeout, @Locked, @SessionItems, @Flags)");
474 DateTime now = DateTime.Now;
475 DbParameterCollection parameters = cmd.Parameters;
476 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
477 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
478 parameters.Add (CreateParameter <DateTime> (factory, "@Created", now));
479 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)timeout)));
480 parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
481 parameters.Add (CreateParameter <int> (factory, "@LockId", 0));
482 parameters.Add (CreateParameter <int> (factory, "@Timeout", timeout));
483 parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
484 parameters.Add (CreateParameter <string> (factory, "@SessionItems", String.Empty));
485 parameters.Add (CreateParameter <int> (factory, "@Flags", 1));
489 cmd.ExecuteNonQuery ();
490 } catch (Exception ex) {
491 throw new ProviderException ("Error creating uninitialized session item in the database.", ex);
497 public override SessionStateStoreData CreateNewStoreData (HttpContext context, int timeout)
499 return new SessionStateStoreData (new SessionStateItemCollection (), SessionStateUtility.GetSessionStaticObjects (context), timeout);
502 public override void ResetItemTimeout (HttpContext context, string id)
504 DbProviderFactory factory = ProviderFactory;
505 DbConnection conn = CreateConnection (factory);
506 DbCommand cmd = CreateCommand (factory, conn,
507 "UPDATE Sessions SET Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
509 DbParameterCollection parameters = cmd.Parameters;
510 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", DateTime.Now.AddMinutes (sessionConfig.Timeout.TotalMinutes)));
511 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
512 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
516 cmd.ExecuteNonQuery ();
517 } catch (Exception ex) {
518 throw new ProviderException ("Error resetting session item timeout in the database.", ex);
524 public override void InitializeRequest (HttpContext context)
528 public override void EndRequest(HttpContext context)
532 DbConnection CreateConnection (DbProviderFactory factory)
534 DbConnection conn = factory.CreateConnection ();
535 conn.ConnectionString = connectionString;
540 DbCommand CreateCommand (DbProviderFactory factory, DbConnection conn, string commandText)
542 DbCommand cmd = factory.CreateCommand ();
543 cmd.CommandTimeout = sqlCommandTimeout;
544 cmd.Connection = conn;
545 cmd.CommandText = commandText;
550 DbParameter CreateParameter <ValueType> (DbProviderFactory factory, string name, ValueType value)
552 return CreateParameter <ValueType> (factory, name, value, -1);
555 DbParameter CreateParameter <ValueType> (DbProviderFactory factory, string name, ValueType value, int size)
557 DbParameter param = factory.CreateParameter ();
558 param.ParameterName = name;
559 Type vt = typeof (ValueType);
561 if (vt == typeof (string))
562 param.DbType = DbType.String;
563 else if (vt == typeof (int))
564 param.DbType = DbType.Int32;
565 else if (vt == typeof (bool))
566 param.DbType = DbType.Boolean;
567 else if (vt == typeof (DateTime))
568 param.DbType = DbType.DateTime;