2 // System.Web.UI.WebControls.SqlProfileProvider.cs
5 // Chris Toshok (toshok@ximian.com)
6 // Vladimir Krasnov (vladimirk@mainsoft.com)
8 // (C) 2006 Novell, Inc (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Data.Common;
33 using System.Collections;
34 using System.Globalization;
35 using System.Configuration;
36 using System.Configuration.Provider;
37 using System.Web.Configuration;
38 using System.Collections.Specialized;
41 using System.Web.Security;
42 using System.Web.Util;
44 namespace System.Web.Profile
46 public class SqlProfileProvider : ProfileProvider
48 ConnectionStringSettings connectionString;
49 DbProviderFactory factory;
51 string applicationName;
52 bool schemaIsOk = false;
54 public override int DeleteInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
56 using (DbConnection connection = CreateConnection ()) {
57 DbCommand command = factory.CreateCommand ();
58 command.Connection = connection;
59 command.CommandType = CommandType.StoredProcedure;
60 command.CommandText = @"aspnet_Profile_DeleteInactiveProfiles";
62 AddParameter (command, "ApplicationName", ApplicationName);
63 AddParameter (command, "ProfileAuthOptions", authenticationOption);
64 AddParameter (command, "InactiveSinceDate", userInactiveSinceDate);
65 DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
67 command.ExecuteNonQuery ();
68 int retVal = GetReturnValue (returnValue);
73 public override int DeleteProfiles (ProfileInfoCollection profiles)
76 throw new ArgumentNullException ("prfoles");
77 if (profiles.Count == 0)
78 throw new ArgumentException ("prfoles");
80 string [] usernames = new string [profiles.Count];
83 foreach (ProfileInfo pi in profiles) {
84 if (pi.UserName == null)
85 throw new ArgumentNullException ("element in profiles collection is null");
87 if (pi.UserName.Length == 0 || pi.UserName.Length > 256 || pi.UserName.IndexOf (',') != -1)
88 throw new ArgumentException ("element in profiles collection in illegal format");
90 usernames [i++] = pi.UserName;
93 return DeleteProfilesInternal (usernames);
96 public override int DeleteProfiles (string [] usernames)
98 if (usernames == null)
99 throw new ArgumentNullException ("usernames");
101 Hashtable users = new Hashtable ();
102 foreach (string username in usernames) {
103 if (username == null)
104 throw new ArgumentNullException ("element in usernames array is null");
106 if (username.Length == 0 || username.Length > 256 || username.IndexOf (',') != -1)
107 throw new ArgumentException ("element in usernames array in illegal format");
109 if (users.ContainsKey(username))
110 throw new ArgumentException ("duplicate element in usernames array");
112 users.Add (username, username);
115 return DeleteProfilesInternal (usernames);
118 int DeleteProfilesInternal (string [] usernames)
120 using (DbConnection connection = CreateConnection ()) {
121 DbCommand command = factory.CreateCommand ();
122 command.Connection = connection;
123 command.CommandType = CommandType.StoredProcedure;
124 command.CommandText = @"aspnet_Profile_DeleteProfiles";
126 AddParameter (command, "ApplicationName", ApplicationName);
127 AddParameter (command, "UserNames", string.Join (",", usernames));
128 DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
130 command.ExecuteNonQuery ();
131 int retVal = GetReturnValue (returnValue);
136 public override ProfileInfoCollection FindInactiveProfilesByUserName (ProfileAuthenticationOption authenticationOption,
137 string usernameToMatch,
138 DateTime userInactiveSinceDate,
141 out int totalRecords)
143 CheckParam ("usernameToMatch", usernameToMatch, 256);
145 throw new ArgumentException("pageIndex is less than zero");
147 throw new ArgumentException ("pageIndex is less than one");
148 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
149 throw new ArgumentException ("pageIndex and pageSize are too large");
151 using (DbConnection connection = CreateConnection ()) {
152 DbCommand command = factory.CreateCommand ();
153 command.Connection = connection;
154 command.CommandType = CommandType.StoredProcedure;
155 command.CommandText = @"aspnet_Profile_GetProfiles";
157 AddParameter (command, "ApplicationName", ApplicationName);
158 AddParameter (command, "ProfileAuthOptions", authenticationOption);
159 AddParameter (command, "PageIndex", pageIndex);
160 AddParameter (command, "PageSize", pageSize);
161 AddParameter (command, "UserNameToMatch", usernameToMatch);
162 AddParameter (command, "InactiveSinceDate", userInactiveSinceDate);
164 using (DbDataReader reader = command.ExecuteReader ()) {
165 return BuildProfileInfoCollection (reader, out totalRecords);
170 public override ProfileInfoCollection FindProfilesByUserName (ProfileAuthenticationOption authenticationOption,
171 string usernameToMatch,
174 out int totalRecords)
176 CheckParam ("usernameToMatch", usernameToMatch, 256);
178 throw new ArgumentException ("pageIndex is less than zero");
180 throw new ArgumentException ("pageIndex is less than one");
181 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
182 throw new ArgumentException ("pageIndex and pageSize are too large");
184 using (DbConnection connection = CreateConnection ()) {
185 DbCommand command = factory.CreateCommand ();
186 command.Connection = connection;
187 command.CommandType = CommandType.StoredProcedure;
188 command.CommandText = @"aspnet_Profile_GetProfiles";
190 AddParameter (command, "ApplicationName", ApplicationName);
191 AddParameter (command, "ProfileAuthOptions", authenticationOption);
192 AddParameter (command, "PageIndex", pageIndex);
193 AddParameter (command, "PageSize", pageSize);
194 AddParameter (command, "UserNameToMatch", usernameToMatch);
195 AddParameter (command, "InactiveSinceDate", null);
197 using (DbDataReader reader = command.ExecuteReader ()) {
198 return BuildProfileInfoCollection (reader, out totalRecords);
203 public override ProfileInfoCollection GetAllInactiveProfiles (ProfileAuthenticationOption authenticationOption,
204 DateTime userInactiveSinceDate,
207 out int totalRecords)
210 throw new ArgumentException ("pageIndex is less than zero");
212 throw new ArgumentException ("pageIndex is less than one");
213 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
214 throw new ArgumentException ("pageIndex and pageSize are too large");
216 using (DbConnection connection = CreateConnection ()) {
217 DbCommand command = factory.CreateCommand ();
218 command.Connection = connection;
219 command.CommandType = CommandType.StoredProcedure;
220 command.CommandText = @"aspnet_Profile_GetProfiles";
222 AddParameter (command, "ApplicationName", ApplicationName);
223 AddParameter (command, "ProfileAuthOptions", authenticationOption);
224 AddParameter (command, "PageIndex", pageIndex);
225 AddParameter (command, "PageSize", pageSize);
226 AddParameter (command, "UserNameToMatch", null);
227 AddParameter (command, "InactiveSinceDate", null);
229 using (DbDataReader reader = command.ExecuteReader ()) {
230 return BuildProfileInfoCollection (reader, out totalRecords);
235 public override ProfileInfoCollection GetAllProfiles (ProfileAuthenticationOption authenticationOption,
238 out int totalRecords)
241 throw new ArgumentException ("pageIndex is less than zero");
243 throw new ArgumentException ("pageIndex is less than one");
244 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
245 throw new ArgumentException ("pageIndex and pageSize are too large");
247 using (DbConnection connection = CreateConnection ()) {
248 DbCommand command = factory.CreateCommand ();
249 command.Connection = connection;
250 command.CommandType = CommandType.StoredProcedure;
251 command.CommandText = @"aspnet_Profile_GetProfiles";
253 AddParameter (command, "ApplicationName", ApplicationName);
254 AddParameter (command, "ProfileAuthOptions", authenticationOption);
255 AddParameter (command, "PageIndex", pageIndex);
256 AddParameter (command, "PageSize", pageSize);
257 AddParameter (command, "UserNameToMatch", null);
258 AddParameter (command, "InactiveSinceDate", null);
260 using (DbDataReader reader = command.ExecuteReader ()) {
261 return BuildProfileInfoCollection (reader, out totalRecords);
266 public override int GetNumberOfInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
268 using (DbConnection connection = CreateConnection ()) {
270 DbCommand command = factory.CreateCommand ();
271 command.Connection = connection;
272 command.CommandType = CommandType.StoredProcedure;
273 command.CommandText = @"aspnet_Profile_GetNumberOfInactiveProfiles";
275 AddParameter (command, "ApplicationName", ApplicationName);
276 AddParameter (command, "ProfileAuthOptions", authenticationOption);
277 AddParameter (command, "InactiveSinceDate", userInactiveSinceDate);
280 using (DbDataReader reader = command.ExecuteReader ()) {
282 returnValue = reader.GetInt32 (0);
288 public override SettingsPropertyValueCollection GetPropertyValues (SettingsContext sc, SettingsPropertyCollection properties)
290 SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection ();
292 if (properties.Count == 0)
295 foreach (SettingsProperty property in properties) {
296 if (property.SerializeAs == SettingsSerializeAs.ProviderSpecific)
297 if (property.PropertyType.IsPrimitive || property.PropertyType == typeof (String))
298 property.SerializeAs = SettingsSerializeAs.String;
300 property.SerializeAs = SettingsSerializeAs.Xml;
302 settings.Add (new SettingsPropertyValue (property));
305 string username = (string) sc ["UserName"];
306 using (DbConnection connection = CreateConnection ()) {
307 DbCommand command = factory.CreateCommand ();
308 command.Connection = connection;
309 command.CommandType = CommandType.StoredProcedure;
310 command.CommandText = @"aspnet_Profile_GetProperties";
312 AddParameter (command, "ApplicationName", ApplicationName);
313 AddParameter (command, "UserName", username);
314 AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
316 using (DbDataReader reader = command.ExecuteReader ()) {
317 if (reader.Read ()) {
318 string allnames = reader.GetString (0);
319 string allvalues = reader.GetString (1);
320 int binaryLen = (int) reader.GetBytes (2, 0, null, 0, 0);
321 byte [] binaryvalues = new byte [binaryLen];
322 reader.GetBytes (2, 0, binaryvalues, 0, binaryLen);
324 DecodeProfileData (allnames, allvalues, binaryvalues, settings);
332 public override void SetPropertyValues (SettingsContext sc, SettingsPropertyValueCollection properties)
334 string username = (string) sc ["UserName"];
335 bool isAnonymous = !(bool) sc ["IsAuthenticated"];
337 string names = String.Empty;
338 string values = String.Empty;
341 EncodeProfileData (ref names, ref values, ref buf, properties, !isAnonymous);
343 using (DbConnection connection = CreateConnection ()) {
344 DbCommand command = factory.CreateCommand ();
345 command.Connection = connection;
346 command.CommandType = CommandType.StoredProcedure;
347 command.CommandText = @"aspnet_Profile_SetProperties";
349 AddParameter (command, "ApplicationName", ApplicationName);
350 AddParameter (command, "PropertyNames", names);
351 AddParameter (command, "PropertyValuesString", values);
352 AddParameter (command, "PropertyValuesBinary", buf);
353 AddParameter (command, "UserName", username);
354 AddParameter (command, "IsUserAnonymous", isAnonymous);
355 AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
358 AddParameter (command, null, ParameterDirection.ReturnValue, null);
360 command.ExecuteNonQuery ();
365 public override void Initialize (string name, NameValueCollection config)
368 throw new ArgumentNullException ("config");
370 base.Initialize (name, config);
372 applicationName = GetStringConfigValue (config, "applicationName", "/");
374 string connectionStringName = config ["connectionStringName"];
376 if (applicationName.Length > 256)
377 throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less.");
378 if (connectionStringName == null || connectionStringName.Length == 0)
379 throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length.");
381 connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];
382 factory = connectionString == null || String.IsNullOrEmpty (connectionString.ProviderName) ?
383 System.Data.SqlClient.SqlClientFactory.Instance :
384 ProvidersHelper.GetDbProviderFactory (connectionString.ProviderName);
387 public override string ApplicationName {
388 get { return applicationName; }
389 set { applicationName = value; }
392 DbConnection CreateConnection ()
394 if (!schemaIsOk && !(schemaIsOk = AspNetDBSchemaChecker.CheckMembershipSchemaVersion (factory, connectionString.ConnectionString, "profile", "1")))
395 throw new ProviderException ("Incorrect ASP.NET DB Schema Version.");
397 DbConnection connection = factory.CreateConnection ();
398 connection.ConnectionString = connectionString.ConnectionString;
404 DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
406 return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
409 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
411 DbParameter dbp = command.CreateParameter ();
412 dbp.ParameterName = parameterName;
413 dbp.Value = parameterValue;
414 dbp.Direction = direction;
415 command.Parameters.Add (dbp);
419 void CheckParam (string pName, string p, int length)
422 throw new ArgumentNullException (pName);
423 if (p.Length == 0 || p.Length > length || p.IndexOf (',') != -1)
424 throw new ArgumentException (String.Concat ("invalid format for ", pName));
427 static int GetReturnValue (DbParameter returnValue)
429 object value = returnValue.Value;
430 return value is int ? (int) value : -1;
433 ProfileInfo ReadProfileInfo (DbDataReader reader)
435 ProfileInfo pi = null;
437 string username = reader.GetString (0);
438 bool anonymous = reader.GetBoolean (1);
439 DateTime lastUpdate = reader.GetDateTime (2);
440 DateTime lastActivity = reader.GetDateTime (3);
441 int size = reader.GetInt32 (4);
443 pi = new ProfileInfo (username, anonymous, lastActivity, lastUpdate, size);
451 ProfileInfoCollection BuildProfileInfoCollection (DbDataReader reader, out int totalRecords)
453 ProfileInfoCollection pic = new ProfileInfoCollection ();
454 while (reader.Read ()) {
455 ProfileInfo pi = ReadProfileInfo (reader);
460 if (reader.NextResult ()) {
462 totalRecords = reader.GetInt32 (0);
467 string GetStringConfigValue (NameValueCollection config, string name, string def)
470 string val = config [name];
477 void DecodeProfileData (string allnames, string values, byte [] buf, SettingsPropertyValueCollection properties)
479 if (allnames == null || values == null || buf == null || properties == null)
482 string [] names = allnames.Split (':');
483 for (int i = 0; i < names.Length; i += 4) {
484 string name = names [i];
485 SettingsPropertyValue pp = properties [name];
490 int pos = Int32.Parse (names [i + 2], Helpers.InvariantCulture);
491 int len = Int32.Parse (names [i + 3], Helpers.InvariantCulture);
493 if (len == -1 && !pp.Property.PropertyType.IsValueType) {
494 pp.PropertyValue = null;
496 pp.Deserialized = true;
498 else if (names [i + 1] == "S" && pos >= 0 && len > 0 && values.Length >= pos + len) {
499 pp.SerializedValue = values.Substring (pos, len);
501 else if (names [i + 1] == "B" && pos >= 0 && len > 0 && buf.Length >= pos + len) {
502 byte [] buf2 = new byte [len];
503 Buffer.BlockCopy (buf, pos, buf2, 0, len);
504 pp.SerializedValue = buf2;
509 void EncodeProfileData (ref string allNames, ref string allValues, ref byte [] buf, SettingsPropertyValueCollection properties, bool userIsAuthenticated)
511 StringBuilder names = new StringBuilder ();
512 StringBuilder values = new StringBuilder ();
513 MemoryStream stream = new MemoryStream ();
516 foreach (SettingsPropertyValue pp in properties) {
517 if (!userIsAuthenticated && !(bool) pp.Property.Attributes ["AllowAnonymous"])
520 if (!pp.IsDirty && pp.UsingDefaultValue)
523 int len = 0, pos = 0;
524 string propValue = null;
526 if (pp.Deserialized && pp.PropertyValue == null)
529 object sVal = pp.SerializedValue;
533 else if (sVal is string) {
534 propValue = (string) sVal;
535 len = propValue.Length;
539 byte [] b2 = (byte []) sVal;
540 pos = (int) stream.Position;
541 stream.Write (b2, 0, b2.Length);
542 stream.Position = pos + b2.Length;
547 names.Append (pp.Name + ":" + ((propValue != null) ? "S" : "B") + ":" + pos.ToString (Helpers.InvariantCulture) + ":" + len.ToString (Helpers.InvariantCulture) + ":");
549 if (propValue != null)
550 values.Append (propValue);
552 buf = stream.ToArray ();
559 allNames = names.ToString ();
560 allValues = values.ToString ();