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.
35 using System.Collections.Generic;
36 using System.Collections.Specialized;
37 using System.Configuration.Provider;
39 using System.Data.Common;
41 using System.IO.Compression;
42 using System.Reflection;
44 using System.Web.Configuration;
45 using System.Web.Hosting;
47 namespace System.Web.SessionState
49 sealed class SessionSQLServerHandler : SessionStateStoreProviderBase
51 static readonly string defaultDbFactoryTypeName = "Mono.Data.Sqlite.SqliteFactory, Mono.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756";
53 SessionStateSection sessionConfig;
54 string connectionString;
55 Type providerFactoryType;
56 DbProviderFactory providerFactory;
57 int sqlCommandTimeout;
59 DbProviderFactory ProviderFactory {
61 if (providerFactory == null) {
63 providerFactory = Activator.CreateInstance (providerFactoryType) as DbProviderFactory;
64 } catch (Exception ex) {
65 throw new ProviderException ("Failure to create database factory instance.", ex);
69 return providerFactory;
73 public string ApplicationName {
77 public override void Initialize (string name, NameValueCollection config)
80 throw new ArgumentNullException ("config");
82 if (String.IsNullOrEmpty (name))
83 name = "SessionSQLServerHandler";
85 if (String.IsNullOrEmpty (config["description"])) {
86 config.Remove ("description");
87 config.Add ("description", "Mono SQL Session Store Provider");
89 ApplicationName = HostingEnvironment.ApplicationVirtualPath;
91 base.Initialize(name, config);
92 sessionConfig = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
93 connectionString = sessionConfig.SqlConnectionString;
94 string dbProviderName;
96 if (String.IsNullOrEmpty (connectionString) || String.Compare (connectionString, SessionStateSection.DefaultSqlConnectionString, StringComparison.Ordinal) == 0) {
97 connectionString = "Data Source=|DataDirectory|/ASPState.sqlite;Version=3";
98 dbProviderName = defaultDbFactoryTypeName;
100 string[] parts = connectionString.Split (';');
101 var newCS = new List <string> ();
102 dbProviderName = null;
103 bool allowDb = sessionConfig.AllowCustomSqlDatabase;
105 foreach (string p in parts) {
106 if (p.Trim ().Length == 0)
109 if (p.StartsWith ("DbProviderName", StringComparison.OrdinalIgnoreCase)) {
110 int idx = p.IndexOf ('=');
112 throw new ProviderException ("Invalid format for the 'DbProviderName' connection string parameter. Expected 'DbProviderName = value'.");
114 dbProviderName = p.Substring (idx + 1);
119 string tmp = p.Trim ();
120 if (tmp.StartsWith ("database", StringComparison.OrdinalIgnoreCase) ||
121 tmp.StartsWith ("initial catalog", StringComparison.OrdinalIgnoreCase))
122 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.");
128 connectionString = String.Join (";", newCS.ToArray ());
129 if (String.IsNullOrEmpty (dbProviderName))
130 dbProviderName = defaultDbFactoryTypeName;
135 Exception typeException = null;
138 providerFactoryType = Type.GetType (dbProviderName, true);
139 } catch (Exception ex) {
141 providerFactoryType = null;
144 if (providerFactoryType == null)
145 throw new ProviderException ("Unable to find database provider factory type.", typeException);
147 sqlCommandTimeout = (int)sessionConfig.SqlCommandTimeout.TotalSeconds;
150 public override void Dispose ()
154 public override bool SetItemExpireCallback (SessionStateItemExpireCallback expireCallback)
159 public override void SetAndReleaseItemExclusive (HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
162 DbCommand deleteCmd = null;
163 string sessItems = Serialize((SessionStateItemCollection)item.Items);
164 DbProviderFactory factory = ProviderFactory;
165 string appName = ApplicationName;
166 DbConnection conn = CreateConnection (factory);
167 DateTime now = DateTime.Now;
168 DbParameterCollection parameters;
171 deleteCmd = CreateCommand (factory, conn, "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Expires < @Expires");
172 parameters = deleteCmd.Parameters;
174 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
175 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
176 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now));
178 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)");
179 parameters = cmd.Parameters;
181 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
182 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
183 parameters.Add (CreateParameter <DateTime> (factory, "@Created", now));
184 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)item.Timeout)));
185 parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
186 parameters.Add (CreateParameter <int> (factory, "@LockId", 0));
187 parameters.Add (CreateParameter <int> (factory, "@Timeout", item.Timeout));
188 parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
189 parameters.Add (CreateParameter <string> (factory, "@SessionItems", sessItems));
190 parameters.Add (CreateParameter <int> (factory, "@Flags", 0));
192 cmd = CreateCommand (factory, conn, "UPDATE Sessions SET Expires = @Expires, SessionItems = @SessionItems, Locked = @Locked WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
193 parameters = cmd.Parameters;
195 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)item.Timeout)));
196 parameters.Add (CreateParameter <string> (factory, "@SessionItems", sessItems));
197 parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
198 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
199 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
200 parameters.Add (CreateParameter <int> (factory, "@Lockid", (int)lockId));
206 if (deleteCmd != null)
207 deleteCmd.ExecuteNonQuery();
209 cmd.ExecuteNonQuery();
210 } catch (Exception ex) {
211 throw new ProviderException ("Failure storing session item in database.", ex);
217 public override SessionStateStoreData GetItem (HttpContext context, string id, out bool locked, out TimeSpan lockAge,
218 out object lockId, out SessionStateActions actionFlags)
220 return GetSessionStoreItem (false, context, id, out locked, out lockAge, out lockId, out actionFlags);
223 public override SessionStateStoreData GetItemExclusive (HttpContext context, string id, out bool locked,out TimeSpan lockAge,
224 out object lockId, out SessionStateActions actionFlags)
226 return GetSessionStoreItem (true, context, id, out locked, out lockAge, out lockId, out actionFlags);
229 private SessionStateStoreData GetSessionStoreItem (bool lockRecord, HttpContext context, string id, out bool locked,
230 out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
232 SessionStateStoreData item = null;
233 lockAge = TimeSpan.Zero;
238 DbProviderFactory factory = ProviderFactory;
239 DbConnection conn = CreateConnection (factory);
240 string appName = ApplicationName;
241 DbCommand cmd = null;
242 DbDataReader reader = null;
243 DbParameterCollection parameters;
245 string serializedItems = String.Empty;
246 bool foundRecord = false;
247 bool deleteData = false;
249 DateTime now = DateTime.Now;
254 cmd = CreateCommand (factory, conn, "UPDATE Sessions SET Locked = @Locked, LockDate = @LockDate WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND Expires > @Expires");
255 parameters = cmd.Parameters;
257 parameters.Add (CreateParameter <bool> (factory, "@Locked", true));
258 parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
259 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
260 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
261 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now));
263 if (cmd.ExecuteNonQuery() == 0)
269 cmd = CreateCommand (factory, conn, "SELECT Expires, SessionItems, LockId, LockDate, Flags, Timeout FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
270 parameters = cmd.Parameters;
272 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
273 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
275 reader = cmd.ExecuteReader (CommandBehavior.SingleRow);
276 while (reader.Read()) {
277 expires = reader.GetDateTime (reader.GetOrdinal ("Expires"));
285 serializedItems = reader.GetString (reader.GetOrdinal ("SessionItems"));
286 lockId = reader.GetInt32 (reader.GetOrdinal ("LockId"));
287 lockAge = now.Subtract (reader.GetDateTime (reader.GetOrdinal ("LockDate")));
288 actionFlags = (SessionStateActions) reader.GetInt32 (reader.GetOrdinal ("Flags"));
289 timeout = reader.GetInt32 (reader.GetOrdinal ("Timeout"));
294 cmd = CreateCommand (factory, conn, "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
295 parameters = cmd.Parameters;
297 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
298 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
300 cmd.ExecuteNonQuery();
306 if (foundRecord && !locked) {
307 lockId = (int)lockId + 1;
309 cmd = CreateCommand (factory, conn, "UPDATE Sessions SET LockId = @LockId, Flags = 0 WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
310 parameters = cmd.Parameters;
312 parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
313 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
314 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", appName, 255));
316 cmd.ExecuteNonQuery();
318 if (actionFlags == SessionStateActions.InitializeItem)
319 item = CreateNewStoreData (context, (int)sessionConfig.Timeout.TotalMinutes);
321 item = Deserialize (context, serializedItems, timeout);
323 } catch (Exception ex) {
324 throw new ProviderException ("Unable to retrieve session item from database.", ex);
335 string Serialize (SessionStateItemCollection items)
338 GZipStream gzip = null;
341 MemoryStream ms = null;
342 BinaryWriter writer = null;
345 ms = new MemoryStream ();
347 if (sessionConfig.CompressionEnabled)
348 output = gzip = new GZipStream (ms, CompressionMode.Compress, true);
352 writer = new BinaryWriter (output);
355 items.Serialize (writer);
361 return Convert.ToBase64String (ms.ToArray ());
370 ((IDisposable)writer).Dispose ();
377 SessionStateStoreData Deserialize (HttpContext context, string serializedItems, int timeout)
379 MemoryStream ms = null;
381 BinaryReader reader = null;
383 GZipStream gzip = null;
386 ms = new MemoryStream (Convert.FromBase64String (serializedItems));
387 var sessionItems = new SessionStateItemCollection ();
391 if (sessionConfig.CompressionEnabled)
392 input = gzip = new GZipStream (ms, CompressionMode.Decompress, true);
397 reader = new BinaryReader (input);
398 sessionItems = SessionStateItemCollection.Deserialize (reader);
406 return new SessionStateStoreData (sessionItems, SessionStateUtility.GetSessionStaticObjects (context), timeout);
415 ((IDisposable)reader).Dispose ();
422 public override void ReleaseItemExclusive (HttpContext context, string id, object lockId)
424 DbProviderFactory factory = ProviderFactory;
425 DbConnection conn = CreateConnection (factory);
426 DbCommand cmd = CreateCommand (factory, conn,
427 "UPDATE Sessions SET Locked = 0, Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
429 DbParameterCollection parameters = cmd.Parameters;
431 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", DateTime.Now.AddMinutes(sessionConfig.Timeout.TotalMinutes)));
432 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
433 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
434 parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
438 cmd.ExecuteNonQuery ();
439 } catch (Exception ex) {
440 throw new ProviderException ("Error releasing item in database.", ex);
446 public override void RemoveItem (HttpContext context, string id, object lockId, SessionStateStoreData item)
448 DbProviderFactory factory = ProviderFactory;
449 DbConnection conn = CreateConnection (factory);
450 DbCommand cmd = CreateCommand (factory, conn,
451 "DELETE FROM Sessions WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName AND LockId = @LockId");
453 DbParameterCollection parameters = cmd.Parameters;
454 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
455 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
456 parameters.Add (CreateParameter <int> (factory, "@LockId", (int)lockId));
460 cmd.ExecuteNonQuery ();
461 } catch (Exception ex) {
462 throw new ProviderException ("Error removing item from database.", ex);
468 public override void CreateUninitializedItem (HttpContext context, string id, int timeout)
470 DbProviderFactory factory = ProviderFactory;
471 DbConnection conn = CreateConnection (factory);
472 DbCommand cmd = CreateCommand (factory, conn,
473 "INSERT INTO Sessions (SessionId, ApplicationName, Created, Expires, LockDate, LockId, Timeout, Locked, SessionItems, Flags) Values (@SessionId, @ApplicationName, @Created, @Expires, @LockDate, @LockId , @Timeout, @Locked, @SessionItems, @Flags)");
475 DateTime now = DateTime.Now;
476 DbParameterCollection parameters = cmd.Parameters;
477 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
478 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
479 parameters.Add (CreateParameter <DateTime> (factory, "@Created", now));
480 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", now.AddMinutes ((double)timeout)));
481 parameters.Add (CreateParameter <DateTime> (factory, "@LockDate", now));
482 parameters.Add (CreateParameter <int> (factory, "@LockId", 0));
483 parameters.Add (CreateParameter <int> (factory, "@Timeout", timeout));
484 parameters.Add (CreateParameter <bool> (factory, "@Locked", false));
485 parameters.Add (CreateParameter <string> (factory, "@SessionItems", String.Empty));
486 parameters.Add (CreateParameter <int> (factory, "@Flags", 1));
490 cmd.ExecuteNonQuery ();
491 } catch (Exception ex) {
492 throw new ProviderException ("Error creating uninitialized session item in the database.", ex);
498 public override SessionStateStoreData CreateNewStoreData (HttpContext context, int timeout)
500 return new SessionStateStoreData (new SessionStateItemCollection (), SessionStateUtility.GetSessionStaticObjects (context), timeout);
503 public override void ResetItemTimeout (HttpContext context, string id)
505 DbProviderFactory factory = ProviderFactory;
506 DbConnection conn = CreateConnection (factory);
507 DbCommand cmd = CreateCommand (factory, conn,
508 "UPDATE Sessions SET Expires = @Expires WHERE SessionId = @SessionId AND ApplicationName = @ApplicationName");
510 DbParameterCollection parameters = cmd.Parameters;
511 parameters.Add (CreateParameter <DateTime> (factory, "@Expires", DateTime.Now.AddMinutes (sessionConfig.Timeout.TotalMinutes)));
512 parameters.Add (CreateParameter <string> (factory, "@SessionId", id, 80));
513 parameters.Add (CreateParameter <string> (factory, "@ApplicationName", ApplicationName, 255));
517 cmd.ExecuteNonQuery ();
518 } catch (Exception ex) {
519 throw new ProviderException ("Error resetting session item timeout in the database.", ex);
525 public override void InitializeRequest (HttpContext context)
529 public override void EndRequest(HttpContext context)
533 DbConnection CreateConnection (DbProviderFactory factory)
535 DbConnection conn = factory.CreateConnection ();
536 conn.ConnectionString = connectionString;
541 DbCommand CreateCommand (DbProviderFactory factory, DbConnection conn, string commandText)
543 DbCommand cmd = factory.CreateCommand ();
544 cmd.CommandTimeout = sqlCommandTimeout;
545 cmd.Connection = conn;
546 cmd.CommandText = commandText;
551 DbParameter CreateParameter <ValueType> (DbProviderFactory factory, string name, ValueType value)
553 return CreateParameter <ValueType> (factory, name, value, -1);
556 DbParameter CreateParameter <ValueType> (DbProviderFactory factory, string name, ValueType value, int size)
558 DbParameter param = factory.CreateParameter ();
559 param.ParameterName = name;
560 Type vt = typeof (ValueType);
562 if (vt == typeof (string))
563 param.DbType = DbType.String;
564 else if (vt == typeof (int))
565 param.DbType = DbType.Int32;
566 else if (vt == typeof (bool))
567 param.DbType = DbType.Boolean;
568 else if (vt == typeof (DateTime))
569 param.DbType = DbType.DateTime;