diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/AspProviders.csproj b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/AspProviders.csproj
new file mode 100644
index 0000000..359ba3b
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/AspProviders.csproj
@@ -0,0 +1,81 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.30729
+ 2.0
+ {306D2F9E-D6D0-4D96-94F1-173C60A13875}
+ Library
+ Properties
+ Microsoft.Samples.ServiceHosting.AspProviders
+ AspProviders
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+ 3.5
+
+
+ 3.5
+
+
+ 3.5
+
+
+
+ 3.5
+
+
+
+
+ $(UtilityComputingSDKRoot)\ref\Microsoft.ServiceHosting.ServiceRuntime.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {C6F30C10-E1C2-4327-BB6B-3160B479CCA1}
+ StorageClient
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/BlobProvider.cs b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/BlobProvider.cs
new file mode 100644
index 0000000..75bc52a
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/BlobProvider.cs
@@ -0,0 +1,277 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Configuration;
+using Microsoft.ServiceHosting.ServiceRuntime;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+using Microsoft.Samples.ServiceHosting.StorageClient;
+
+
+namespace Microsoft.Samples.ServiceHosting.AspProviders
+{
+
+ public enum EventKind
+ {
+ Critical,
+ Error,
+ Warning,
+ Information,
+ Verbose
+ };
+
+ static class Log
+ {
+ internal static void WriteToLog(string logName, string fmt, params object[] args)
+ {
+ RoleManager.WriteToLog(logName, String.Format(CultureInfo.InvariantCulture, fmt, args));
+ }
+ internal static void Write(EventKind eventKind, string message, params object[] args)
+ {
+ if (RoleManager.IsRoleManagerRunning)
+ {
+ switch(eventKind)
+ {
+ case EventKind.Error:
+ WriteToLog("Error", message, args);
+ break;
+ case EventKind.Critical:
+ WriteToLog("Critical", message, args);
+ break;
+ case EventKind.Warning:
+ WriteToLog("Warning", message, args);
+ break;
+ case EventKind.Information:
+ WriteToLog("Information", message, args);
+ break;
+ case EventKind.Verbose:
+ WriteToLog("Verbose", message, args);
+ break;
+ }
+ }
+ else
+ {
+ switch (eventKind)
+ {
+ case EventKind.Error:
+ case EventKind.Critical:
+ Trace.TraceError(message, args);
+ break;
+ case EventKind.Warning:
+ Trace.TraceWarning(message, args);
+ break;
+ case EventKind.Information:
+ case EventKind.Verbose:
+ Trace.TraceInformation(message, args);
+ break;
+ }
+ }
+ }
+ }
+
+ internal class BlobProvider
+ {
+ private StorageAccountInfo _info;
+ private BlobContainer _container;
+ private string _containerName;
+ private object _lock = new object();
+
+ private static readonly TimeSpan _Timeout = TimeSpan.FromSeconds(30);
+ private static readonly RetryPolicy _RetryPolicy = RetryPolicies.RetryN(3, TimeSpan.FromSeconds(1));
+ private const string _PathSeparator = "/";
+
+
+ internal BlobProvider(StorageAccountInfo info, string containerName)
+ {
+ this._info = info;
+ this._containerName = containerName;
+ }
+
+ internal string ContainerUrl
+ {
+ get
+ {
+ return string.Join(_PathSeparator, new string[] { _info.BaseUri.AbsolutePath, _containerName });
+ }
+ }
+
+ internal bool GetBlobContentsWithoutInitialization(string blobName, Stream outputStream, out BlobProperties properties)
+ {
+ Debug.Assert(outputStream != null);
+
+ BlobContainer container = GetContainer();
+
+ try
+ {
+ properties = container.GetBlob(blobName, new BlobContents(outputStream), false);
+ Log.Write(EventKind.Information, "Getting contents of blob {0}", _info.BaseUri + _PathSeparator + _containerName + _PathSeparator + blobName);
+ return true;
+ }
+ catch (StorageClientException sc)
+ {
+ if (sc.ErrorCode == StorageErrorCode.ResourceNotFound || sc.ErrorCode == StorageErrorCode.BlobNotFound)
+ {
+ properties = null;
+ return false;
+ }
+ else
+ throw;
+ }
+ }
+
+ internal MemoryStream GetBlobContent(string blobName, out BlobProperties properties)
+ {
+ MemoryStream blobContent = new MemoryStream();
+ properties = GetBlobContent(blobName, blobContent);
+ blobContent.Seek(0, SeekOrigin.Begin);
+ return blobContent;
+ }
+
+
+ internal BlobProperties GetBlobContent(string blobName, Stream outputStream)
+ {
+ BlobProperties properties;
+ BlobContainer container = GetContainer();
+ try
+ {
+ properties = container.GetBlob(blobName, new BlobContents(outputStream), false);
+ Log.Write(EventKind.Information, "Getting contents of blob {0}", ContainerUrl + _PathSeparator + blobName);
+ return properties;
+ }
+ catch (StorageClientException sc)
+ {
+ Log.Write(EventKind.Error, "Error getting contents of blob {0}: {1}", ContainerUrl + _PathSeparator + blobName, sc.Message);
+ throw;
+ }
+ }
+
+ internal void UploadStream(string blobName, Stream output)
+ {
+ UploadStream(blobName, output, true);
+ }
+
+ internal bool UploadStream(string blobName, Stream output, bool overwrite)
+ {
+ BlobContainer container = GetContainer();
+ try
+ {
+ output.Position = 0; //Rewind to start
+ Log.Write(EventKind.Information, "Uploading contents of blob {0}", ContainerUrl + _PathSeparator + blobName);
+ BlobProperties properties = new BlobProperties(blobName);
+ return container.CreateBlob(properties, new BlobContents(output), overwrite);
+ }
+ catch (StorageException se)
+ {
+ Log.Write(EventKind.Error, "Error uploading blob {0}: {1}", ContainerUrl + _PathSeparator + blobName, se.Message);
+ throw;
+ }
+ }
+
+ internal bool DeleteBlob(string blobName)
+ {
+ BlobContainer container = GetContainer();
+ try
+ {
+ return container.DeleteBlob(blobName);
+ }
+ catch (StorageException se)
+ {
+ Log.Write(EventKind.Error, "Error deleting blob {0}: {1}", ContainerUrl + _PathSeparator + blobName, se.Message);
+ throw;
+ }
+ }
+
+ internal bool DeleteBlobsWithPrefix(string prefix)
+ {
+ bool ret = true;
+
+ IEnumerable e = ListBlobs(prefix);
+ if (e == null)
+ {
+ return true;
+ }
+ IEnumerator props = e.GetEnumerator();
+ if (props == null)
+ {
+ return true;
+ }
+ while (props.MoveNext())
+ {
+ if (props.Current != null)
+ {
+ if (!DeleteBlob(props.Current.Name))
+ {
+ // ignore this; it is possible that another thread could try to delete the blob
+ // at the same time
+ ret = false;
+ }
+ }
+ }
+ return ret;
+ }
+
+ public IEnumerable ListBlobs(string folder)
+ {
+ BlobContainer container = GetContainer();
+ try
+ {
+ return container.ListBlobs(folder, false).OfType();
+ }
+ catch (StorageException se)
+ {
+ Log.Write(EventKind.Error, "Error enumerating contents of folder {0} exists: {1}", ContainerUrl + _PathSeparator + folder, se.Message);
+ throw;
+ }
+ }
+
+ private BlobContainer GetContainer()
+ {
+ // we have to make sure that only one thread tries to create the container
+ lock (_lock)
+ {
+ if (_container != null)
+ {
+ return _container;
+ }
+ try
+ {
+ BlobContainer container = BlobStorage.Create(_info).GetBlobContainer(_containerName);
+ container.Timeout = _Timeout;
+ container.RetryPolicy = _RetryPolicy;
+ container.CreateContainer();
+ _container = container;
+ return _container;
+ }
+ catch (StorageException se)
+ {
+ Log.Write(EventKind.Error, "Error creating container {0}: {1}", ContainerUrl, se.Message);
+ throw;
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/Configuration.cs b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/Configuration.cs
new file mode 100644
index 0000000..70c7805
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/Configuration.cs
@@ -0,0 +1,309 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+using System.Configuration;
+using System.Collections.Specialized;
+using System.Runtime.InteropServices;
+using Microsoft.Samples.ServiceHosting.StorageClient;
+using Microsoft.ServiceHosting.ServiceRuntime;
+
+[assembly: CLSCompliant(true)]
+
+namespace Microsoft.Samples.ServiceHosting.AspProviders
+{
+ internal static class Configuration
+ {
+
+ internal const string DefaultMembershipTableNameConfigurationString = "DefaultMembershipTableName";
+ internal const string DefaultRoleTableNameConfigurationString = "DefaultRoleTableName";
+ internal const string DefaultSessionTableNameConfigurationString = "DefaultSessionTableName";
+ internal const string DefaultSessionContainerNameConfigurationString = "DefaultSessionContainerName";
+ internal const string DefaultProfileContainerNameConfigurationString = "DefaultProfileContainerName";
+ internal const string DefaultProviderApplicationNameConfigurationString = "DefaultProviderApplicationName";
+
+ internal const string DefaultMembershipTableName = "Membership";
+ internal const string DefaultRoleTableName = "Roles";
+ internal const string DefaultSessionTableName = "Sessions";
+ internal const string DefaultSessionContainerName = "sessionprovidercontainer";
+ internal const string DefaultProfileContainerName = "profileprovidercontainer";
+ internal const string DefaultProviderApplicationName = "appname";
+
+
+ internal static string GetConfigurationSetting(string configurationString, string defaultValue)
+ {
+ return GetConfigurationSetting(configurationString, defaultValue, false);
+ }
+
+ ///
+ /// Gets a configuration setting from application settings in the Web.config or App.config file.
+ /// When running in a hosted environment, configuration settings are read from the settings specified in
+ /// .cscfg files (i.e., the settings are read from the fabrics configuration system).
+ ///
+ internal static string GetConfigurationSetting(string configurationString, string defaultValue, bool throwIfNull)
+ {
+ if (string.IsNullOrEmpty(configurationString)) {
+ throw new ArgumentException("The parameter configurationString cannot be null or empty.");
+ }
+
+ string ret = null;
+
+ // first, try to read from appsettings
+ ret = TryGetAppSetting(configurationString);
+
+ // settings in the csc file overload settings in Web.config
+ if (RoleManager.IsRoleManagerRunning)
+ {
+ string cscRet = TryGetConfigurationSetting(configurationString);
+ if (!string.IsNullOrEmpty(cscRet))
+ {
+ ret = cscRet;
+ }
+
+ // if there is a csc config name in the app settings, this config name even overloads the
+ // setting we have right now
+ string refWebRet = TryGetAppSetting(StorageAccountInfo.CSConfigStringPrefix + configurationString);
+ if (!string.IsNullOrEmpty(refWebRet))
+ {
+ cscRet = TryGetConfigurationSetting(refWebRet);
+ if (!string.IsNullOrEmpty(cscRet))
+ {
+ ret = cscRet;
+ }
+ }
+ }
+
+ // if we could not retrieve any configuration string set return value to the default value
+ if (string.IsNullOrEmpty(ret) && defaultValue != null)
+ {
+ ret = defaultValue;
+ }
+
+ if (string.IsNullOrEmpty(ret) && throwIfNull)
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "Cannot find configuration string {0}.", configurationString));
+ }
+ return ret;
+ }
+
+ internal static string GetConfigurationSettingFromNameValueCollection(NameValueCollection config, string valueName)
+ {
+ if (config == null)
+ {
+ throw new ArgumentNullException("config");
+ }
+ if (valueName == null) {
+ throw new ArgumentNullException("valueName");
+ }
+
+ string sValue = config[valueName];
+
+ if (RoleManager.IsRoleManagerRunning)
+ {
+ // settings in the hosting configuration are stronger than settings in app config
+ string cscRet = TryGetConfigurationSetting(valueName);
+ if (!string.IsNullOrEmpty(cscRet))
+ {
+ sValue = cscRet;
+ }
+
+ // if there is a csc config name in the app settings, this config name even overloads the
+ // setting we have right now
+ string refWebRet = config[StorageAccountInfo.CSConfigStringPrefix + valueName];
+ if (!string.IsNullOrEmpty(refWebRet))
+ {
+ cscRet = TryGetConfigurationSetting(refWebRet);
+ if (!string.IsNullOrEmpty(cscRet))
+ {
+ sValue = cscRet;
+ }
+ }
+ }
+ return sValue;
+ }
+
+ internal static bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue)
+ {
+ string sValue = GetConfigurationSettingFromNameValueCollection(config, valueName);
+
+ if (string.IsNullOrEmpty(sValue))
+ {
+ return defaultValue;
+ }
+
+ bool result;
+ if (bool.TryParse(sValue, out result))
+ {
+ return result;
+ }
+ else
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The value must be boolean (true or false) for property '{0}'.", valueName));
+ }
+ }
+
+ internal static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed)
+ {
+ string sValue = GetConfigurationSettingFromNameValueCollection(config, valueName);
+
+ if (string.IsNullOrEmpty(sValue))
+ {
+ return defaultValue;
+ }
+
+ int iValue;
+ if (!Int32.TryParse(sValue, out iValue))
+ {
+ if (zeroAllowed)
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The value must be a non-negative 32-bit integer for property '{0}'.", valueName));
+ }
+
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The value must be a positive 32-bit integer for property '{0}'.", valueName));
+ }
+
+ if (zeroAllowed && iValue < 0)
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The value must be a non-negative 32-bit integer for property '{0}'.", valueName));
+ }
+
+ if (!zeroAllowed && iValue <= 0)
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The value must be a positive 32-bit integer for property '{0}'.", valueName));
+ }
+
+ if (maxValueAllowed > 0 && iValue > maxValueAllowed)
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The value '{0}' can not be greater than '{1}'.", valueName, maxValueAllowed.ToString(CultureInfo.InstalledUICulture)));
+ }
+
+ return iValue;
+ }
+
+ internal static string GetStringValue(NameValueCollection config, string valueName, string defaultValue, bool nullAllowed)
+ {
+ string sValue = GetConfigurationSettingFromNameValueCollection(config, valueName);
+
+ if (string.IsNullOrEmpty(sValue) && nullAllowed)
+ {
+ return null;
+ }
+ else if (string.IsNullOrEmpty(sValue) && defaultValue != null)
+ {
+ return defaultValue;
+ }
+ else if (string.IsNullOrEmpty(sValue))
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The parameter '{0}' must not be empty.", valueName));
+ }
+
+ return sValue;
+ }
+
+
+ internal static string GetStringValueWithGlobalDefault(NameValueCollection config, string valueName, string defaultConfigString, string defaultValue, bool nullAllowed)
+ {
+ string sValue = GetConfigurationSettingFromNameValueCollection(config, valueName);
+
+ if (string.IsNullOrEmpty(sValue))
+ {
+ sValue = GetConfigurationSetting(defaultConfigString, null);
+ }
+
+ if (string.IsNullOrEmpty(sValue) && nullAllowed)
+ {
+ return null;
+ }
+ else if (string.IsNullOrEmpty(sValue) && defaultValue != null)
+ {
+ return defaultValue;
+ }
+ else if (string.IsNullOrEmpty(sValue))
+ {
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InstalledUICulture, "The parameter '{0}' must not be empty.", valueName));
+ }
+
+ return sValue;
+ }
+
+ internal static string GetInitExceptionDescription(StorageAccountInfo table, StorageAccountInfo blob) {
+ StringBuilder builder = new StringBuilder();
+ builder.Append(GetInitExceptionDescription(table, "table storage configuration"));
+ builder.Append(GetInitExceptionDescription(blob, "blob storage configuration"));
+ return builder.ToString();
+ }
+
+ internal static string GetInitExceptionDescription(StorageAccountInfo info, string desc) {
+ StringBuilder builder = new StringBuilder();
+ builder.Append("The reason for this exception is typically that the endpoints are not correctly configured. " + Environment.NewLine);
+ if (info == null)
+ {
+ builder.Append("The current " + desc + " is null. Please specify a table endpoint!" + Environment.NewLine);
+ }
+ else
+ {
+ string baseUri = (info.BaseUri == null) ? "null" : info.BaseUri.ToString();
+ builder.Append("The current " + desc + " is: " + baseUri + ", usePathStyleUris is: " + info.UsePathStyleUris + Environment.NewLine);
+ builder.Append("Please also make sure that the account name and the shared key are specified correctly. This information cannot be shown here because of security reasons.");
+ }
+ return builder.ToString();
+ }
+
+ private static string TryGetConfigurationSetting(string configName)
+ {
+ string ret = null;
+ try
+ {
+ ret = RoleManager.GetConfigurationSetting(configName);
+ }
+ catch (RoleException)
+ {
+ return null;
+ }
+ return ret;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
+ Justification = "Make sure that no error condition prevents environment from reading service configuration.")]
+ private static string TryGetAppSetting(string configName)
+ {
+ string ret = null;
+ try
+ {
+ ret = ConfigurationSettings.AppSettings[configName];
+ }
+ // some exception happened when accessing the app settings section
+ // most likely this is because there is no app setting file
+ // this is not an error because configuration settings can also be located in the cscfg file, and explicitly
+ // all exceptions are captured here
+ catch (Exception)
+ {
+ return null;
+ }
+ return ret;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/SecUtil.cs b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/SecUtil.cs
new file mode 100644
index 0000000..cbc00c9
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/SecUtil.cs
@@ -0,0 +1,502 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Globalization;
+using System.Web.Hosting;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Data;
+using System.Data.SqlClient;
+using System.Data.SqlTypes;
+using System.Configuration.Provider;
+using System.Configuration;
+using System.Text.RegularExpressions;
+using System.Xml;
+using System.Net;
+using System.Diagnostics;
+using System.Security;
+using System.Text;
+using Microsoft.Samples.ServiceHosting.StorageClient;
+using System.Threading;
+
+
+namespace Microsoft.Samples.ServiceHosting.AspProviders
+{
+
+ internal static class SecUtility
+ {
+
+ internal const int Infinite = Int32.MaxValue;
+
+ internal static bool ValidateParameter(ref string param, bool checkForNull, bool checkIfEmpty, bool checkForCommas, int maxSize)
+ {
+ if (param == null)
+ {
+ return !checkForNull;
+ }
+
+ param = param.Trim();
+ if ((checkIfEmpty && param.Length < 1) ||
+ (maxSize > 0 && param.Length > maxSize) ||
+ (checkForCommas && param.Contains(",")))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static void CheckParameter(ref string param, bool checkForNull, bool checkIfEmpty, bool checkForCommas, int maxSize, string paramName)
+ {
+ if (param == null)
+ {
+ if (checkForNull)
+ {
+ throw new ArgumentNullException(paramName);
+ }
+
+ return;
+ }
+
+ param = param.Trim();
+ if (checkIfEmpty && param.Length < 1)
+ {
+ throw new ArgumentException(string.Format(CultureInfo.InstalledUICulture, "The parameter '{0}' must not be empty.", paramName), paramName);
+ }
+
+ if (maxSize > 0 && param.Length > maxSize)
+ {
+ throw new ArgumentException(string.Format(CultureInfo.InstalledUICulture, "The parameter '{0}' is too long: it must not exceed {1} chars in length.", paramName, maxSize.ToString(CultureInfo.InvariantCulture)), paramName);
+ }
+
+ if (checkForCommas && param.Contains(","))
+ {
+ throw new ArgumentException(string.Format(CultureInfo.InstalledUICulture, "The parameter '{0}' must not contain commas.", paramName), paramName);
+ }
+ }
+
+ internal static void CheckArrayParameter(ref string[] param, bool checkForNull, bool checkIfEmpty, bool checkForCommas, int maxSize, string paramName)
+ {
+ if (param == null)
+ {
+ throw new ArgumentNullException(paramName);
+ }
+
+ if (param.Length < 1)
+ {
+ throw new ArgumentException(string.Format(CultureInfo.InstalledUICulture, "The array parameter '{0}' should not be empty.", paramName), paramName);
+ }
+
+ Hashtable values = new Hashtable(param.Length);
+ for (int i = param.Length - 1; i >= 0; i--)
+ {
+ SecUtility.CheckParameter(ref param[i], checkForNull, checkIfEmpty, checkForCommas, maxSize,
+ paramName + "[ " + i.ToString(CultureInfo.InvariantCulture) + " ]");
+ if (values.Contains(param[i]))
+ {
+ throw new ArgumentException(string.Format(CultureInfo.InstalledUICulture, "The array '{0}' should not contain duplicate values.", paramName), paramName);
+ }
+ else
+ {
+ values.Add(param[i], param[i]);
+ }
+ }
+ }
+
+ internal static void SetUtcTime(DateTime value, out DateTime res)
+ {
+ res = TableStorageConstants.MinSupportedDateTime;
+ if ((value.Kind == DateTimeKind.Local && value.ToUniversalTime() < TableStorageConstants.MinSupportedDateTime) ||
+ value < TableStorageConstants.MinSupportedDateTime)
+ {
+ throw new ArgumentException("Invalid time value!");
+ }
+ if (value.Kind == DateTimeKind.Local)
+ {
+ res = value.ToUniversalTime();
+ }
+ else
+ {
+ res = value;
+ }
+ }
+
+ internal static bool IsValidTableName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ return false;
+ }
+ Regex reg = new Regex(StorageClient.StorageHttpConstants.RegularExpressionStrings.ValidTableNameRegex);
+ if (reg.IsMatch(name))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ internal static bool IsValidContainerName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ return false;
+ }
+ Regex reg = new Regex(StorageClient.StorageHttpConstants.RegularExpressionStrings.ValidContainerNameRegex);
+ if (reg.IsMatch(name))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // the table storage system currently does not support the StartsWith() operation in
+ // queries. As a result we transform s.StartsWith(substring) into s.CompareTo(substring) > 0 &&
+ // s.CompareTo(NextComparisonString(substring)) < 0
+ // we assume that comparison on the service side is as ordinal comparison
+ internal static string NextComparisonString(string s)
+ {
+ if (string.IsNullOrEmpty(s))
+ {
+ throw new ArgumentException("The string argument must not be null or empty!");
+ }
+ string ret;
+ char last = s[s.Length - 1];
+ if ((int)last + 1 > (int)char.MaxValue)
+ {
+ throw new ArgumentException("Cannot convert the string.");
+ }
+ // don't use "as" because we want to have an explicit exception here if something goes wrong
+ last = (char)((int)last + 1);
+ ret = s.Substring(0, s.Length - 1) + last;
+ return ret;
+ }
+
+ // we use a normal character as the separator because of string comparison operations
+ // these have to be valid characters
+ internal const char KeySeparator = 'a';
+ internal static readonly string KeySeparatorString = new string(KeySeparator, 1);
+ internal const char EscapeCharacter = 'b';
+ internal static readonly string EscapeCharacterString = new string(EscapeCharacter, 1);
+
+ // Some characters can cause problems when they are contained in columns
+ // that are included in queries. We are very defensive here and escape a wide range
+ // of characters for key columns (as the key columns are present in most queries)
+ internal static bool IsInvalidKeyCharacter(char c)
+ {
+ return ((c < 32)
+ || (c >= 127 && c < 160)
+ || (c == '#')
+ || (c == '&')
+ || (c == '+')
+ || (c == '/')
+ || (c == '?')
+ || (c == ':')
+ || (c == '%')
+ || (c == '\\')
+ );
+ }
+
+ internal static string CharToEscapeSequence(char c) {
+ string ret;
+ ret = EscapeCharacterString + string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c);
+ return ret;
+ }
+
+ internal static string Escape(string s)
+ {
+ if (string.IsNullOrEmpty(s)) {
+ return s;
+ }
+ StringBuilder ret = new StringBuilder();
+ foreach (char c in s)
+ {
+ if (c == EscapeCharacter || c == KeySeparator || IsInvalidKeyCharacter(c))
+ {
+ ret.Append(CharToEscapeSequence(c));
+ }
+ else
+ {
+ ret.Append(c);
+ }
+ }
+ return ret.ToString();
+ }
+
+ internal static string UnEscape(string s)
+ {
+ if (string.IsNullOrEmpty(s))
+ {
+ return s;
+ }
+ StringBuilder ret = new StringBuilder();
+ char c;
+ for (int i = 0; i < s.Length; i++)
+ {
+ c = s[i];
+ if (c == EscapeCharacter)
+ {
+ if (i + 2 >= s.Length)
+ {
+ throw new FormatException("The string " + s + " is not correctly escaped!");
+ }
+ int ascii = Convert.ToInt32(s.Substring(i + 1, 2), 16);
+ ret.Append((char)ascii);
+ i += 2;
+ }
+ else
+ {
+ ret.Append(c);
+ }
+ }
+ return ret.ToString();
+ }
+
+ internal static string CombineToKey(string s1, string s2)
+ {
+ return Escape(s1) + KeySeparator + Escape(s2);
+ }
+
+ internal static string EscapedFirst(string s)
+ {
+ return Escape(s) + KeySeparator;
+ }
+
+ internal static string GetFirstFromKey(string key) {
+ Debug.Assert(key.IndexOf(KeySeparator) != -1);
+ string first = key.Substring(0, key.IndexOf(KeySeparator));
+ return UnEscape(first);
+ }
+
+ internal static string GetSecondFromKey(string key)
+ {
+ Debug.Assert(key.IndexOf(KeySeparator) != -1);
+ string second = key.Substring(key.IndexOf(KeySeparator) + 1);
+ return UnEscape(second);
+ }
+
+ internal static void CheckAllowInsecureEndpoints(bool allowInsecureRemoteEndpoints, StorageAccountInfo info)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException("info");
+ }
+ if (allowInsecureRemoteEndpoints)
+ {
+ return;
+ }
+ if (info.BaseUri == null || string.IsNullOrEmpty(info.BaseUri.Scheme))
+ {
+ throw new SecurityException("allowInsecureRemoteEndpoints is set to false (default setting) but the endpoint URL seems to be empty or there is no URL scheme." +
+ "Please configure the provider to use an https enpoint for the storage endpoint or " +
+ "explicitly set the configuration option allowInsecureRemoteEndpoints to true.");
+ }
+ if (info.BaseUri.Scheme.ToUpper(CultureInfo.InvariantCulture) == Uri.UriSchemeHttps.ToUpper(CultureInfo.InvariantCulture))
+ {
+ return;
+ }
+ if (info.BaseUri.IsLoopback)
+ {
+ return;
+ }
+ throw new SecurityException("The provider is configured with allowInsecureRemoteEndpoints set to false (default setting) but the endpoint for " +
+ "the storage system does not seem to be an https or local endpoint. " +
+ "Please configure the provider to use an https enpoint for the storage endpoint or " +
+ "explicitly set the configuration option allowInsecureRemoteEndpoints to true.");
+ }
+ }
+
+ internal static class Constants
+ {
+ internal const int MaxTableUsernameLength = 256;
+ internal const int MaxTableApplicationNameLength = 256;
+ }
+
+
+ ///
+ /// This delegate defines the shape of a provider retry policy.
+ /// Provider retry policies are only used to retry when a row retrieved from a table
+ /// was changed by another entity before it could be saved to the data store.A retry policy will invoke the given
+ /// as many times as it wants to in the face of
+ /// retriable InvalidOperationExceptions.
+ ///
+ /// The action to retry
+ ///
+ public delegate void ProviderRetryPolicy(Action action);
+
+
+ ///
+ /// We are using this retry policies for only one purpose: the ASP providers often read data from the server, process it
+ /// locally and then write the result back to the server. The problem is that the row that has been read might have changed
+ /// between the read and write operation. This retry policy is used to retry the whole process in this case.
+ ///
+ ///
+ /// Provides definitions for some standard retry policies.
+ ///
+ public static class ProviderRetryPolicies
+ {
+
+ public static readonly TimeSpan StandardMinBackoff = TimeSpan.FromMilliseconds(100);
+ public static readonly TimeSpan StandardMaxBackoff = TimeSpan.FromSeconds(30);
+ private static readonly Random Random = new Random();
+
+ ///
+ /// Policy that does no retries i.e., it just invokes exactly once
+ ///
+ /// The action to retry
+ /// The return value of
+ internal static void NoRetry(Action action)
+ {
+ action();
+ }
+
+ ///
+ /// Policy that retries a specified number of times with an exponential backoff scheme
+ ///
+ /// The number of times to retry. Should be a non-negative number.
+ /// The multiplier in the exponential backoff scheme
+ ///
+ /// For this retry policy, the minimum amount of milliseconds between retries is given by the
+ /// StandardMinBackoff constant, and the maximum backoff is predefined by the StandardMaxBackoff constant.
+ /// Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.
+ public static ProviderRetryPolicy RetryN(int numberOfRetries, TimeSpan deltaBackoff)
+ {
+ return new ProviderRetryPolicy((Action action) =>
+ {
+ RetryNImpl(action, numberOfRetries, StandardMinBackoff, StandardMaxBackoff, deltaBackoff);
+ }
+ );
+ }
+
+ ///
+ /// Policy that retries a specified number of times with an exponential backoff scheme
+ ///
+ /// The number of times to retry. Should be a non-negative number
+ /// The multiplier in the exponential backoff scheme
+ /// The minimum backoff interval
+ /// The minimum backoff interval
+ ///
+ /// For this retry policy, the minimum amount of milliseconds between retries is given by the
+ /// minBackoff parameter, and the maximum backoff is predefined by the maxBackoff parameter.
+ /// Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.
+ public static ProviderRetryPolicy RetryN(int numberOfRetries, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff)
+ {
+ return new ProviderRetryPolicy((Action action) =>
+ {
+ RetryNImpl(action, numberOfRetries, minBackoff, maxBackoff, deltaBackoff);
+ }
+ );
+ }
+
+
+ private static void RetryNImpl(Action action, int numberOfRetries, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff)
+ {
+ int totalNumberOfRetries = numberOfRetries;
+ int backoff;
+
+ if (minBackoff > maxBackoff)
+ {
+ throw new ArgumentException("The minimum backoff must not be larger than the maximum backoff period.");
+ }
+ if (minBackoff.TotalMilliseconds < 0)
+ {
+ throw new ArgumentException("The minimum backoff period must not be negative.");
+ }
+
+ do
+ {
+ try
+ {
+ action();
+ break;
+ }
+ catch (InvalidOperationException e)
+ {
+ HttpStatusCode status;
+ // precondition failed is the status code returned by the server to indicate that the etag is wrong
+ if (TableStorageHelpers.EvaluateException(e, out status))
+ {
+ if (status == HttpStatusCode.PreconditionFailed)
+ {
+ if (numberOfRetries == 0)
+ {
+ throw;
+ }
+ backoff = CalculateCurrentBackoff(minBackoff, maxBackoff, deltaBackoff, totalNumberOfRetries - numberOfRetries);
+ Debug.Assert(backoff >= minBackoff.TotalMilliseconds);
+ Debug.Assert(backoff <= maxBackoff.TotalMilliseconds);
+ if (backoff > 0)
+ {
+ Thread.Sleep(backoff);
+ }
+ }
+ else
+ {
+ throw;
+ }
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+ while (numberOfRetries-- > 0);
+ }
+
+
+ private static int CalculateCurrentBackoff(TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, int curRetry)
+ {
+ int backoff;
+
+ if (curRetry > 31)
+ {
+ backoff = (int)maxBackoff.TotalMilliseconds;
+ }
+ try
+ {
+ backoff = Random.Next((1 << curRetry) + 1);
+ // Console.WriteLine("backoff:" + backoff);
+ // Console.WriteLine("index:" + ((1 << curRetry) + 1));
+ backoff *= (int)deltaBackoff.TotalMilliseconds;
+ backoff += (int)minBackoff.TotalMilliseconds;
+ }
+ catch (OverflowException)
+ {
+ backoff = (int)maxBackoff.TotalMilliseconds;
+ }
+ if (backoff > (int)maxBackoff.TotalMilliseconds)
+ {
+ backoff = (int)maxBackoff.TotalMilliseconds;
+ }
+ Debug.Assert(backoff >= (int)minBackoff.TotalMilliseconds);
+ // Console.WriteLine("real backoff:" + backoff);
+ return backoff;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageMembershipProvider.cs b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageMembershipProvider.cs
new file mode 100644
index 0000000..c7d02b8
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageMembershipProvider.cs
@@ -0,0 +1,2289 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Configuration.Provider;
+using System.Data.Services.Client;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Security;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Web;
+using System.Web.Security;
+using Microsoft.Samples.ServiceHosting.StorageClient;
+
+
+namespace Microsoft.Samples.ServiceHosting.AspProviders
+{
+ ///
+ /// This class allows DevtableGen to generate the correct table (named 'Membership')
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses",
+ Justification="Class is used by devtablegen to generate database for the development storage tool")]
+ internal class MembershipDataServiceContext : TableStorageDataServiceContext
+ {
+ public IQueryable Membership
+ {
+ get
+ {
+ return this.CreateQuery("Membership");
+ }
+ }
+ }
+
+ internal class MembershipRow : TableStorageEntity, IComparable
+ {
+ private string _applicationName;
+ private string _userName;
+ private string _password;
+ private string _passwordSalt;
+ private string _email;
+ private string _passwordAnswer;
+ private string _passwordQuestion;
+ private string _comment;
+ private string _profileBlobName;
+
+ private DateTime _createDate;
+ private DateTime _lastLoginDate;
+ private DateTime _lastPasswordChangedDate;
+ private DateTime _lastLockoutDate;
+ private DateTime _lastActivityDate;
+ private DateTime _failedPasswordAttemptWindowStart;
+ private DateTime _failedPasswordAnswerAttemptWindowStart;
+ private DateTime _profileLastUpdated;
+
+ // partition key is applicationName + userName
+ // rowKey is empty
+ public MembershipRow(string applicationName, string userName)
+ : base()
+ {
+ if (string.IsNullOrEmpty(applicationName)) {
+ throw new ProviderException("Partition key cannot be empty!");
+ }
+ if (string.IsNullOrEmpty(userName))
+ {
+ throw new ProviderException("RowKey cannot be empty!");
+ }
+
+ // applicationName + userName is partitionKey
+ // the reasoning behind this is that we want to strive for the best scalability possible
+ // chosing applicationName as the partition key and userName as row key would not give us that because
+ // it would mean that a site with millions of users had all users on a single partition
+ // having the applicationName and userName inside the partition key is important for queries as queries
+ // for users in a single application are the most frequent
+ // these queries are faster because application name and user name are part of the key
+ PartitionKey = SecUtility.CombineToKey(applicationName, userName);
+ RowKey = string.Empty;
+
+ ApplicationName = applicationName;
+ UserName = userName;
+
+ Password = string.Empty;
+ PasswordSalt = string.Empty;
+ Email = string.Empty;
+ PasswordAnswer = string.Empty;
+ PasswordQuestion = string.Empty;
+ Comment = string.Empty;
+ ProfileBlobName = string.Empty;
+
+ CreateDateUtc = TableStorageConstants.MinSupportedDateTime;
+ LastLoginDateUtc = TableStorageConstants.MinSupportedDateTime;
+ LastActivityDateUtc = TableStorageConstants.MinSupportedDateTime;
+ LastLockoutDateUtc = TableStorageConstants.MinSupportedDateTime;
+ LastPasswordChangedDateUtc = TableStorageConstants.MinSupportedDateTime;
+ FailedPasswordAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ FailedPasswordAnswerAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ ProfileLastUpdatedUtc = TableStorageConstants.MinSupportedDateTime;
+
+ ProfileIsCreatedByProfileProvider = false;
+ ProfileSize = 0;
+
+ }
+
+ public MembershipRow()
+ : base()
+ {
+ }
+
+ public string ApplicationName
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _applicationName = value;
+ PartitionKey = SecUtility.CombineToKey(ApplicationName, UserName);
+ }
+ get
+ {
+ return _applicationName;
+ }
+ }
+
+
+ public string UserName
+ {
+ set {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _userName = value;
+ PartitionKey = SecUtility.CombineToKey(ApplicationName, UserName);
+ }
+ get
+ {
+ return _userName;
+ }
+ }
+
+ public Guid UserId {
+ set;
+ get;
+ }
+
+ public string Password
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _password = value;
+ }
+ get
+ {
+ return _password;
+ }
+ }
+
+ public int PasswordFormat
+ {
+ set;
+ get;
+ }
+
+ public string PasswordSalt
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _passwordSalt = value;
+ }
+ get
+ {
+ return _passwordSalt;
+ }
+ }
+
+ public string Email
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _email = value;
+ }
+ get
+ {
+ return _email;
+ }
+ }
+
+ public string PasswordQuestion
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _passwordQuestion = value;
+ }
+ get
+ {
+ return _passwordQuestion;
+ }
+ }
+
+ public string PasswordAnswer
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _passwordAnswer = value;
+ }
+ get
+ {
+ return _passwordAnswer;
+ }
+ }
+
+
+ public bool IsApproved
+ {
+ set;
+ get;
+ }
+
+ public bool IsAnonymous
+ {
+ set;
+ get;
+ }
+
+ public bool IsLockedOut
+ {
+ set;
+ get;
+ }
+
+ public DateTime CreateDateUtc {
+ set {
+ SecUtility.SetUtcTime(value, out _createDate);
+ }
+ get {
+ return _createDate;
+ }
+ }
+
+ public DateTime LastLoginDateUtc
+ {
+ set
+ {
+ SecUtility.SetUtcTime(value, out _lastLoginDate);
+ }
+ get
+ {
+ return _lastLoginDate;
+ }
+ }
+
+ public DateTime LastPasswordChangedDateUtc
+ {
+ set {
+ SecUtility.SetUtcTime(value, out _lastPasswordChangedDate);
+ }
+ get {
+ return _lastPasswordChangedDate;
+ }
+ }
+
+ public DateTime LastLockoutDateUtc
+ {
+ set
+ {
+ SecUtility.SetUtcTime(value, out _lastLockoutDate);
+ }
+ get
+ {
+ return _lastLockoutDate;
+ }
+ }
+
+ public DateTime LastActivityDateUtc {
+ set {
+ SecUtility.SetUtcTime(value, out _lastActivityDate);
+ }
+ get {
+ return _lastActivityDate;
+ }
+ }
+
+ public int FailedPasswordAttemptCount
+ {
+ set;
+ get;
+ }
+
+ public DateTime FailedPasswordAttemptWindowStartUtc
+ {
+ set {
+ SecUtility.SetUtcTime(value, out _failedPasswordAttemptWindowStart);
+ }
+ get {
+ return _failedPasswordAttemptWindowStart;
+ }
+ }
+
+ public int FailedPasswordAnswerAttemptCount
+ {
+ set;
+ get;
+ }
+
+ public DateTime FailedPasswordAnswerAttemptWindowStartUtc
+ {
+ set {
+ SecUtility.SetUtcTime(value, out _failedPasswordAnswerAttemptWindowStart);
+ }
+ get {
+ return _failedPasswordAnswerAttemptWindowStart;
+ }
+ }
+
+ public string Comment
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _comment = value;
+ }
+ get
+ {
+ return _comment;
+ }
+ }
+
+ #region Profile provider related properties
+
+ public DateTime ProfileLastUpdatedUtc
+ {
+ set {
+ SecUtility.SetUtcTime(value, out _profileLastUpdated);
+ }
+ get {
+ return _profileLastUpdated;
+ }
+ }
+
+ public int ProfileSize
+ {
+ set;
+ get;
+ }
+
+ public string ProfileBlobName
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value..");
+ }
+ _profileBlobName = value;
+ }
+ get
+ {
+ return _profileBlobName;
+ }
+ }
+
+ public bool ProfileIsCreatedByProfileProvider
+ {
+ set;
+ get;
+ }
+
+ #endregion
+
+ public int CompareTo(object obj)
+ {
+ if (obj == null)
+ {
+ return 1;
+ }
+ MembershipRow row = obj as MembershipRow;
+ if (row == null)
+ {
+ throw new ArgumentException("The parameter obj is not of type MembershipRow.");
+ }
+ return string.Compare(this.UserName, row.UserName, StringComparison.Ordinal);
+ }
+
+ }
+
+ internal class EmailComparer: IComparer {
+ public int Compare(MembershipRow x, MembershipRow y)
+ {
+ if (x == null)
+ {
+ if (y == null)
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ return string.Compare(x.Email, y.Email, StringComparison.Ordinal);
+ }
+ }
+ }
+
+
+ public class TableStorageMembershipProvider : MembershipProvider
+ {
+
+ #region Member variables and constants
+
+ private const int MaxTablePasswordSize = 128;
+ private const int MaxTableEmailLength = 256;
+ private const int MaxTablePasswordQuestionLength = 256;
+ private const int MaxTablePasswordAnswerLength = 128;
+ private const int MaxFindUserSize = 10000;
+ // this is the absolute minimum password size when generating new password
+ // the number is chosen so that it corresponds to the SQL membership provider implementation
+ private const int MinGeneratedPasswordSize = 14;
+ private const int NumRetries = 3;
+
+ // member variables shared between most providers
+ private string _applicationName;
+ private string _accountName;
+ private string _sharedKey;
+ private string _tableName;
+ private string _tableServiceBaseUri;
+ private TableStorage _tableStorage;
+ private object _lock = new object();
+ // retry policies are used sparingly throughout this class because we often want to be
+ // very conservative when it comes to security-related functions
+ private RetryPolicy _tableRetry = RetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
+
+ // membership provider specific member variables
+ private bool _enablePasswordRetrieval;
+ private bool _enablePasswordReset;
+ private bool _requiresQuestionAndAnswer;
+ private bool _requiresUniqueEmail;
+ private int _maxInvalidPasswordAttempts;
+ private int _passwordAttemptWindow;
+ private int _minRequiredPasswordLength;
+ private int _minRequiredNonalphanumericCharacters;
+ private string _passwordStrengthRegularExpression;
+ private MembershipPasswordFormat _passwordFormat;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// The app name is not used in this implementation.
+ ///
+ public override string ApplicationName
+ {
+ get { return _applicationName; }
+ set
+ {
+ lock (_lock)
+ {
+ SecUtility.CheckParameter(ref value, true, true, true, Constants.MaxTableApplicationNameLength, "ApplicationName");
+ _applicationName = value;
+ }
+ }
+ }
+
+ public override bool EnablePasswordRetrieval
+ {
+ get
+ {
+ return _enablePasswordRetrieval;
+ }
+ }
+
+ public override bool EnablePasswordReset
+ {
+ get
+ {
+ return _enablePasswordReset;
+ }
+ }
+
+ public override bool RequiresQuestionAndAnswer
+ {
+ get
+ {
+ return _requiresQuestionAndAnswer;
+ }
+ }
+
+ public override bool RequiresUniqueEmail
+ {
+ get
+ {
+ return _requiresUniqueEmail;
+ }
+ }
+
+ public override MembershipPasswordFormat PasswordFormat
+ {
+ get
+ {
+ return _passwordFormat;
+ }
+ }
+
+ public override int MaxInvalidPasswordAttempts
+ {
+ get
+ {
+ return _maxInvalidPasswordAttempts;
+ }
+ }
+
+ public override int PasswordAttemptWindow
+ {
+ get
+ {
+ return _passwordAttemptWindow;
+ }
+ }
+
+ public override int MinRequiredPasswordLength
+ {
+ get
+ {
+ return _minRequiredPasswordLength;
+ }
+ }
+
+ public override int MinRequiredNonAlphanumericCharacters
+ {
+ get
+ {
+ return _minRequiredNonalphanumericCharacters;
+ }
+ }
+
+ public override string PasswordStrengthRegularExpression
+ {
+ get
+ {
+ return _passwordStrengthRegularExpression;
+ }
+ }
+
+ #endregion
+
+ #region Public methods
+
+ ///
+ /// Initializes the membership provider. This is the only function that cannot be accessed
+ /// in parallel by multiple applications. The function reads the properties of the
+ /// provider specified in the Web.config file and stores them in member variables.
+ ///
+ public override void Initialize(string name, NameValueCollection config)
+ {
+ if (config == null)
+ {
+ throw new ArgumentNullException("config");
+ }
+
+ if (String.IsNullOrEmpty(name))
+ {
+ name = "TableStorageMembershipProvider";
+ }
+
+ if (string.IsNullOrEmpty(config["description"]))
+ {
+ config.Remove("description");
+ config.Add("description", "Table storage-based membership provider");
+ }
+
+ base.Initialize(name, config);
+
+ bool allowInsecureRemoteEndpoints = Configuration.GetBooleanValue(config, "allowInsecureRemoteEndpoints", false);
+ _enablePasswordRetrieval = Configuration.GetBooleanValue(config, "enablePasswordRetrieval", false);
+ _enablePasswordReset = Configuration.GetBooleanValue(config, "enablePasswordReset", true);
+ _requiresQuestionAndAnswer = Configuration.GetBooleanValue(config, "requiresQuestionAndAnswer", true);
+ _requiresUniqueEmail = Configuration.GetBooleanValue(config, "requiresUniqueEmail", true);
+ _maxInvalidPasswordAttempts = Configuration.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
+ _passwordAttemptWindow = Configuration.GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
+ _minRequiredPasswordLength = Configuration.GetIntValue(config, "minRequiredPasswordLength", 7, false, MaxTablePasswordSize);
+ _minRequiredNonalphanumericCharacters = Configuration.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, MaxTablePasswordSize);
+
+ _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"];
+ if (_passwordStrengthRegularExpression != null)
+ {
+ _passwordStrengthRegularExpression = _passwordStrengthRegularExpression.Trim();
+ if (_passwordStrengthRegularExpression.Length != 0)
+ {
+ try
+ {
+ Regex testIfRegexIsValid = new Regex(_passwordStrengthRegularExpression);
+ }
+ catch (ArgumentException e)
+ {
+ throw new ProviderException(e.Message, e);
+ }
+ }
+ }
+ else
+ {
+ _passwordStrengthRegularExpression = string.Empty;
+ }
+
+ if (_minRequiredNonalphanumericCharacters > _minRequiredPasswordLength)
+ {
+ throw new HttpException("The minRequiredNonalphanumericCharacters can not be greater than minRequiredPasswordLength.");
+ }
+
+ string strTemp = config["passwordFormat"];
+ if (strTemp == null)
+ {
+ strTemp = "Hashed";
+ }
+
+ switch (strTemp)
+ {
+ case "Clear":
+ _passwordFormat = MembershipPasswordFormat.Clear;
+ break;
+ case "Encrypted":
+ _passwordFormat = MembershipPasswordFormat.Encrypted;
+ break;
+ case "Hashed":
+ _passwordFormat = MembershipPasswordFormat.Hashed;
+ break;
+ default:
+ throw new ProviderException("Password format specified is invalid.");
+ }
+
+ if (PasswordFormat == MembershipPasswordFormat.Hashed && EnablePasswordRetrieval)
+ throw new ProviderException("Configured settings are invalid: Hashed passwords cannot be retrieved. Either set the password format to different type, or set supportsPasswordRetrieval to false.");
+
+
+ // Table storage-related properties
+ _applicationName = Configuration.GetStringValueWithGlobalDefault(config, "applicationName",
+ Configuration.DefaultProviderApplicationNameConfigurationString,
+ Configuration.DefaultProviderApplicationName, false);
+ _accountName = Configuration.GetStringValue(config, "accountName", null, true);
+ _sharedKey = Configuration.GetStringValue(config, "sharedKey", null, true);
+ _tableName = Configuration.GetStringValueWithGlobalDefault(config, "membershipTableName",
+ Configuration.DefaultMembershipTableNameConfigurationString,
+ Configuration.DefaultMembershipTableName, false);
+ _tableServiceBaseUri = Configuration.GetStringValue(config, "tableServiceBaseUri", null, true);
+
+ config.Remove("allowInsecureRemoteEndpoints");
+ config.Remove("enablePasswordRetrieval");
+ config.Remove("enablePasswordReset");
+ config.Remove("requiresQuestionAndAnswer");
+ config.Remove("requiresUniqueEmail");
+ config.Remove("maxInvalidPasswordAttempts");
+ config.Remove("passwordAttemptWindow");
+ config.Remove("passwordFormat");
+ config.Remove("minRequiredPasswordLength");
+ config.Remove("minRequiredNonalphanumericCharacters");
+ config.Remove("passwordStrengthRegularExpression");
+ config.Remove("applicationName");
+ config.Remove("accountName");
+ config.Remove("sharedKey");
+ config.Remove("membershipTableName");
+ config.Remove("tableServiceBaseUri");
+
+
+ // Throw an exception if unrecognized attributes remain
+ if (config.Count > 0)
+ {
+ string attr = config.GetKey(0);
+ if (!String.IsNullOrEmpty(attr))
+ throw new ProviderException("Unrecognized attribute: " + attr);
+ }
+
+ StorageAccountInfo info = null;
+ try
+ {
+ info = StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration(true);
+ if (_tableServiceBaseUri != null)
+ {
+ info.BaseUri = new Uri(_tableServiceBaseUri);
+ }
+ if (_accountName != null)
+ {
+ info.AccountName = _accountName;
+ }
+ if (_sharedKey != null)
+ {
+ info.Base64Key = _sharedKey;
+ }
+ info.CheckComplete();
+ SecUtility.CheckAllowInsecureEndpoints(allowInsecureRemoteEndpoints, info);
+ _tableStorage = TableStorage.Create(info);
+ _tableStorage.RetryPolicy = _tableRetry;
+ _tableStorage.TryCreateTable(_tableName);
+ }
+ catch (SecurityException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ string exceptionDescription = Configuration.GetInitExceptionDescription(info, "table storage configuration");
+ string tableName = (_tableName == null) ? "no membership table name specified" : _tableName;
+ Log.Write(EventKind.Error, "Could not create or find membership table: " + tableName + "!" + Environment.NewLine +
+ exceptionDescription + Environment.NewLine +
+ e.Message + Environment.NewLine + e.StackTrace);
+ throw new ProviderException("Could not create or find membership table. The most probable reason for this is that " +
+ "the storage endpoints are not configured correctly. Please look at the configuration settings " +
+ "in your .cscfg and Web.config files. More information about this error " +
+ "can be found in the logs when running inside the hosting environment or in the output " +
+ "window of Visual Studio.", e);
+
+ }
+ }
+
+ ///
+ /// Returns true if the username and password match an exsisting user.
+ /// This implementation does not update a user's login time and does
+ /// not raise corresponding Web events
+ ///
+ public override bool ValidateUser(string username, string password)
+ {
+ if (SecUtility.ValidateParameter(ref username, true, true, true, Constants.MaxTableUsernameLength) &&
+ SecUtility.ValidateParameter(ref password, true, true, false, MaxTablePasswordSize) &&
+ CheckPassword(username, password, true, true))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Get a user based on the username parameter.
+ /// If the userIsOnline parameter is set the lastActivity flag of the user
+ /// is changed in the data store
+ ///
+ public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
+ {
+ if (providerUserKey == null)
+ {
+ throw new ArgumentNullException("providerUserKey");
+ }
+
+ if (providerUserKey.GetType() != typeof(Guid)) {
+ throw new ArgumentException("Provided key is not a Guid!");
+ }
+ Guid key = (Guid)providerUserKey;
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ // we need an IQueryable here because we do a Top(2) in the ProcessGetUserQuery()
+ // and cast it to DataServiceQuery object in this function
+ // this does not work when we use IEnumerable as a type here
+ IQueryable query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.UserId == key &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+ return ProcessGetUserQuery(svc, query, userIsOnline);
+ }
+ catch (InvalidOperationException e)
+ {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Retrieves a user based on his/her username.
+ /// The userIsOnline parameter determines whether to update the lastActivityDate of
+ /// the user in the data store
+ ///
+ public override MembershipUser GetUser(string username, bool userIsOnline)
+ {
+ SecUtility.CheckParameter(
+ ref username,
+ true,
+ false,
+ true,
+ Constants.MaxTableUsernameLength,
+ "username");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ // we need an IQueryable here because we do a Top(2) in the ProcessGetUserQuery()
+ // and cast it to DataServiceQuery object in this function
+ // this does not work when we use IEnumerable as a type here
+ IQueryable query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, username) &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+ return ProcessGetUserQuery(svc, query, userIsOnline);
+ }
+ catch (InvalidOperationException e)
+ {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Retrieves a collection of all the users.
+ ///
+ public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
+ {
+ if ( pageIndex < 0 ) {
+ throw new ArgumentException("The page index cannot be negative.");
+ }
+ if ( pageSize < 1 ) {
+ throw new ArgumentException("The page size can only be a positive integer.");
+ }
+
+
+ long upperBound = (long)pageIndex * pageSize + pageSize - 1;
+ if ( upperBound > Int32.MaxValue ) {
+ throw new ArgumentException("pageIndex and pageSize are too big.");
+ }
+
+ totalRecords = 0;
+ MembershipUserCollection users = new MembershipUserCollection();
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ try {
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteAllWithRetries();
+ List allUsersSorted = new List(allUsers);
+
+ // the result should already be sorted because the user name is part of the primary key
+ // we have to sort anyway because we have encoded the user names in the key
+ // this is also why we cannot use the table stoage pagination mechanism here and need to retrieve all elements
+ // for every page
+ allUsersSorted.Sort();
+
+ int startIndex = pageIndex * pageSize;
+ int endIndex = startIndex + pageSize;
+ MembershipRow row;
+ for (int i = startIndex; i < endIndex && i < allUsersSorted.Count; i++) {
+ row = allUsersSorted.ElementAt(i);
+ users.Add(new MembershipUser(this.Name,
+ row.UserName,
+ row.UserId,
+ row.Email,
+ row.PasswordQuestion,
+ row.Comment,
+ row.IsApproved,
+ row.IsLockedOut,
+ row.CreateDateUtc.ToLocalTime(),
+ row.LastLoginDateUtc.ToLocalTime(),
+ row.LastActivityDateUtc.ToLocalTime(),
+ row.LastPasswordChangedDateUtc.ToLocalTime(),
+ row.LastLockoutDateUtc.ToLocalTime()));
+ }
+ } catch (InvalidOperationException e) {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ totalRecords = users.Count;
+ return users;
+ }
+
+
+ ///
+ /// Changes a users password. We don't use retries in this highly security-related function.
+ /// All errors are exposed to the user of this function.
+ ///
+ public override bool ChangePassword(string username, string oldPassword, string newPassword)
+ {
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+ SecUtility.CheckParameter(ref oldPassword, true, true, false, MaxTablePasswordSize, "oldPassword");
+ SecUtility.CheckParameter(ref newPassword, true, true, false, MaxTablePasswordSize, "newPassword");
+
+ try
+ {
+ string salt = null;
+ int passwordFormat;
+ MembershipRow member;
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+
+ if (!CheckPassword(svc, username, oldPassword, false, false, out member))
+ {
+ return false;
+ }
+ salt = member.PasswordSalt;
+ passwordFormat = member.PasswordFormat;
+
+ if (newPassword.Length < MinRequiredPasswordLength)
+ {
+ throw new ArgumentException("The new password is to short.");
+ }
+
+ int count = 0;
+
+ for (int i = 0; i < newPassword.Length; i++)
+ {
+ if (!char.IsLetterOrDigit(newPassword, i))
+ {
+ count++;
+ }
+ }
+
+ if (count < MinRequiredNonAlphanumericCharacters)
+ {
+ throw new ArgumentException("The new password does not have enough non-alphanumeric characters!");
+ }
+
+ if (PasswordStrengthRegularExpression.Length > 0)
+ {
+ if (!Regex.IsMatch(newPassword, PasswordStrengthRegularExpression))
+ {
+ throw new ArgumentException("The new password does not match the specified password strength regular expression.");
+ }
+ }
+
+ string pass = EncodePassword(newPassword, (int)passwordFormat, salt);
+ if (pass.Length > MaxTablePasswordSize)
+ {
+ throw new ArgumentException("Password is too long!");
+ }
+
+ ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, newPassword, false);
+ OnValidatingPassword(e);
+
+ if (e.Cancel)
+ {
+ if (e.FailureInformation != null)
+ {
+ throw e.FailureInformation;
+ }
+ else
+ {
+ throw new ArgumentException("Password validation failure!");
+ }
+ }
+
+ member.Password = pass;
+ member.PasswordSalt = salt;
+ member.PasswordFormat = passwordFormat;
+ member.LastPasswordChangedDateUtc = DateTime.UtcNow;
+ svc.UpdateObject(member);
+ svc.SaveChanges();
+
+ return true;
+ }
+ catch (InvalidOperationException e)
+ {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Creates a new user and stores it in the membership table. We do not use retry policies in this
+ /// highly security-related function. All error conditions are directly exposed to the user.
+ ///
+ public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion,
+ string passwordAnswer, bool isApproved, object providerUserKey,
+ out MembershipCreateStatus status)
+ {
+ if (!SecUtility.ValidateParameter(ref password, true, true, false, MaxTablePasswordSize))
+ {
+ status = MembershipCreateStatus.InvalidPassword;
+ return null;
+ }
+
+ string salt = GenerateSalt();
+ string pass = EncodePassword(password, (int)_passwordFormat, salt);
+ if (pass.Length > MaxTablePasswordSize)
+ {
+ status = MembershipCreateStatus.InvalidPassword;
+ return null;
+ }
+
+ string encodedPasswordAnswer;
+ if (passwordAnswer != null)
+ {
+ passwordAnswer = passwordAnswer.Trim();
+ }
+
+ if (!string.IsNullOrEmpty(passwordAnswer))
+ {
+ if (passwordAnswer.Length > MaxTablePasswordSize)
+ {
+ status = MembershipCreateStatus.InvalidAnswer;
+ return null;
+ }
+ encodedPasswordAnswer = EncodePassword(passwordAnswer.ToLowerInvariant(), (int)_passwordFormat, salt);
+ }
+ else
+ {
+ encodedPasswordAnswer = passwordAnswer;
+ }
+
+ if (!SecUtility.ValidateParameter(ref encodedPasswordAnswer, RequiresQuestionAndAnswer, true, false, MaxTablePasswordSize))
+ {
+ status = MembershipCreateStatus.InvalidAnswer;
+ return null;
+ }
+
+ if (!SecUtility.ValidateParameter(ref username, true, true, true, Constants.MaxTableUsernameLength))
+ {
+ status = MembershipCreateStatus.InvalidUserName;
+ return null;
+ }
+
+ if (!SecUtility.ValidateParameter(ref email,
+ RequiresUniqueEmail,
+ RequiresUniqueEmail,
+ false,
+ Constants.MaxTableUsernameLength))
+ {
+ status = MembershipCreateStatus.InvalidEmail;
+ return null;
+ }
+
+ if (!SecUtility.ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, true, false, Constants.MaxTableUsernameLength))
+ {
+ status = MembershipCreateStatus.InvalidQuestion;
+ return null;
+ }
+
+ if (providerUserKey != null)
+ {
+ if (!(providerUserKey is Guid))
+ {
+ status = MembershipCreateStatus.InvalidProviderUserKey;
+ return null;
+ }
+ }
+
+ if (!EvaluatePasswordRequirements(password))
+ {
+ status = MembershipCreateStatus.InvalidPassword;
+ return null;
+ }
+
+ ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true);
+ OnValidatingPassword(e);
+
+ if (e.Cancel)
+ {
+ status = MembershipCreateStatus.InvalidPassword;
+ return null;
+ }
+
+ // Check whether a user with the same email address already exists.
+ // The danger here is (as we don't have transaction support here) that
+ // there are overlapping requests for creating two users with the same email
+ // address at the same point in time.
+ // A solution for this would be to have a separate table for email addresses.
+ // At this point here in the code we would try to insert this user's email address into the
+ // table and thus check whether the email is unique (the email would be the primary key of the
+ // separate table). There are quite some problems
+ // associated with that. For example, what happens if the user creation fails etc., stale data in the
+ // email table etc.
+ // Another solution is to already insert the user at this point and then check at the end of this
+ // funcation whether the email is unique.
+ if (RequiresUniqueEmail && !IsUniqueEmail(email))
+ {
+ status = MembershipCreateStatus.DuplicateEmail;
+ return null;
+ }
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ MembershipRow newUser = new MembershipRow(_applicationName, username);
+ if (providerUserKey == null)
+ {
+ providerUserKey = Guid.NewGuid();
+ }
+ newUser.UserId = (Guid)providerUserKey;
+ newUser.Password = pass;
+ newUser.PasswordSalt = salt;
+ newUser.Email = (email == null) ? string.Empty : email; ;
+ newUser.PasswordQuestion = (passwordQuestion == null) ? string.Empty : passwordQuestion;
+ newUser.PasswordAnswer = (encodedPasswordAnswer == null) ? string.Empty : encodedPasswordAnswer;
+ newUser.IsApproved = isApproved;
+ newUser.PasswordFormat = (int)_passwordFormat;
+ DateTime now = DateTime.UtcNow;
+ newUser.CreateDateUtc = now;
+ newUser.LastActivityDateUtc = now;
+ newUser.LastPasswordChangedDateUtc = now;
+ newUser.LastLoginDateUtc = now;
+ newUser.IsLockedOut = false;
+
+ svc.AddObject(_tableName, newUser);
+ svc.SaveChanges();
+
+ status = MembershipCreateStatus.Success;
+ return new MembershipUser(this.Name,
+ username,
+ providerUserKey,
+ email,
+ passwordQuestion,
+ null,
+ isApproved,
+ false,
+ now.ToLocalTime(),
+ now.ToLocalTime(),
+ now.ToLocalTime(),
+ now.ToLocalTime(),
+ TableStorageConstants.MinSupportedDateTime);
+ }
+ catch (InvalidOperationException ex)
+ {
+ HttpStatusCode httpStatus;
+ if (TableStorageHelpers.EvaluateException(ex, out httpStatus) && httpStatus == HttpStatusCode.Conflict)
+ {
+ // in this case, some membership providers update the last activity time of the user
+ // we don't do this in this implementation because it would add another roundtrip
+ status = MembershipCreateStatus.DuplicateUserName;
+ return null;
+ }
+ else if (TableStorageHelpers.IsTableStorageException(ex))
+ {
+ throw new ProviderException("Cannot add user to membership data store because of problems when accessing the data store.", ex);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Deletes the user from the membership table.
+ /// This implementation ignores the deleteAllRelatedData argument
+ ///
+ public override bool DeleteUser(string username, bool deleteAllRelatedData)
+ {
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ MembershipRow user = new MembershipRow(_applicationName, username);
+ svc.AttachTo(_tableName, user, "*");
+ svc.DeleteObject(user);
+ svc.SaveChangesWithRetries();
+ return true;
+ }
+ catch (InvalidOperationException e)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(e, out status)) {
+ if (status == HttpStatusCode.NotFound)
+ {
+ return false;
+ }
+ else
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Retrieves a username based on a matching email.
+ ///
+ public override string GetUserNameByEmail(string email)
+ {
+ SecUtility.CheckParameter(ref email, false, false, false, MaxTableEmailLength, "email");
+
+ string nonNullEmail = (email == null) ? string.Empty : email;
+
+ try
+ {
+ DataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.Email == nonNullEmail &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteWithRetries();
+ if (allUsers == null)
+ {
+ return null;
+ }
+ List allUsersList = new List(allUsers);
+ if (allUsersList == null || allUsersList.Count < 1)
+ {
+ return null;
+ }
+ if (allUsersList.Count > 1 && RequiresUniqueEmail)
+ {
+ throw new ProviderException("No unique email address!");
+ }
+ MembershipRow firstMatch = allUsersList.ElementAt(0);
+ return (string.IsNullOrEmpty(firstMatch.Email)) ? null : firstMatch.Email;
+ }
+ catch (InvalidOperationException e)
+ {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Updates a user. The username will not be changed. We explicitly don't use a large retry policy statement between
+ /// reading the user data and updating the user data.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration",
+ MessageId = "0#", Justification = "Code clarity.")]
+ public override void UpdateUser(MembershipUser updatedUser)
+ {
+ if (updatedUser == null)
+ {
+ throw new ArgumentNullException("updatedUser");
+ }
+
+ try
+ {
+ string temp = updatedUser.UserName;
+ SecUtility.CheckParameter(ref temp, true, true, true, Constants.MaxTableUsernameLength, "username");
+ temp = updatedUser.Email;
+ SecUtility.CheckParameter(ref temp, RequiresUniqueEmail, RequiresUniqueEmail, false, MaxTableEmailLength, "Email");
+ updatedUser.Email = temp;
+
+ MembershipRow member = null;
+ if (RequiresUniqueEmail && !IsUniqueEmail(updatedUser.Email, out member) &&
+ member != null && member.UserName != updatedUser.UserName)
+ {
+ throw new ProviderException("Not a unique email address!");
+ }
+
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, updatedUser.UserName) &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteWithRetries();
+
+ if (allUsers == null)
+ {
+ throw new ProviderException("Cannot update user. User not found.");
+ }
+ List allUsersList = new List(allUsers);
+ if (allUsersList == null || allUsersList.Count != 1)
+ {
+ throw new ProviderException("No or no unique user to update.");
+ }
+ MembershipRow userToUpdate = allUsersList.ElementAt(0);
+ userToUpdate.Email = updatedUser.Email;
+ userToUpdate.Comment = (updatedUser.Comment == null) ? string.Empty : updatedUser.Comment;
+ userToUpdate.IsApproved = updatedUser.IsApproved;
+ userToUpdate.LastLoginDateUtc = updatedUser.LastLoginDate.ToUniversalTime();
+ userToUpdate.LastActivityDateUtc = updatedUser.LastActivityDate.ToUniversalTime();
+
+ svc.UpdateObject(userToUpdate);
+ svc.SaveChangesWithRetries();
+ }
+ catch (Exception e)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(e, out status))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ public virtual string GeneratePassword()
+ {
+ return Membership.GeneratePassword(
+ MinRequiredPasswordLength < MinGeneratedPasswordSize ? MinGeneratedPasswordSize : MinRequiredPasswordLength,
+ MinRequiredNonAlphanumericCharacters);
+ }
+
+ ///
+ /// Reset the password of a user. No retry policies are used in this function.
+ ///
+ public override string ResetPassword(string username, string answer)
+ {
+ if (!EnablePasswordReset)
+ {
+ throw new NotSupportedException("Membership provider is configured to not allow password resets!");
+ }
+
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ MembershipRow member = GetUserFromTable(svc, username);
+ if (member == null)
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "Couldn't find a unique user with the name {0}.", username));
+ }
+ if (member.IsLockedOut)
+ {
+ throw new MembershipPasswordException(string.Format(CultureInfo.InstalledUICulture, "The user {0} is currently locked out!", username));
+ }
+
+ int passwordFormat = member.PasswordFormat;
+ string salt = member.PasswordSalt;
+ string encodedPasswordAnswer;
+
+ if (answer != null)
+ {
+ answer = answer.Trim();
+ }
+ if (!string.IsNullOrEmpty(answer))
+ {
+ encodedPasswordAnswer = EncodePassword(answer.ToLowerInvariant(), passwordFormat, salt);
+ }
+ else
+ {
+ encodedPasswordAnswer = answer;
+ }
+ SecUtility.CheckParameter(ref encodedPasswordAnswer, RequiresQuestionAndAnswer, RequiresQuestionAndAnswer, false, MaxTablePasswordSize, "passwordAnswer");
+
+ string newPassword = GeneratePassword();
+ ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, newPassword, false);
+ OnValidatingPassword(e);
+ if (e.Cancel)
+ {
+ if (e.FailureInformation != null)
+ {
+ throw e.FailureInformation;
+ }
+ else
+ {
+ throw new ProviderException("Password validation failed.");
+ }
+ }
+
+ DateTime now = DateTime.UtcNow;
+ Exception ex = null;
+ if (encodedPasswordAnswer == null || encodedPasswordAnswer == member.PasswordAnswer)
+ {
+ member.Password = EncodePassword(newPassword, (int)passwordFormat, salt);
+ member.LastPasswordChangedDateUtc = now;
+ if (member.FailedPasswordAnswerAttemptCount > 0 && encodedPasswordAnswer != null)
+ {
+ member.FailedPasswordAnswerAttemptCount = 0;
+ member.FailedPasswordAnswerAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ }
+ }
+ else
+ {
+ if (now > member.FailedPasswordAnswerAttemptWindowStartUtc.Add(TimeSpan.FromMinutes(PasswordAttemptWindow)))
+ {
+ member.FailedPasswordAnswerAttemptWindowStartUtc = now;
+ member.FailedPasswordAnswerAttemptCount = 1;
+ }
+ else
+ {
+ member.FailedPasswordAnswerAttemptWindowStartUtc = now;
+ member.FailedPasswordAnswerAttemptCount++;
+ }
+ if (member.FailedPasswordAnswerAttemptCount >= MaxInvalidPasswordAttempts)
+ {
+ member.IsLockedOut = true;
+ member.LastLockoutDateUtc = now;
+ }
+ ex = new MembershipPasswordException("Wrong password answer.");
+ }
+
+ svc.UpdateObject(member);
+ svc.SaveChanges();
+
+ if (ex != null)
+ {
+ throw ex;
+ }
+ return newPassword;
+ }
+ catch (Exception e)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(e, out status))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Unlock a user
+ ///
+ public override bool UnlockUser(string userName)
+ {
+ SecUtility.CheckParameter(ref userName, true, true, true, Constants.MaxTableUsernameLength, "username");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ MembershipRow member = GetUserFromTable(svc, userName);
+ if (member == null)
+ {
+ return false;
+ }
+ member.IsLockedOut = false;
+ member.FailedPasswordAttemptCount = 0;
+ member.FailedPasswordAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ member.FailedPasswordAnswerAttemptCount = 0;
+ member.FailedPasswordAnswerAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ member.LastLockoutDateUtc = TableStorageConstants.MinSupportedDateTime;
+ svc.UpdateObject(member);
+ svc.SaveChangesWithRetries();
+ return true;
+ }
+ catch (Exception e)
+ {
+ if (TableStorageHelpers.IsTableStorageException(e)) {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Find users based on their email addresses.
+ /// The emailToMatch must be a complete email string like abc@def.com or can contain a '%' character at the end.
+ /// A '%' character at the end implies that arbitrary characters can follow.
+ /// Supporting additional searches right now would be very expensive because the filtering would have to be done on the
+ /// client side.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Predefined interface.")]
+ public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
+ {
+ SecUtility.CheckParameter(ref emailToMatch, false, false, false, Constants.MaxTableUsernameLength, "emailToMatch");
+
+ if (pageIndex < 0)
+ {
+ throw new ArgumentException("Page index must be a non-negative integer.");
+ }
+ if (pageSize < 1)
+ {
+ throw new ArgumentException("Page size must be a positive integer.");
+ }
+ if (emailToMatch == null)
+ {
+ emailToMatch = string.Empty;
+ }
+ bool startswith = false;
+ if (emailToMatch.Contains('%'))
+ {
+ if (emailToMatch.IndexOf('%') != emailToMatch.Length - 1)
+ {
+ throw new ArgumentException("The TableStorageMembershipProvider only supports search strings that contain '%' as the last character!");
+ }
+ emailToMatch = emailToMatch.Substring(0, emailToMatch.Length - 1);
+ startswith = true;
+ }
+
+ long upperBound = (long)pageIndex * pageSize + pageSize - 1;
+ if (upperBound > Int32.MaxValue)
+ {
+ throw new ArgumentException("Cannot return so many elements!");
+ }
+
+ MembershipUserCollection users = new MembershipUserCollection();
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query;
+ if (startswith && string.IsNullOrEmpty(emailToMatch)) {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+ } else if (startswith) {
+ // so far, the table storage service does not support StartsWith; thus, we retrieve all users whose email is "larger" than the one
+ // specified and do the comparison locally
+ // this can result in significant overhead
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.ProfileIsCreatedByProfileProvider == false &&
+ user.Email.CompareTo(emailToMatch) >= 0
+ select user;
+ } else {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.ProfileIsCreatedByProfileProvider == false &&
+ user.Email == emailToMatch
+ select user;
+ }
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteAllWithRetries();
+
+ int startIndex = pageIndex * pageSize;
+ int endIndex = startIndex + pageSize;
+ int i = 0;
+ List allUsersList = new List(allUsers);
+ allUsersList.Sort(new EmailComparer());
+ MembershipRow row;
+ bool userMatches = true;
+ for (i = startIndex; i < endIndex && i < allUsersList.Count && userMatches; i++) {
+ row = allUsersList.ElementAt(i);
+ Debug.Assert(emailToMatch != null);
+ if (startswith && !string.IsNullOrEmpty(emailToMatch))
+ {
+ if (!row.Email.StartsWith(emailToMatch, StringComparison.Ordinal))
+ {
+ userMatches = false;
+ continue;
+ }
+ }
+ users.Add(new MembershipUser(this.Name,
+ row.UserName,
+ row.UserId,
+ row.Email,
+ row.PasswordQuestion,
+ row.Comment,
+ row.IsApproved,
+ row.IsLockedOut,
+ row.CreateDateUtc.ToLocalTime(),
+ row.LastLoginDateUtc.ToLocalTime(),
+ row.LastActivityDateUtc.ToLocalTime(),
+ row.LastPasswordChangedDateUtc.ToLocalTime(),
+ row.LastLockoutDateUtc.ToLocalTime()));
+ }
+ }
+ catch (Exception e)
+ {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ totalRecords = users.Count;
+ return users;
+ }
+
+ ///
+ /// Find users by their names.
+ /// The usernameToMatch must be the complete username like frank or can contain a '%' character at the end.
+ /// A '%' character at the end implies that arbitrary characters can follow.
+ /// Supporting additional searches right now would be very expensive because the filtering would have to be done on the
+ /// client side; i.e., all users would have to be retrieved in order to do the filtering.
+ /// IMPORTANT: because of this decision, user names must not contain a % character when using this function.
+ ///
+ public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
+ {
+ SecUtility.CheckParameter(ref usernameToMatch, true, true, false, Constants.MaxTableUsernameLength, "usernameToMatch");
+
+ if (pageIndex < 0)
+ {
+ throw new ArgumentException("Page index must be a non-negative integer.");
+ }
+ if (pageSize < 1)
+ {
+ throw new ArgumentException("Page size must be a positive integer.");
+ }
+
+ bool startswith = false;
+ if (usernameToMatch.Contains('%'))
+ {
+ if (usernameToMatch.IndexOf('%') != usernameToMatch.Length - 1)
+ {
+ throw new ArgumentException("The TableStorageMembershipProvider only supports search strings that contain '%' as the last character!");
+ }
+ usernameToMatch = usernameToMatch.Substring(0, usernameToMatch.Length - 1);
+ startswith = true;
+ }
+
+ long upperBound = (long)pageIndex * pageSize + pageSize - 1;
+ if (upperBound > Int32.MaxValue)
+ {
+ throw new ArgumentException("Cannot return so many elements!");
+ }
+
+ MembershipUserCollection users = new MembershipUserCollection();
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query;
+ if (startswith && string.IsNullOrEmpty(usernameToMatch)) {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+ } else if (startswith) {
+ // note that we cannot include the usernameToMatch in the query over the partition key because the partitionkey is escaped, which destroys
+ // the sorting order
+ // and yes, we get all users here whose username is larger than the usernameToMatch because StartsWith is not supported in the current
+ // table storage service
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.UserName.CompareTo(usernameToMatch) >= 0 &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+ } else {
+ query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, usernameToMatch) &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+ }
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteAllWithRetries();
+
+ int startIndex = pageIndex * pageSize;
+ int endIndex = startIndex + pageSize;
+ int i;
+ List allUsersList = new List(allUsers);
+ // default sorting is by user name (not the escaped version in the partition key)
+ allUsersList.Sort();
+ MembershipRow row;
+ bool userMatches = true;
+ for (i = startIndex; i < endIndex && i < allUsersList.Count && userMatches; i++) {
+ row = allUsersList.ElementAt(i);
+ if (startswith && !string.IsNullOrEmpty(usernameToMatch))
+ {
+ if (!row.UserName.StartsWith(usernameToMatch, StringComparison.Ordinal))
+ {
+ userMatches = false;
+ continue;
+ }
+ }
+ users.Add(new MembershipUser(this.Name,
+ row.UserName,
+ row.UserId,
+ row.Email,
+ row.PasswordQuestion,
+ row.Comment,
+ row.IsApproved,
+ row.IsLockedOut,
+ row.CreateDateUtc.ToLocalTime(),
+ row.LastLoginDateUtc.ToLocalTime(),
+ row.LastActivityDateUtc.ToLocalTime(),
+ row.LastPasswordChangedDateUtc.ToLocalTime(),
+ row.LastLockoutDateUtc.ToLocalTime()));
+ }
+ }
+ catch (Exception e)
+ {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ totalRecords = users.Count;
+ return users;
+ }
+
+ ///
+ /// Get the number of users that are currently online
+ ///
+ ///
+ public override int GetNumberOfUsersOnline()
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ DateTime thresh = DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(Membership.UserIsOnlineTimeWindow));
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.LastActivityDateUtc > thresh &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteAllWithRetries();
+ if (allUsers == null)
+ {
+ return 0;
+ }
+ List allUsersList = new List(allUsers);
+ if (allUsersList == null)
+ {
+ return 0;
+ }
+ return allUsersList.Count;
+ }
+
+ ///
+ /// Change the password answer for a user.
+ ///
+ public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
+ {
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+ SecUtility.CheckParameter(ref password, true, true, false, MaxTablePasswordSize, "password");
+
+ try {
+ MembershipRow member;
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ if (!CheckPassword(svc, username, password, false, false, out member))
+ {
+ return false;
+ }
+
+ SecUtility.CheckParameter(ref newPasswordQuestion, RequiresQuestionAndAnswer, RequiresQuestionAndAnswer, false, MaxTablePasswordQuestionLength, "newPasswordQuestion");
+ string encodedPasswordAnswer;
+ if (newPasswordAnswer != null)
+ {
+ newPasswordAnswer = newPasswordAnswer.Trim();
+ }
+
+ SecUtility.CheckParameter(ref newPasswordAnswer, RequiresQuestionAndAnswer, RequiresQuestionAndAnswer, false, MaxTablePasswordAnswerLength, "newPasswordAnswer");
+ if (!string.IsNullOrEmpty(newPasswordAnswer))
+ {
+ encodedPasswordAnswer = EncodePassword(newPasswordAnswer.ToLowerInvariant(), member.PasswordFormat, member.PasswordSalt);
+ }
+ else
+ {
+ encodedPasswordAnswer = newPasswordAnswer;
+ }
+ SecUtility.CheckParameter(ref encodedPasswordAnswer, RequiresQuestionAndAnswer, RequiresQuestionAndAnswer, false, MaxTablePasswordAnswerLength, "newPasswordAnswer");
+
+ member.PasswordQuestion = newPasswordQuestion;
+ member.PasswordAnswer = encodedPasswordAnswer;
+
+ svc.UpdateObject(member);
+ svc.SaveChanges();
+ return true;
+ } catch(Exception e) {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Gets the password of a user given the provided password answer
+ ///
+ public override string GetPassword(string username, string answer)
+ {
+ if (!EnablePasswordRetrieval)
+ {
+ throw new NotSupportedException("Membership provider is configured to reject password retrieval.");
+ }
+
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username" );
+
+ try {
+ if (answer != null)
+ {
+ answer = answer.Trim();
+ }
+ string encodedPasswordAnswer;
+ DataServiceContext svc = CreateDataServiceContext();
+ MembershipRow member = GetUserFromTable(svc, username);
+ if (member == null)
+ {
+ throw new ProviderException("Couldn't find a unique user with that name.");
+ }
+ if (member.IsLockedOut)
+ {
+ throw new MembershipPasswordException("User is locked out.");
+ }
+ if (string.IsNullOrEmpty(answer))
+ {
+ encodedPasswordAnswer = answer;
+ }
+ else
+ {
+ encodedPasswordAnswer = EncodePassword(answer.ToLowerInvariant(), member.PasswordFormat, member.PasswordSalt);
+ }
+ SecUtility.CheckParameter(ref encodedPasswordAnswer, RequiresQuestionAndAnswer, RequiresQuestionAndAnswer, false, MaxTablePasswordAnswerLength, "passwordAnswer");
+
+ Exception ex = null;
+ if (RequiresQuestionAndAnswer) {
+ DateTime now = DateTime.UtcNow;
+ if (string.IsNullOrEmpty(member.PasswordAnswer) || encodedPasswordAnswer != member.PasswordAnswer) {
+ ex = new MembershipPasswordException("Password answer is invalid.");
+ if (now > member.FailedPasswordAnswerAttemptWindowStartUtc.Add(TimeSpan.FromMinutes(PasswordAttemptWindow))) {
+ member.FailedPasswordAnswerAttemptWindowStartUtc = now;
+ member.FailedPasswordAnswerAttemptCount = 1;
+ }
+ else
+ {
+ member.FailedPasswordAnswerAttemptWindowStartUtc = now;
+ member.FailedPasswordAnswerAttemptCount++;
+ }
+ if (member.FailedPasswordAnswerAttemptCount >= MaxInvalidPasswordAttempts)
+ {
+ member.IsLockedOut = true;
+ member.LastLockoutDateUtc = now;
+ }
+ } else {
+ if (member.FailedPasswordAnswerAttemptCount > 0) {
+ member.FailedPasswordAnswerAttemptCount = 0;
+ member.FailedPasswordAnswerAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ }
+ }
+ }
+ svc.UpdateObject(member);
+ svc.SaveChanges();
+ if (ex != null) {
+ throw ex;
+ }
+ return UnEncodePassword(member.Password, member.PasswordFormat);
+ } catch(Exception e) {
+ if (TableStorageHelpers.IsTableStorageException(e))
+ {
+ throw new ProviderException("Error accessing the data source.", e);
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Private helper methods
+
+ private TableStorageDataServiceContext CreateDataServiceContext()
+ {
+ return _tableStorage.GetDataServiceContext();
+ }
+
+ private MembershipUser ProcessGetUserQuery(TableStorageDataServiceContext svc, IQueryable query, bool updateLastActivityDate)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException("query");
+ }
+
+ // if no user is found, we return null
+ MembershipUser res = null;
+
+ // the GetUser query should return at most 1 result, we do a Take(2) to detect error conditions
+ query = query.Take(2);
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable qResult = q.ExecuteWithRetries();
+ if (qResult == null) {
+ return null;
+ }
+ List l = new List(qResult);
+ if (l.Count == 0)
+ {
+ return null;
+ }
+ else if (l.Count > 1)
+ {
+ throw new ProviderException("Non-unique primary keys!");
+ } else {
+ MembershipRow row = l.First();
+ if (updateLastActivityDate)
+ {
+ row.LastActivityDateUtc = DateTime.UtcNow;
+ }
+ res = new MembershipUser(this.Name,
+ row.UserName,
+ row.UserId,
+ row.Email,
+ row.PasswordQuestion,
+ row.Comment,
+ row.IsApproved,
+ row.IsLockedOut,
+ row.CreateDateUtc.ToLocalTime(),
+ row.LastLoginDateUtc.ToLocalTime(),
+ row.LastActivityDateUtc.ToLocalTime(),
+ row.LastPasswordChangedDateUtc.ToLocalTime(),
+ row.LastLockoutDateUtc.ToLocalTime());
+
+ if (updateLastActivityDate)
+ {
+ svc.UpdateObject(row);
+ svc.SaveChangesWithRetries();
+ }
+ }
+ return res;
+ }
+
+ private bool IsUniqueEmail(string email)
+ {
+ MembershipRow member;
+ return IsUniqueEmail(email, out member);
+ }
+
+ private bool IsUniqueEmail(string email, out MembershipRow member)
+ {
+ member = null;
+ SecUtility.ValidateParameter(ref email, true, true, true, TableStorageConstants.MaxStringPropertySizeInChars);
+
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.Email == email &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteWithRetries();
+ if (allUsers == null)
+ {
+ return true;
+ }
+ IEnumerator e = allUsers.GetEnumerator();
+ if (e == null)
+ {
+ return true;
+ }
+ // e.Reset() throws a not implemented exception
+ // according to the spec, the enumerator is at the beginning of the collections after a call to GetEnumerator()
+ if (!e.MoveNext())
+ {
+ return true;
+ }
+ else
+ {
+ member = e.Current;
+ }
+ return false;
+ }
+
+ private bool CheckPassword(string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved)
+ {
+ MembershipRow member = null;
+ return CheckPassword(username, password, updateLastLoginActivityDate, failIfNotApproved, out member);
+ }
+
+ private bool CheckPassword(string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, out MembershipRow member)
+ {
+ return CheckPassword(null, username, password, updateLastLoginActivityDate, failIfNotApproved, out member);
+ }
+
+ private bool CheckPassword(DataServiceContext svc, string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, out MembershipRow member)
+ {
+ bool createContextAndWriteState = false;
+ try
+ {
+ if (svc == null)
+ {
+ svc = CreateDataServiceContext();
+ createContextAndWriteState = true;
+ }
+ member = GetUserFromTable(svc, username);
+ if (member == null)
+ {
+ return false;
+ }
+ if (member.IsLockedOut)
+ {
+ return false;
+ }
+ if (!member.IsApproved && failIfNotApproved)
+ {
+ return false;
+ }
+
+ DateTime now = DateTime.UtcNow;
+ string encodedPasswd = EncodePassword(password, member.PasswordFormat, member.PasswordSalt);
+
+ bool isPasswordCorrect = member.Password.Equals(encodedPasswd);
+
+ if (isPasswordCorrect && member.FailedPasswordAttemptCount == 0 && member.FailedPasswordAnswerAttemptCount == 0)
+ {
+ if (createContextAndWriteState)
+ {
+ svc.UpdateObject(member);
+ svc.SaveChanges();
+ }
+ return true;
+ }
+
+ if (!isPasswordCorrect)
+ {
+ if (now > member.FailedPasswordAttemptWindowStartUtc.Add(TimeSpan.FromMinutes(PasswordAttemptWindow)))
+ {
+ member.FailedPasswordAttemptWindowStartUtc = now;
+ member.FailedPasswordAttemptCount = 1;
+ }
+ else
+ {
+ member.FailedPasswordAttemptWindowStartUtc = now;
+ member.FailedPasswordAttemptCount++;
+ }
+ if (member.FailedPasswordAttemptCount >= MaxInvalidPasswordAttempts)
+ {
+ member.IsLockedOut = true;
+ member.LastLockoutDateUtc = now;
+ }
+ }
+ else
+ {
+ if (member.FailedPasswordAttemptCount > 0 || member.FailedPasswordAnswerAttemptCount > 0)
+ {
+ member.FailedPasswordAnswerAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ member.FailedPasswordAnswerAttemptCount = 0;
+ member.FailedPasswordAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
+ member.FailedPasswordAttemptCount = 0;
+ member.LastLockoutDateUtc = TableStorageConstants.MinSupportedDateTime;
+ }
+ }
+ if (isPasswordCorrect && updateLastLoginActivityDate)
+ {
+ member.LastActivityDateUtc = now;
+ member.LastLoginDateUtc = now;
+ }
+
+ if (createContextAndWriteState)
+ {
+ svc.UpdateObject(member);
+ svc.SaveChanges();
+ }
+
+ return isPasswordCorrect;
+ }
+ catch (Exception e)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(e, out status) && status == HttpStatusCode.PreconditionFailed)
+ {
+ // this element was changed between read and writes
+ Log.Write(EventKind.Warning, "A membership element has been changed between read and writes.");
+ member = null;
+ return false;
+ }
+ else
+ {
+ throw new ProviderException("Error accessing the data store!", e);
+ }
+ }
+ }
+
+ private TimeSpan PasswordAttemptWindowAsTimeSpan()
+ {
+ return new TimeSpan(0, PasswordAttemptWindow, 0);
+ }
+
+ private MembershipRow GetUserFromTable(DataServiceContext svc, string username)
+ {
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, username) &&
+ user.ProfileIsCreatedByProfileProvider == false
+ select user;
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable allUsers = q.ExecuteWithRetries();
+ if (allUsers == null)
+ {
+ return null;
+ }
+ IEnumerator e = allUsers.GetEnumerator();
+ if (e == null)
+ {
+ return null;
+ }
+ // e.Reset() throws a not implemented exception
+ // according to the spec, the enumerator is at the beginning of the collections after a call to GetEnumerator()
+ if (!e.MoveNext())
+ {
+ return null;
+ }
+ MembershipRow ret = e.Current;
+ if (e.MoveNext())
+ {
+ throw new ProviderException("Duplicate elements for primary keys application and user name.");
+ }
+ return ret;
+ }
+
+ private bool EvaluatePasswordRequirements(string password)
+ {
+ if (password.Length < MinRequiredPasswordLength)
+ {
+ return false;
+ }
+
+ int count = 0;
+ for (int i = 0; i < password.Length; i++)
+ {
+ if (!char.IsLetterOrDigit(password, i))
+ {
+ count++;
+ }
+ }
+
+ if (count < MinRequiredNonAlphanumericCharacters)
+ {
+ return false;
+ }
+
+ if (PasswordStrengthRegularExpression.Length > 0)
+ {
+ if (!Regex.IsMatch(password, PasswordStrengthRegularExpression))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static string GenerateSalt()
+ {
+ byte[] buf = new byte[16];
+ (new RNGCryptoServiceProvider()).GetBytes(buf);
+ return Convert.ToBase64String(buf);
+ }
+
+ private string EncodePassword(string pass, int passwordFormat, string salt)
+ {
+ if (passwordFormat == 0)
+ { // MembershipPasswordFormat.Clear
+ return pass;
+ }
+
+ byte[] bIn = Encoding.Unicode.GetBytes(pass);
+ byte[] bSalt = Convert.FromBase64String(salt);
+ byte[] bAll = new byte[bSalt.Length + bIn.Length];
+ byte[] bRet = null;
+
+ Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
+ Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
+ if (passwordFormat == 1)
+ { // MembershipPasswordFormat.Hashed
+ HashAlgorithm s = HashAlgorithm.Create(Membership.HashAlgorithmType);
+ bRet = s.ComputeHash(bAll);
+ }
+ else
+ {
+ bRet = EncryptPassword(bAll);
+ }
+
+ return Convert.ToBase64String(bRet);
+ }
+
+ private string UnEncodePassword(string pass, int passwordFormat)
+ {
+ switch (passwordFormat)
+ {
+ case 0: // MembershipPasswordFormat.Clear:
+ return pass;
+ case 1: // MembershipPasswordFormat.Hashed:
+ throw new ProviderException("Hashed password cannot be decrypted.");
+ default:
+ byte[] bIn = Convert.FromBase64String(pass);
+ byte[] bRet = DecryptPassword(bIn);
+ if (bRet == null)
+ {
+ return null;
+ }
+ return Encoding.Unicode.GetString(bRet, 16, bRet.Length - 16);
+ }
+ }
+
+ #endregion
+ }
+
+
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageProfileProvider.cs b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageProfileProvider.cs
new file mode 100644
index 0000000..79f68fc
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageProfileProvider.cs
@@ -0,0 +1,1267 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Configuration;
+using System.Configuration.Provider;
+using System.Data.Services.Client;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Security;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Web.Profile;
+using System.Xml.Serialization;
+using Microsoft.Samples.ServiceHosting.StorageClient;
+
+
+namespace Microsoft.Samples.ServiceHosting.AspProviders
+{
+
+ public class TableStorageProfileProvider : ProfileProvider
+ {
+
+ #region Member variables and constants
+
+ private const int NumRetries = 3;
+
+ // member variables shared between most providers
+ private string _applicationName;
+ private string _accountName;
+ private string _sharedKey;
+ private string _tableName;
+ private string _tableServiceBaseUri;
+ private string _blobServiceBaseUri;
+ private string _containerName;
+ private BlobProvider _blobProvider;
+ private object _lock = new object();
+ private TableStorage _tableStorage;
+ private RetryPolicy _tableRetry = RetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
+ private ProviderRetryPolicy _providerRetry = ProviderRetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
+
+ #endregion
+
+ public override string ApplicationName
+ {
+ get { return _applicationName; }
+ set
+ {
+ lock (_lock)
+ {
+ SecUtility.CheckParameter(ref value, true, true, true, Constants.MaxTableApplicationNameLength, "ApplicationName");
+ _applicationName = value;
+ }
+ }
+ }
+
+ public override void Initialize(string name, NameValueCollection config)
+ {
+ // Verify that config isn't null
+ if (config == null)
+ {
+ throw new ArgumentNullException("config");
+ }
+
+ // Assign the provider a default name if it doesn't have one
+ if (String.IsNullOrEmpty(name))
+ {
+ name = "TableStorageProfileProvider";
+ }
+
+ // Add a default "description" attribute to config if the
+ // attribute doesn't exist or is empty
+ if (string.IsNullOrEmpty(config["description"]))
+ {
+ config.Remove("description");
+ config.Add("description", "Table storage-based profile provider");
+ }
+
+ // Call the base class's Initialize method
+ base.Initialize(name, config);
+
+ bool allowInsecureRemoteEndpoints = Configuration.GetBooleanValue(config, "allowInsecureRemoteEndpoints", false);
+
+ // structure storage-related properties
+ ApplicationName = Configuration.GetStringValueWithGlobalDefault(config, "applicationName",
+ Configuration.DefaultProviderApplicationNameConfigurationString,
+ Configuration.DefaultProviderApplicationName, false);
+ _accountName = Configuration.GetStringValue(config, "accountName", null, true);
+ _sharedKey = Configuration.GetStringValue(config, "sharedKey", null, true);
+ // profile information are stored in the membership table
+ _tableName = Configuration.GetStringValueWithGlobalDefault(config, "membershipTableName",
+ Configuration.DefaultMembershipTableNameConfigurationString,
+ Configuration.DefaultMembershipTableName, false);
+ _tableServiceBaseUri = Configuration.GetStringValue(config, "tableServiceBaseUri", null, true);
+ _containerName = Configuration.GetStringValueWithGlobalDefault(config, "containerName",
+ Configuration.DefaultProfileContainerNameConfigurationString,
+ Configuration.DefaultProfileContainerName, false);
+ if (!SecUtility.IsValidContainerName(_containerName))
+ {
+ throw new ProviderException("The provider configuration for the TableStorageProfileProvider does not contain a valid container name. " +
+ "Please refer to the documentation for the concrete rules for valid container names." +
+ "The current container name is" + _containerName);
+ }
+ _blobServiceBaseUri = Configuration.GetStringValue(config, "blobServiceBaseUri", null, true);
+
+
+ // remove required attributes
+ config.Remove("allowInsecureRemoteEndpoints");
+ config.Remove("applicationName");
+ config.Remove("accountName");
+ config.Remove("sharedKey");
+ config.Remove("membershipTableName");
+ config.Remove("tableServiceBaseUri");
+ config.Remove("containerName");
+ config.Remove("blobServiceBaseUri");
+
+
+ // Throw an exception if unrecognized attributes remain
+ if (config.Count > 0)
+ {
+ string attr = config.GetKey(0);
+ if (!String.IsNullOrEmpty(attr))
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "Unrecognized attribute: {0}", attr));
+ }
+ }
+
+ // profiles are stored within the membership table
+ StorageAccountInfo tableInfo = null;
+ StorageAccountInfo blobInfo = null;
+ try
+ {
+ tableInfo = StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration(true);
+ blobInfo = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration(true);
+ if (_tableServiceBaseUri != null)
+ {
+ tableInfo.BaseUri = new Uri(_tableServiceBaseUri);
+ }
+ if (_blobServiceBaseUri != null)
+ {
+ blobInfo.BaseUri = new Uri(_blobServiceBaseUri);
+ }
+ if (_accountName != null)
+ {
+ tableInfo.AccountName = _accountName;
+ blobInfo.AccountName = _accountName;
+ }
+ if (_sharedKey != null)
+ {
+ tableInfo.Base64Key = _sharedKey;
+ blobInfo.Base64Key = _sharedKey;
+ }
+ tableInfo.CheckComplete();
+ SecUtility.CheckAllowInsecureEndpoints(allowInsecureRemoteEndpoints, tableInfo);
+ blobInfo.CheckComplete();
+ SecUtility.CheckAllowInsecureEndpoints(allowInsecureRemoteEndpoints, blobInfo);
+ _tableStorage = TableStorage.Create(tableInfo);
+ _tableStorage.RetryPolicy = _tableRetry;
+ _tableStorage.TryCreateTable(_tableName);
+ _blobProvider = new BlobProvider(blobInfo, _containerName);
+ }
+ catch (SecurityException)
+ {
+ throw;
+ }
+ // catch InvalidOperationException and StorageException
+ catch (Exception e)
+ {
+ string exceptionDescription = Configuration.GetInitExceptionDescription(tableInfo, blobInfo);
+ string tableName = (_tableName == null) ? "no profile table name specified" : _tableName;
+ string containerName = (_containerName == null) ? "no container name specified" : _containerName;
+ Log.Write(EventKind.Error, "Initialization of data service structures (tables and/or blobs) failed!" + Environment.NewLine +
+ exceptionDescription + Environment.NewLine +
+ "Configured blob container: " + containerName + Environment.NewLine +
+ "Configured table name: " + tableName + Environment.NewLine +
+ e.Message + Environment.NewLine + e.StackTrace);
+ throw new ProviderException("Initialization of data service structures (tables and/or blobs) failed! " +
+ "The most probable reason for this is that " +
+ "the storage endpoints are not configured correctly. Please look at the configuration settings " +
+ "in your .cscfg and Web.config files. More information about this error " +
+ "can be found in the logs when running inside the hosting environment or in the output " +
+ "window of Visual Studio.", e);
+ }
+ Debug.Assert(_blobProvider != null);
+ }
+
+
+ public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context,
+ SettingsPropertyCollection collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection();
+
+ // Do nothing if there are no properties to retrieve
+ if (collection.Count < 1)
+ {
+ return settings;
+ }
+
+ // For properties lacking an explicit SerializeAs setting, set
+ // SerializeAs to String for strings and primitives, and XML
+ // for everything else
+ foreach (SettingsProperty property in collection)
+ {
+ if (property.SerializeAs == SettingsSerializeAs.ProviderSpecific)
+ {
+ if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(String))
+ {
+ property.SerializeAs = SettingsSerializeAs.String;
+ }
+ else
+ {
+ property.SerializeAs = SettingsSerializeAs.Xml;
+ }
+ }
+ settings.Add(new SettingsPropertyValue(property));
+ }
+
+ // Get the user name or anonymous user ID
+ string username = (string)context["UserName"];
+
+ if (string.IsNullOrEmpty(username))
+ {
+ return settings;
+ }
+
+ if (!VerifyUsername(ref username))
+ {
+ return settings;
+ }
+
+ MembershipRow profile = null;
+ if (!DoesProfileExistAndUpdateUser(username, out profile))
+ {
+ // the profile does not exist
+ // we update the last activity time of the user only if the profile does exist
+ // so we can just return here
+ return settings;
+ }
+
+ Debug.Assert(profile != null);
+
+ // We are ready to go: load the profile
+ // Note that we do not have to deal with write locks here because we use a
+ // different blob name each time we write a new profile
+ StreamReader reader = null;
+ MemoryStream stream = null;
+ BlobProperties blobProperties;
+ string[] names;
+ string values;
+ byte[] buf = null;
+ string line;
+ try
+ {
+ // Open the blob containing the profile data
+ stream = new MemoryStream();
+ if (!_blobProvider.GetBlobContentsWithoutInitialization(profile.ProfileBlobName, stream, out blobProperties) || blobProperties.ContentLength == 0)
+ {
+ // not an error if the blob does not exist
+ return settings;
+ }
+ stream.Seek(0, SeekOrigin.Begin);
+ reader = new StreamReader(stream);
+
+ // Read names, values, and buf from the blob
+ line = reader.ReadLine();
+ names = line.Split(':');
+ values = reader.ReadLine();
+ if (!string.IsNullOrEmpty(values))
+ {
+ UnicodeEncoding encoding = new UnicodeEncoding();
+ values = encoding.GetString(Convert.FromBase64String(values));
+ }
+ string temp = reader.ReadLine();
+ if (!String.IsNullOrEmpty(temp))
+ {
+ buf = Convert.FromBase64String(temp);
+ }
+ else
+ {
+ buf = new byte[0];
+ }
+ }
+ catch (StorageException se)
+ {
+ throw new ProviderException("Error accessing blob storage when getting property values!", se);
+ }
+ catch (Exception e)
+ {
+ throw new ProviderException("Error getting property values.", e);
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.Close();
+ }
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ }
+ // Decode names, values, and buf and initialize the
+ // SettingsPropertyValueCollection returned to the caller
+ DecodeProfileData(names, values, buf, settings);
+
+ return settings;
+ }
+
+ public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ // Get information about the user who owns the profile
+ string username = (string)context["UserName"];
+ bool authenticated = (bool)context["IsAuthenticated"];
+
+ if (String.IsNullOrEmpty(username) || collection.Count == 0)
+ {
+ return;
+ }
+
+ if (!VerifyUsername(ref username))
+ {
+ return;
+ }
+
+ // lets create the blob containing the profile without checking whether the user exists
+
+ // Format the profile data for saving
+ string names = String.Empty;
+ string values = String.Empty;
+ byte[] buf = null;
+
+ EncodeProfileData(ref names, ref values, ref buf, collection, authenticated);
+
+ // Do nothing if no properties need saving
+ if (string.IsNullOrEmpty(names))
+ {
+ return;
+ }
+
+ // Save the profile data
+ MemoryStream stream = null;
+ StreamWriter writer = null;
+ string blobName = null;
+ try {
+ stream = new MemoryStream();
+ writer = new StreamWriter(stream);
+
+ writer.WriteLine(names);
+ if (!String.IsNullOrEmpty(values))
+ {
+ UnicodeEncoding encoding = new UnicodeEncoding();
+ writer.WriteLine(Convert.ToBase64String(encoding.GetBytes(values)));
+ }
+ else
+ {
+ writer.WriteLine();
+ }
+ if (buf != null && buf.Length > 0)
+ {
+ writer.WriteLine(Convert.ToBase64String(buf));
+ }
+ else
+ {
+ writer.WriteLine();
+ }
+ writer.Flush();
+ blobName = GetProfileBlobPrefix(username) + Guid.NewGuid().ToString("N");
+ Debug.Assert(blobName.Length <= TableStorageConstants.MaxStringPropertySizeInChars);
+ stream.Seek(0, SeekOrigin.Begin);
+ _blobProvider.UploadStream(blobName, stream);
+
+ CreateOrUpdateUserAndProfile(username, authenticated, DateTime.UtcNow, blobName, (int)stream.Length);
+ } catch(Exception e) {
+ throw new ProviderException("Error writing property values!", e);
+ } finally {
+ if (writer != null)
+ {
+ writer.Close();
+ }
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ }
+ }
+
+ public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
+ {
+ try
+ {
+ TableStorageDataServiceContext context = CreateDataServiceContext();
+ List inactiveUsers = GetUsersInactive(context, "%", true, authenticationOption, userInactiveSinceDate.ToUniversalTime());
+ int ret = 0;
+ if (inactiveUsers == null || inactiveUsers.Count == 0)
+ {
+ return ret;
+ }
+ foreach (MembershipRow user in inactiveUsers)
+ {
+ if (string.IsNullOrEmpty(user.ProfileBlobName))
+ {
+ continue;
+ }
+ try
+ {
+ if (user.ProfileIsCreatedByProfileProvider)
+ {
+ // we can completely remove this user from the table
+ context.DeleteObject(user);
+ context.SaveChangesWithRetries();
+ }
+ else
+ {
+ user.ProfileBlobName = string.Empty;
+ user.ProfileLastUpdatedUtc = TableStorageConstants.MinSupportedDateTime;
+ user.ProfileSize = 0;
+ context.UpdateObject(user);
+ context.SaveChangesWithRetries();
+ }
+ ret++;
+ // if we fail after savechanges, the profile will still be deleted
+ // but it will be a stale profile
+ // deletes all blobs that start with the prefix
+ _blobProvider.DeleteBlobsWithPrefix(GetProfileBlobPrefix(user.UserName));
+ }
+ catch (InvalidOperationException)
+ {
+ // we ignore these errors and try to continue deleting
+ Log.Write(EventKind.Warning, "The deletion of a single profile failed! Problem when writing to table storage.");
+ if (user != null)
+ {
+ context.Detach(user);
+ }
+ }
+ catch (StorageException)
+ {
+ // we ignore these errors and try to continue deleting
+ Log.Write(EventKind.Warning, "The deletion of a single profile failed! Problem when deleting blob.");
+ }
+ }
+ return ret;
+ }
+ catch (Exception e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ public override int DeleteProfiles(string[] usernames)
+ {
+ SecUtility.CheckArrayParameter(ref usernames, true, true, true, Constants.MaxTableUsernameLength, "usernames");
+
+ TableStorageDataServiceContext context = CreateDataServiceContext();
+ MembershipRow currentProfile = null;
+ int ret = 0;
+
+ try
+ {
+ foreach (string name in usernames)
+ {
+ DataServiceQuery queryObj = context.CreateQuery(_tableName);
+ IEnumerable query = from profile in queryObj
+ where profile.PartitionKey == SecUtility.CombineToKey(_applicationName, name)
+ select profile;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable profiles = q.ExecuteAllWithRetries();
+
+ if (profiles == null)
+ {
+ continue;
+ }
+ // enumerate results
+ List l = new List(profiles);
+ if (l == null || l.Count == 0)
+ {
+ continue;
+ }
+ else if (l.Count > 1)
+ {
+ // there cannot be more than one items in the list (application name + user name is a primary key)
+ Log.Write(EventKind.Warning, "There should only be one user with the same name in one application!");
+ }
+ try
+ {
+ currentProfile = l.First();
+ if (currentProfile.ProfileIsCreatedByProfileProvider)
+ {
+ // we can completely delete this user from the table
+ context.DeleteObject(currentProfile);
+ context.SaveChangesWithRetries();
+ }
+ else
+ {
+ currentProfile.ProfileBlobName = string.Empty;
+ currentProfile.ProfileLastUpdatedUtc = TableStorageConstants.MinSupportedDateTime;
+ currentProfile.ProfileSize = 0;
+ context.UpdateObject(currentProfile);
+ context.SaveChangesWithRetries();
+ }
+ ret++;
+ // if we fail after savechanges, the profile will still be deleted
+ // but it will be a stale profile
+ // deletes all blobs that start with the prefix
+ _blobProvider.DeleteBlobsWithPrefix(GetProfileBlobPrefix(l.First().UserName));
+ }
+ catch (InvalidOperationException)
+ {
+ // we ignore these errors and try to continue deleting
+ Log.Write(EventKind.Warning, "The deletion of a single profile failed! Problem when writing to table storage.");
+ if (currentProfile != null)
+ {
+ context.Detach(currentProfile);
+ }
+ }
+ catch (StorageException)
+ {
+ // we ignore these errors and try to continue deleting
+ Log.Write(EventKind.Warning, "The deletion of a single profile failed! Problem when deleting blob.");
+ }
+ }
+ return ret;
+ }
+ // catch all exceptions, also InvalidOperationExceptions outside the above try block
+ catch (Exception e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ public override int DeleteProfiles(ProfileInfoCollection profiles)
+ {
+ if (profiles == null)
+ {
+ throw new ArgumentNullException("profiles");
+ }
+
+ if (profiles.Count < 1)
+ {
+ throw new ArgumentException("Cannot delete empty profile collection.");
+ }
+
+ List usernames = new List();
+
+ foreach (ProfileInfo profile in profiles)
+ {
+ if (!usernames.Contains(profile.UserName))
+ {
+ usernames.Add(profile.UserName);
+ }
+ }
+ return DeleteProfiles(usernames.ToArray());
+ }
+
+ public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption,
+ string usernameToMatch, DateTime userInactiveSinceDate,
+ int pageIndex, int pageSize, out int totalRecords)
+ {
+ return GetProfilesForQuery(usernameToMatch, userInactiveSinceDate.ToUniversalTime(), authenticationOption, pageIndex, pageSize, out totalRecords);
+ }
+
+ public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption,
+ string usernameToMatch, int pageIndex, int pageSize,
+ out int totalRecords)
+ {
+ return GetProfilesForQuery(usernameToMatch, DateTime.Now.ToUniversalTime().AddDays(1), authenticationOption, pageIndex, pageSize, out totalRecords);
+ }
+
+ public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption,
+ DateTime userInactiveSinceDate, int pageIndex, int pageSize,
+ out int totalRecords)
+ {
+ return GetProfilesForQuery("%", userInactiveSinceDate.ToUniversalTime(), authenticationOption, pageIndex, pageSize, out totalRecords);
+ }
+
+ public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption,
+ int pageIndex, int pageSize, out int totalRecords)
+ {
+ return GetProfilesForQuery("%", DateTime.Now.ToUniversalTime().AddDays(1), authenticationOption, pageIndex, pageSize, out totalRecords);
+ }
+
+ public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption,
+ DateTime userInactiveSinceDate)
+ {
+ // NUM queries are not supported in the PDC offering
+ // so we have to retrieve all records and count at the client side
+ int totalRecords;
+ GetProfilesForQuery("%", userInactiveSinceDate.ToUniversalTime(), authenticationOption, 0, Int32.MaxValue, out totalRecords);
+ return totalRecords;
+ }
+
+ #region Helper methods
+
+ private TableStorageDataServiceContext CreateDataServiceContext()
+ {
+ return _tableStorage.GetDataServiceContext();
+ }
+
+ private string GetProfileBlobPrefix(string username)
+ {
+ return _applicationName + username;
+ }
+
+ private ProfileInfoCollection GetProfilesForQuery(string usernameToMatch, DateTime inactiveSinceUtc,
+ ProfileAuthenticationOption auth, int pageIndex, int pageSize,
+ out int totalRecords)
+ {
+ SecUtility.CheckParameter(ref usernameToMatch, true, true, false, Constants.MaxTableUsernameLength, "usernameToMatch");
+ bool startsWith = false;
+ if (usernameToMatch.Contains('%'))
+ {
+ if (usernameToMatch.IndexOf('%') != usernameToMatch.Length - 1)
+ {
+ throw new ArgumentException("The TableStorageProfileProvider only supports search strings that contain '%' as the last character!");
+ }
+ usernameToMatch = usernameToMatch.Substring(0, usernameToMatch.Length - 1);
+ startsWith = true;
+ }
+ if (inactiveSinceUtc < TableStorageConstants.MinSupportedDateTime)
+ {
+ throw new ArgumentException("DateTime not supported by data source.");
+ }
+ if (pageIndex < 0)
+ {
+ throw new ArgumentException("pageIndex must not be a negative integer.");
+ }
+ if (pageSize < 1)
+ {
+ throw new ArgumentException("pageSize must be a positive integer (strictly larger than zero).");
+ }
+
+ long upperBound = (long)pageIndex * pageSize + pageSize - 1;
+ if (upperBound > Int32.MaxValue)
+ {
+ throw new ArgumentException("pageIndex and pageSize too big.");
+ }
+ try
+ {
+ ProfileInfoCollection infoColl = new ProfileInfoCollection();
+ totalRecords = 0;
+ TableStorageDataServiceContext context = CreateDataServiceContext();
+ List users = GetUsersInactive(context, usernameToMatch, startsWith, auth, inactiveSinceUtc);
+ // default order is by user name (not by escaped user name as it appears in the key)
+ users.Sort();
+
+ int startIndex = pageIndex * pageSize;
+ int endIndex = startIndex + pageSize;
+ int i = 0;
+ bool userMatches = true;
+ MembershipRow user;
+ for (i = startIndex; i < endIndex && i < users.Count && userMatches; i++)
+ {
+ user = users.ElementAt(i);
+ if (startsWith && !string.IsNullOrEmpty(usernameToMatch))
+ {
+ if (!user.UserName.StartsWith(usernameToMatch, StringComparison.Ordinal))
+ {
+ userMatches = false;
+ continue;
+ }
+ }
+ infoColl.Add(new ProfileInfo(user.UserName, user.IsAnonymous, user.LastActivityDateUtc.ToLocalTime(),
+ user.ProfileLastUpdatedUtc.ToLocalTime(), user.ProfileSize));
+ }
+ totalRecords = infoColl.Count;
+ return infoColl;
+ }
+ catch (Exception e)
+ {
+ throw new ProviderException("Error accessing the data store.", e);
+ }
+ }
+
+ // we don't use _providerRetry here because of the out parameter prof
+ private bool DoesProfileExistAndUpdateUser(string username, out MembershipRow prof)
+ {
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+
+ int curRetry = 0;
+ bool retry = false;
+ do
+ {
+ retry = false;
+ try
+ {
+ TableStorageDataServiceContext context = CreateDataServiceContext();
+ DataServiceQuery queryObj = context.CreateQuery(_tableName);
+ IEnumerable query = from profile in queryObj
+ where profile.PartitionKey == SecUtility.CombineToKey(_applicationName, username)
+ select profile;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable profiles = q.ExecuteWithRetries();
+ if (profiles == null)
+ {
+ prof = null;
+ return false;
+ }
+
+ // instantiate results
+ List profileList = new List(profiles);
+
+ if (profileList.Count > 1)
+ {
+ throw new ProviderException("Multiple profile rows for the same user!");
+ }
+ if (profileList.Count == 1)
+ {
+ prof = profileList.First();
+ if (!string.IsNullOrEmpty(prof.ProfileBlobName))
+ {
+ prof.LastActivityDateUtc = DateTime.UtcNow;
+ context.UpdateObject(prof);
+ context.SaveChangesWithRetries();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ prof = null;
+ return false;
+ }
+ }
+ catch (InvalidOperationException e)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(e, out status) && status == HttpStatusCode.PreconditionFailed)
+ {
+ retry = true;
+ }
+ else
+ {
+ throw new ProviderException("Error accessing storage.", e);
+ }
+ }
+ } while (curRetry++ < NumRetries && retry);
+ prof = null;
+ return false;
+ }
+
+ private void UpdateUser(string username, DateTime now)
+ {
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+
+ _providerRetry(() =>
+ {
+ TableStorageDataServiceContext context = CreateDataServiceContext();
+ DataServiceQuery queryObj = context.CreateQuery(_tableName);
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, username)
+ select user;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable users = q.ExecuteWithRetries();
+
+ if (users == null)
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The user {0} does not exist!", username));
+ }
+
+ List memberList = new List(users);
+
+ if (memberList.Count > 1)
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "Multiple users under the same name {0}!", username));
+ }
+
+ memberList.First().LastActivityDateUtc = now;
+ context.UpdateObject(memberList.First());
+ context.SaveChangesWithRetries();
+ });
+ }
+
+ private void CreateOrUpdateUserAndProfile(string username, bool authenticated, DateTime now, string blobName, int size)
+ {
+ Debug.Assert(now.Kind == DateTimeKind.Utc);
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+
+ _providerRetry(() =>
+ {
+ TableStorageDataServiceContext context = CreateDataServiceContext();
+ DataServiceQuery queryObj = context.CreateQuery(_tableName);
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, username)
+ select user;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable users = q.ExecuteWithRetries();
+
+ // instantiate results
+ List userList = null;
+ if (users != null)
+ {
+ userList = new List(users);
+ }
+ if (userList != null && userList.Count > 0)
+ {
+ MembershipRow current = userList.First();
+ if (current.IsAnonymous != !authenticated)
+ {
+ // this is an error because we would need to create a user with the same name
+ // this should not happen
+ throw new ProviderException("A user with the same name but with a different authentication status already exists!");
+ }
+ current.LastActivityDateUtc = now;
+ current.ProfileBlobName = blobName;
+ current.ProfileSize = size;
+ current.ProfileLastUpdatedUtc = now;
+ context.UpdateObject(current);
+ }
+ else
+ {
+ if (authenticated)
+ {
+ Log.Write(EventKind.Warning, "The authenticated user does not exist in the database.");
+ }
+ MembershipRow member = new MembershipRow(_applicationName, username);
+ member.LastActivityDateUtc = now;
+ member.IsAnonymous = !authenticated;
+ member.ProfileBlobName = blobName;
+ member.ProfileSize = size;
+ member.ProfileLastUpdatedUtc = now;
+ member.ProfileIsCreatedByProfileProvider = true;
+ context.AddObject(_tableName, member);
+ }
+ context.SaveChanges();
+ });
+ }
+
+ private List GetUsersInactive(DataServiceContext context, string usernameToMatch, bool startswith, ProfileAuthenticationOption auth, DateTime userInactiveSinceDateUtc)
+ {
+ DataServiceQuery queryObj = context.CreateQuery(_tableName);
+
+ IEnumerable query = null;
+
+ // play a trick to deal with the restrictions of currently supported linq queries
+ bool first, second;
+ if (auth == ProfileAuthenticationOption.All)
+ {
+ first = true;
+ second = false;
+ }
+ else if (auth == ProfileAuthenticationOption.Anonymous)
+ {
+ first = true;
+ second = true;
+ }
+ else
+ {
+ first = false;
+ second = false;
+ }
+
+
+ if (startswith && usernameToMatch == string.Empty)
+ {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.LastActivityDateUtc < userInactiveSinceDateUtc &&
+ user.ProfileBlobName != string.Empty &&
+ (user.IsAnonymous == first || user.IsAnonymous == second)
+ select user;
+ }
+ else if (startswith)
+ {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.LastActivityDateUtc < userInactiveSinceDateUtc &&
+ user.ProfileBlobName != string.Empty &&
+ user.UserName.CompareTo(usernameToMatch) >= 0 &&
+ (user.IsAnonymous == first || user.IsAnonymous == second)
+ select user;
+ }
+ else
+ {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.CombineToKey(_applicationName, usernameToMatch)) == 0 &&
+ user.LastActivityDateUtc < userInactiveSinceDateUtc &&
+ user.ProfileBlobName != string.Empty &&
+ (user.IsAnonymous == first || user.IsAnonymous == second)
+ select user;
+ }
+
+ /*
+ if (auth == ProfileAuthenticationOption.All) {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.LastActivityDateUtc < userInactiveSinceDateUtc &&
+ user.ProfileBlobName != string.Empty
+ select user;
+ }
+ else if (auth == ProfileAuthenticationOption.Anonymous)
+ {
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.LastActivityDateUtc < userInactiveSinceDateUtc &&
+ user.ProfileBlobName != string.Empty &&
+ user.IsAnonymous == true
+ select user;
+ }
+ else
+ {
+ Debug.Assert(auth == ProfileAuthenticationOption.Authenticated);
+ query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.LastActivityDateUtc < userInactiveSinceDateUtc &&
+ user.ProfileBlobName != string.Empty &&
+ user.IsAnonymous == false
+ select user;
+ }
+ */
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable users = q.ExecuteAllWithRetries();
+
+ if (users == null)
+ {
+ return new List(); ;
+ }
+ return new List(users);
+ }
+
+ private List GetAllProfiles()
+ {
+ TableStorageDataServiceContext context = CreateDataServiceContext();
+ DataServiceQuery queryObj = context.CreateQuery(_tableName);
+
+ IEnumerable query = from profile in queryObj
+ where profile.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
+ profile.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0
+ select profile;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable profiles = q.ExecuteAllWithRetries();
+
+ if (profiles == null)
+ {
+ return new List(); ;
+ }
+ return new List(profiles);
+ }
+
+ private static bool VerifyUsername(ref string username)
+ {
+ if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(username.Trim()))
+ {
+ return false;
+ }
+ if (username.Length > Constants.MaxTableUsernameLength)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ private string CompleteContainerName()
+ {
+ Debug.Assert(!string.IsNullOrEmpty(_containerName));
+
+ string completeContainerName = _containerName;
+ if (!string.IsNullOrEmpty(_applicationName))
+ {
+ completeContainerName = _containerName;
+ }
+ return completeContainerName;
+ }
+
+ private static object Deserialize(SettingsPropertyValue p, string s) {
+ if (p == null)
+ {
+ throw new ArgumentNullException("p");
+ }
+ Type type = p.Property.PropertyType;
+ SettingsSerializeAs serializeAs = p.Property.SerializeAs;
+ object ret = null;
+
+ if (type == typeof(string) && (string.IsNullOrEmpty(s) || (serializeAs == SettingsSerializeAs.String)))
+ {
+ return s;
+ } else if (serializeAs == SettingsSerializeAs.String) {
+ TypeConverter converter = TypeDescriptor.GetConverter(type);
+ if (converter == null || !converter.CanConvertTo(typeof(string)) || !converter.CanConvertFrom(typeof(string)))
+ {
+ throw new ArgumentException("Cannot convert type!");
+ }
+ ret = converter.ConvertFromInvariantString(s);
+ } else if (serializeAs == SettingsSerializeAs.Xml) {
+ StringReader reader = new StringReader(s);
+ XmlSerializer serializer = new XmlSerializer(type);
+ ret = serializer.Deserialize(reader);
+ } else {
+ throw new ProviderException("The provider does not support binary serialization because of security constraints!");
+ }
+ return ret;
+ }
+
+ private static void DecodeProfileData(string[] names, string values, byte[] buf, SettingsPropertyValueCollection properties)
+ {
+ if (names == null || values == null || buf == null || properties == null)
+ {
+ return;
+ }
+
+ for (int i = 0; i < names.Length; i += 4)
+ {
+ // Read the next property name from "names" and retrieve
+ // the corresponding SettingsPropertyValue from
+ // "properties"
+ string name = names[i];
+ SettingsPropertyValue pp = properties[name];
+ if (pp == null)
+ {
+ continue;
+ }
+
+ // Get the length and index of the persisted property value
+ int pos = Int32.Parse(names[i + 2], CultureInfo.InvariantCulture);
+ int len = Int32.Parse(names[i + 3], CultureInfo.InvariantCulture);
+
+ // If the length is -1 and the property is a reference
+ // type, then the property value is null
+ if (len == -1 && !pp.Property.PropertyType.IsValueType)
+ {
+ pp.PropertyValue = null;
+ pp.IsDirty = false;
+ pp.Deserialized = true;
+ }
+ // If the property value was peristed as a string,
+ // restore it from "values"
+ else if (names[i + 1] == "S" && pos >= 0 && len > 0 && values.Length >= pos + len)
+ {
+ pp.Deserialized = true;
+ pp.PropertyValue = Deserialize(pp, values.Substring(pos, len));
+ }
+ // If the property value was peristed as a byte array,
+ // restore it from "buf"
+ else if (names[i + 1] == "B" && pos >= 0 && len > 0 && buf.Length >= pos + len)
+ {
+ throw new ProviderException("Not supported because of security-related hosting constraints.");
+ }
+ }
+ }
+
+ private static object Serialize(SettingsPropertyValue p)
+ {
+ if (p == null)
+ {
+ throw new ArgumentNullException("p");
+ }
+ if (p.PropertyValue == null) {
+ return null;
+ }
+ if (p.Property.SerializeAs == SettingsSerializeAs.ProviderSpecific)
+ {
+ if (p.PropertyValue is string || p.Property.PropertyType.IsPrimitive)
+ {
+ p.Property.SerializeAs = SettingsSerializeAs.String;
+ }
+ else
+ {
+ p.Property.SerializeAs = SettingsSerializeAs.Xml;
+ }
+ }
+
+ if (p.Property.SerializeAs == SettingsSerializeAs.String)
+ {
+ return p.PropertyValue.ToString();
+ }
+ else if (p.Property.SerializeAs == SettingsSerializeAs.Xml)
+ {
+ string ret;
+ XmlSerializer serializer = new XmlSerializer(p.Property.PropertyType);
+ using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
+ {
+ serializer.Serialize((TextWriter)writer, p.PropertyValue);
+ writer.Flush();
+ ret = writer.ToString();
+ }
+ return ret;
+ }
+ else if (p.Property.SerializeAs == SettingsSerializeAs.Binary)
+ {
+ throw new ProviderException("Binary serialization is not supported because of security constraints!");
+ }
+ else
+ {
+ throw new ProviderException("Unknown serialization type.");
+ }
+ }
+
+ private static void EncodeProfileData(ref string allNames, ref string allValues, ref byte[] buf,
+ SettingsPropertyValueCollection properties, bool userIsAuthenticated)
+ {
+ StringBuilder names = new StringBuilder();
+ StringBuilder values = new StringBuilder();
+ // currently not used because of length limitations
+ MemoryStream stream = new MemoryStream();
+
+ try
+ {
+ bool anyItemsToSave = false;
+
+ foreach (SettingsPropertyValue pp in properties)
+ {
+ if (pp.IsDirty)
+ {
+ if (!userIsAuthenticated)
+ {
+ bool allowAnonymous = (bool)pp.Property.Attributes["AllowAnonymous"];
+ if (!allowAnonymous)
+ {
+ continue;
+ }
+ }
+ anyItemsToSave = true;
+ break;
+ }
+ }
+
+ if (!anyItemsToSave)
+ {
+ return;
+ }
+
+ foreach (SettingsPropertyValue pp in properties)
+ {
+ // Ignore this property if the user is anonymous and
+ // the property's AllowAnonymous property is false
+ if (!userIsAuthenticated && !(bool)pp.Property.Attributes["AllowAnonymous"])
+ {
+ continue;
+ }
+
+ // Ignore this property if it's not dirty and is
+ // currently assigned its default value
+ if (!pp.IsDirty && pp.UsingDefaultValue)
+ {
+ continue;
+ }
+
+ int len = 0, pos = 0;
+ string propValue = null;
+ // If Deserialized is true and PropertyValue is null,
+ // then the property's current value is null (which
+ // we'll represent by setting len to -1)
+ if (pp.Deserialized && pp.PropertyValue == null)
+ {
+ len = -1;
+ }
+ // Otherwise get the property value from PropertyValue
+ else if (pp.Deserialized)
+ {
+ // because this implementation cannot make use of the providers internal serialization routine
+ // due to security constraints, deserialized means that we need to do the serialization on our own
+ object sVal = Serialize(pp);
+ // If SerializedValue is null, then the property's
+ // current value is null
+ if (sVal == null)
+ len = -1;
+ // If sVal is a string, then encode it as a string
+ else if (sVal is string)
+ {
+ propValue = (string)sVal;
+ len = propValue.Length;
+ pos = values.Length;
+ }
+ // If sVal is binary, then encode it as a byte
+ // array
+ else
+ {
+ throw new ProviderException("Not supported because of security hosting constraints.");
+ }
+ }
+ // Otherwise get the property value from
+ // SerializedValue
+ // This does not work in this implementation because it runs in medium trust and security permissions for binary serialization
+ // are not available
+ else
+ {
+ throw new ProviderException("Because this provider currently runs under partial trust, accessing the standard serialization interface is not allowed.");
+ }
+
+ // Add a string conforming to the following format
+ // to "names:"
+ //
+ // "name:B|S:pos:len"
+ // ^ ^ ^ ^
+ // | | | |
+ // | | | +--- Length of data
+ // | | +------- Offset of data
+ // | +----------- Location (B="buf", S="values")
+ // +--------------- Property name
+ names.Append(pp.Name + ":" + ((propValue != null) ? "S" : "B") + ":" +
+ pos.ToString(CultureInfo.InvariantCulture) + ":" +
+ len.ToString(CultureInfo.InvariantCulture) + ":");
+
+ // If the propery value is encoded as a string, add the
+ // string to "values"
+ if (propValue != null)
+ {
+ values.Append(propValue);
+ }
+ }
+ // Copy the binary property values written to the
+ // stream to "buf"
+ buf = stream.ToArray();
+ }
+ finally
+ {
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ }
+ allNames = names.ToString();
+ allValues = values.ToString();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageRoleProvider.cs b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageRoleProvider.cs
new file mode 100644
index 0000000..95230f0
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageRoleProvider.cs
@@ -0,0 +1,881 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Configuration.Provider;
+using System.Data.Services.Client;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Security;
+using System.Web.Security;
+using Microsoft.Samples.ServiceHosting.StorageClient;
+
+
+namespace Microsoft.Samples.ServiceHosting.AspProviders
+{
+ ///
+ /// This class allows DevtableGen to generate the correct table (named 'Roles')
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses",
+ Justification="Class is used by the devtablegen tool to generate a database for the development storage tool.")]
+ internal class RoleDataServiceContext : TableStorageDataServiceContext
+ {
+ public IQueryable Roles
+ {
+ get
+ {
+ return this.CreateQuery("Roles");
+ }
+ }
+ }
+
+ [CLSCompliant(false)]
+ public class RoleRow : TableStorageEntity
+ {
+ private string _applicationName;
+ private string _roleName;
+ private string _userName;
+
+
+ // applicationName + userName is partitionKey
+ // roleName is rowKey
+ public RoleRow(string applicationName, string roleName, string userName)
+ : base()
+ {
+ SecUtility.CheckParameter(ref applicationName, true, true, true, Constants.MaxTableApplicationNameLength, "applicationName");
+ SecUtility.CheckParameter(ref roleName, true, true, true, TableStorageRoleProvider.MaxTableRoleNameLength, "roleName");
+ SecUtility.CheckParameter(ref userName, true, false, true, Constants.MaxTableUsernameLength, "userName");
+
+
+ PartitionKey = SecUtility.CombineToKey(applicationName, userName);
+ RowKey = SecUtility.Escape(roleName);
+ ApplicationName = applicationName;
+ RoleName = roleName;
+ UserName = userName;
+ }
+
+ public RoleRow()
+ : base()
+ {
+ }
+
+ public string ApplicationName
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _applicationName = value;
+ PartitionKey = SecUtility.CombineToKey(ApplicationName, UserName);
+ }
+ get
+ {
+ return _applicationName;
+ }
+ }
+
+ public string RoleName
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _roleName = value;
+ RowKey = SecUtility.Escape(RoleName);
+ }
+ get
+ {
+ return _roleName;
+ }
+ }
+
+ public string UserName
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _userName = value;
+ PartitionKey = SecUtility.CombineToKey(ApplicationName, UserName);
+ }
+ get
+ {
+ return _userName;
+ }
+ }
+
+ }
+
+ public class TableStorageRoleProvider : RoleProvider
+ {
+
+ #region Member variables and constants
+
+ internal const int MaxTableRoleNameLength = 512;
+ internal const int NumRetries = 3;
+
+ // member variables shared between most providers
+ private string _applicationName;
+ private string _accountName;
+ private string _sharedKey;
+ private string _tableName;
+ private string _membershipTableName;
+ private string _tableServiceBaseUri;
+ private TableStorage _tableStorage;
+ private object _lock = new object();
+ private RetryPolicy _tableRetry = RetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
+ // private ProviderRetryPolicy _providerRetry = ProviderRetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
+
+ #endregion
+
+ #region Properties
+
+ public override string ApplicationName
+ {
+ get { return _applicationName; }
+ set
+ {
+ lock (_lock)
+ {
+ SecUtility.CheckParameter(ref value, true, true, true, Constants.MaxTableApplicationNameLength, "ApplicationName");
+ _applicationName = value;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Public methods
+
+ // RoleProvider methods
+ public override void Initialize(string name, NameValueCollection config)
+ {
+ // Verify that config isn't null
+ if (config == null)
+ {
+ throw new ArgumentNullException("config");
+ }
+
+ // Assign the provider a default name if it doesn't have one
+ if (String.IsNullOrEmpty(name))
+ {
+ name = "TableStorageRoleProvider";
+ }
+
+ // Add a default "description" attribute to config if the
+ // attribute doesn't exist or is empty
+ if (string.IsNullOrEmpty(config["description"]))
+ {
+ config.Remove("description");
+ config.Add("description", "Table storage-based role provider");
+ }
+
+ // Call the base class's Initialize method
+ base.Initialize(name, config);
+
+ bool allowInsecureRemoteEndpoints = Configuration.GetBooleanValue(config, "allowInsecureRemoteEndpoints", false);
+
+ // structure storage-related properties
+ ApplicationName = Configuration.GetStringValueWithGlobalDefault(config, "applicationName",
+ Configuration.DefaultProviderApplicationNameConfigurationString,
+ Configuration.DefaultProviderApplicationName, false);
+ _accountName = Configuration.GetStringValue(config, "accountName", null, true);
+ _sharedKey = Configuration.GetStringValue(config, "sharedKey", null, true);
+ _tableName = Configuration.GetStringValueWithGlobalDefault(config, "roleTableName",
+ Configuration.DefaultRoleTableNameConfigurationString,
+ Configuration.DefaultRoleTableName, false);
+ _membershipTableName = Configuration.GetStringValueWithGlobalDefault(config, "membershipTableName",
+ Configuration.DefaultMembershipTableNameConfigurationString,
+ Configuration.DefaultMembershipTableName, false);
+ _tableServiceBaseUri = Configuration.GetStringValue(config, "tableServiceBaseUri", null, true);
+
+ // remove required attributes
+ config.Remove("allowInsecureRemoteEndpoints");
+ config.Remove("applicationName");
+ config.Remove("accountName");
+ config.Remove("sharedKey");
+ config.Remove("roleTableName");
+ config.Remove("membershipTableName");
+ config.Remove("tableServiceBaseUri");
+
+
+ // Throw an exception if unrecognized attributes remain
+ if (config.Count > 0)
+ {
+ string attr = config.GetKey(0);
+ if (!String.IsNullOrEmpty(attr))
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "Unrecognized attribute: {0}", attr));
+ }
+ }
+
+ StorageAccountInfo info = null;
+ try
+ {
+ info = StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration(true);
+ if (_tableServiceBaseUri != null)
+ {
+ info.BaseUri = new Uri(_tableServiceBaseUri);
+ }
+ if (_accountName != null)
+ {
+ info.AccountName = _accountName;
+ }
+ if (_sharedKey != null)
+ {
+ info.Base64Key = _sharedKey;
+ }
+ info.CheckComplete();
+ SecUtility.CheckAllowInsecureEndpoints(allowInsecureRemoteEndpoints, info);
+ _tableStorage = TableStorage.Create(info);
+ _tableStorage.RetryPolicy = _tableRetry;
+ _tableStorage.TryCreateTable(_tableName);
+ }
+ catch (SecurityException)
+ {
+ throw;
+ }
+ // catch InvalidOperationException as well as StorageException
+ catch (Exception e)
+ {
+ string exceptionDescription = Configuration.GetInitExceptionDescription(info, "table storage configuration");
+ string tableName = (_tableName == null) ? "no role table name specified" : _tableName;
+ Log.Write(EventKind.Error, "Could not create or find role table: " + tableName + "!" + Environment.NewLine +
+ exceptionDescription + Environment.NewLine +
+ e.Message + Environment.NewLine + e.StackTrace);
+ throw new ProviderException("Could not create or find role table. The most probable reason for this is that " +
+ "the storage endpoints are not configured correctly. Please look at the configuration settings " +
+ "in your .cscfg and Web.config files. More information about this error " +
+ "can be found in the logs when running inside the hosting environment or in the output " +
+ "window of Visual Studio.", e);
+ }
+ }
+
+
+ public override bool IsUserInRole(string username, string roleName)
+ {
+ SecUtility.CheckParameter(ref roleName, true, true, true, MaxTableRoleNameLength, "rolename");
+ SecUtility.CheckParameter(ref username, true, false, true, Constants.MaxTableUsernameLength, "username");
+ if (username.Length < 1)
+ {
+ return false;
+ }
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where (user.PartitionKey == SecUtility.CombineToKey(_applicationName, username) ||
+ user.PartitionKey == SecUtility.CombineToKey(_applicationName, string.Empty)) &&
+ user.RowKey == SecUtility.Escape(roleName)
+ select user;
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable userRows = q.ExecuteWithRetries();
+
+ if (userRows == null)
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The role {0} does not exist.", roleName));
+ }
+ List l = new List(userRows);
+ if (l.Count == 0)
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The role {0} does not exist.", roleName));
+ }
+ RoleRow row;
+ if (IsStaleRole(l, out row)) {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The role {0} does not exist.", roleName));
+ }
+ if (l.Count > 2)
+ {
+ throw new ProviderException("User name appears twice in the same role!");
+ }
+ if (l.Count == 1)
+ {
+ Debug.Assert(string.IsNullOrEmpty(l.ElementAt(0).UserName));
+ return false;
+ }
+ return true;
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ public override string[] GetRolesForUser(string username)
+ {
+ SecUtility.CheckParameter(ref username, true, false, true, Constants.MaxTableUsernameLength, "username");
+ if (username.Length < 1)
+ {
+ return new string[0];
+ }
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, username) ||
+ user.PartitionKey == SecUtility.CombineToKey(_applicationName, string.Empty)
+ select user;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable userRows = q.ExecuteAllWithRetries();
+
+ if (userRows == null)
+ {
+ return new string[0];
+ }
+ List l = new List(userRows);
+ if (l.Count == 0)
+ {
+ return new string[0];
+ }
+ List ret = new List();
+ foreach (RoleRow user in l) {
+ if (!string.IsNullOrEmpty(user.UserName) && !IsStaleRole(l, user.RoleName))
+ {
+ ret.Add(user.RoleName);
+ }
+ }
+ return ret.ToArray();
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ public override string[] GetUsersInRole(string roleName)
+ {
+ SecUtility.CheckParameter(ref roleName, true, true, true, MaxTableRoleNameLength, "rolename");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) >= 0 &&
+ user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ user.RowKey == SecUtility.Escape(roleName)
+ select user;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable userRows = q.ExecuteAllWithRetries();
+
+ if (userRows == null)
+ {
+ // role does not exist; we are supposed to throw an exception here
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The role {0} does not exist!", roleName));
+ }
+ List l = new List(userRows);
+ if (l.Count == 0 || IsStaleRole(l, roleName))
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The role {0} does not exist!", roleName));
+ }
+ List ret = new List();
+ foreach (RoleRow user in l)
+ {
+ if (!string.IsNullOrEmpty(user.UserName))
+ {
+ ret.Add(user.UserName);
+ }
+ }
+ return ret.ToArray();
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ public override string[] GetAllRoles()
+ {
+ try
+ {
+ DataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from role in queryObj
+ where role.PartitionKey == SecUtility.CombineToKey(_applicationName, string.Empty)
+ select role;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable userRows = q.ExecuteAllWithRetries();
+
+ if (userRows == null)
+ {
+ return new string[0];
+ }
+ List l = new List(userRows);
+ if (l.Count == 0)
+ {
+ return new string[0];
+ }
+ List ret = new List();
+ foreach (RoleRow role in l)
+ {
+ Debug.Assert(role.UserName != null);
+ if (string.IsNullOrEmpty(role.UserName))
+ {
+ ret.Add(role.RoleName);
+ }
+ }
+ return ret.ToArray();
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ public override bool RoleExists(string roleName)
+ {
+ SecUtility.CheckParameter(ref roleName, true, true, true, MaxTableRoleNameLength, "rolename");
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from role in queryObj
+ where role.PartitionKey == SecUtility.CombineToKey(_applicationName, string.Empty) &&
+ role.RowKey == SecUtility.Escape(roleName)
+ select role;
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ try
+ {
+ // this query addresses exactly one result
+ // we thus should get an exception if there is no element
+ q.ExecuteWithRetries();
+ return true;
+ }
+ catch (DataServiceQueryException e)
+ {
+ HttpStatusCode s;
+ if (TableStorageHelpers.EvaluateException(e, out s) && s == HttpStatusCode.NotFound)
+ {
+ return false;
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ public override void CreateRole(string roleName)
+ {
+ SecUtility.CheckParameter(ref roleName, true, true, true, MaxTableRoleNameLength, "rolename");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ RoleRow newRole = new RoleRow(_applicationName, roleName, string.Empty);
+ svc.AddObject(_tableName, newRole);
+ svc.SaveChangesWithRetries();
+ }
+ catch (InvalidOperationException e)
+ {
+ HttpStatusCode status;
+ // when retry policies are used we cannot distinguish between a conflict and success
+ // so, in the case of a conflict, we just retrun success here
+ if (TableStorageHelpers.EvaluateException(e, out status) && status == HttpStatusCode.Conflict)
+ {
+ return;
+ // the role already exists
+ }
+ throw new ProviderException("Error accessing role table.", e);
+ }
+
+ }
+
+ public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
+ {
+ SecUtility.CheckParameter(ref roleName, true, true, true, MaxTableRoleNameLength, "rolename");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from userRole in queryObj
+ where userRole.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) >= 0 &&
+ userRole.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ userRole.RowKey == SecUtility.Escape(roleName)
+ select userRole;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable userRows = q.ExecuteAllWithRetries();
+
+ if (userRows == null)
+ {
+ return false;
+ }
+ List l = new List(userRows);
+ if (l.Count == 0)
+ {
+ // the role does not exist
+ return false;
+ }
+ RoleRow role;
+ if (IsStaleRole(l, out role)) {
+ return false;
+ }
+ if (l.Count > 1 && throwOnPopulatedRole) {
+ throw new ProviderException("Cannot delete populated role.");
+ }
+ svc.DeleteObject(role);
+ svc.SaveChangesWithRetries();
+ // lets try to remove all remaining elements in the role
+ foreach(RoleRow row in l) {
+ if (row != role) {
+ try
+ {
+ svc.DeleteObject(row);
+ svc.SaveChangesWithRetries();
+ }
+ catch (InvalidOperationException ex)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(ex, out status) && (status == HttpStatusCode.NoContent || status == HttpStatusCode.NotFound))
+ {
+ // this element already was already deleted by another process or during a failed retry
+ // this is not a fatal error; continue deleting elements
+ Log.Write(EventKind.Warning, string.Format(CultureInfo.InstalledUICulture, "The user {0} does not exist in the role {1}.", row.UserName, row.RoleName));
+ }
+ else
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "Error deleting user {0} from role {1}.", row.UserName, row.RoleName));
+ }
+ }
+ }
+ }
+ return true;
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ // Because of limited transactional support in the table storage offering, this function gives limited guarantees
+ // for inserting all users into all roles.
+ // We do not recommend using this function because of missing transactional support.
+ public override void AddUsersToRoles(string[] usernames, string[] roleNames)
+ {
+ SecUtility.CheckArrayParameter(ref roleNames, true, true, true, MaxTableRoleNameLength, "roleNames");
+ SecUtility.CheckArrayParameter(ref usernames, true, true, true, Constants.MaxTableUsernameLength, "usernames");
+
+ RoleRow row;
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ foreach (string role in roleNames)
+ {
+ if (!RoleExists(role))
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The role {0} does not exist!", role));
+ }
+ foreach (string user in usernames)
+ {
+ row = new RoleRow(_applicationName, role, user);
+ try
+ {
+ svc.AddObject(_tableName, row);
+ svc.SaveChangesWithRetries();
+ }
+ catch (InvalidOperationException e)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(e, out status) && status == HttpStatusCode.Conflict)
+ {
+ // this element already exists or was created in a failed retry
+ // this is not a fatal error; continue adding elements
+ Log.Write(EventKind.Warning, string.Format(CultureInfo.InstalledUICulture, "The user {0} already exists in the role {1}.", user, role));
+ svc.Detach(row);
+ }
+ else
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "Error adding user {0} to role {1}", user, role));
+ }
+ }
+ }
+ }
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ // the username to match can be in a format that varies between providers
+ // for this implementation, a syntax similar to the one used in the SQL provider is applied
+ // "user%" will return all users in a role that start with the string "user"
+ // the % sign can only appear at the end of the usernameToMatch parameter
+ // because the current version of the table storage service does not support StartsWith in LINQ queries,
+ // calling this function can cause significant network trafic when '%' is used in the usernameToMach
+ // parameter
+ public override string[] FindUsersInRole(string roleName, string usernameToMatch)
+ {
+ SecUtility.CheckParameter(ref roleName, true, true, true, MaxTableRoleNameLength, "rolename");
+ SecUtility.CheckParameter(ref usernameToMatch, true, true, false, Constants.MaxTableUsernameLength, "usernameToMatch");
+
+ bool startswith = false;
+ if (usernameToMatch.Contains('%'))
+ {
+ if (usernameToMatch.IndexOf('%') != usernameToMatch.Length - 1)
+ {
+ throw new ArgumentException("The TableStorageRoleProvider only supports search strings that contain '%' as the last character!");
+ }
+ usernameToMatch = usernameToMatch.Substring(0, usernameToMatch.Length - 1);
+ startswith = true;
+ }
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query;
+
+ if (startswith && string.IsNullOrEmpty(usernameToMatch)) {
+ // get all users in the role
+ query = from userRole in queryObj
+ where userRole.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) >= 0 &&
+ userRole.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ userRole.RowKey == SecUtility.Escape(roleName)
+ select userRole;
+ } else if (startswith) {
+ // get all users in the role that start with the specified string (we cannot restrict the query more because StartsWith is not supported)
+ // we cannot include the username to search for in the key, because the key might e escaped
+ query = from userRole in queryObj
+ where userRole.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) >= 0 &&
+ userRole.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
+ userRole.RowKey == SecUtility.Escape(roleName) &&
+ (userRole.UserName.CompareTo(usernameToMatch) >= 0 || userRole.UserName == string.Empty)
+ select userRole;
+ } else {
+ // get a specific user
+ query = from userRole in queryObj
+ where (userRole.PartitionKey == SecUtility.CombineToKey(_applicationName, usernameToMatch) ||
+ userRole.PartitionKey == SecUtility.CombineToKey(_applicationName, string.Empty)) &&
+ userRole.RowKey == SecUtility.Escape(roleName)
+ select userRole;
+ }
+
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable userRows = q.ExecuteAllWithRetries();
+
+ if (userRows == null)
+ {
+ throw new ProviderException("The role does not exist!");
+ }
+ List l = new List(userRows);
+ if (l.Count == 0)
+ {
+ // the role does not exist
+ throw new ProviderException("The role does not exist!");
+ }
+ RoleRow role;
+ if (IsStaleRole(l, out role))
+ {
+ throw new ProviderException("The role does not exist!");
+ }
+ List ret = new List();
+ foreach (RoleRow row in l)
+ {
+ if (row != role)
+ {
+ if (startswith && !string.IsNullOrEmpty(usernameToMatch) && !row.UserName.StartsWith(usernameToMatch, StringComparison.Ordinal))
+ {
+ continue;
+ }
+ ret.Add(row.UserName);
+ }
+ }
+ return ret.ToArray();
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ // remember that there is no is no rollback functionality for the table storage service right now
+ // be cautious when using this function
+ // if a role does not exist, we stop deleting roles, if a user in a role does not exist, we continue deleting
+ // in case of error conditions, the behavior of this function is different than the SQL role provider
+ public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
+ {
+ SecUtility.CheckArrayParameter(ref roleNames, true, true, true, MaxTableRoleNameLength, "roleNames");
+ SecUtility.CheckArrayParameter(ref usernames, true, true, true, Constants.MaxTableUsernameLength, "usernames");
+
+ RoleRow row;
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ foreach (string role in roleNames)
+ {
+ if (!RoleExists(role))
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "The role {0} does not exist!", role));
+ }
+ foreach (string user in usernames)
+ {
+ row = GetUserInRole(svc, role, user);
+ if (row == null)
+ {
+ Log.Write(EventKind.Warning, string.Format(CultureInfo.InstalledUICulture, "The user {0} does not exist in the role {1}.", user, role));
+ continue;
+ }
+ try
+ {
+ svc.DeleteObject(row);
+ svc.SaveChangesWithRetries();
+ }
+ catch (Exception e)
+ {
+ HttpStatusCode status;
+ if (TableStorageHelpers.EvaluateException(e, out status) && (status == HttpStatusCode.NoContent || status == HttpStatusCode.NotFound))
+ {
+ Log.Write(EventKind.Warning, string.Format(CultureInfo.InstalledUICulture, "The user {0} does not exist in the role {1}.", user, role));
+ svc.Detach(row);
+ }
+ else
+ {
+ throw new ProviderException(string.Format(CultureInfo.InstalledUICulture, "Error deleting user {0} from role {1}.", user, role));
+ }
+ }
+ }
+ }
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ #endregion
+
+
+ #region Helper methods
+
+ private TableStorageDataServiceContext CreateDataServiceContext()
+ {
+ return _tableStorage.GetDataServiceContext();
+ }
+
+ private static bool IsStaleRole(List l, string rolename) {
+ if (l == null || l.Count == 0)
+ {
+ return false;
+ }
+ foreach (RoleRow row in l)
+ {
+ // if (row.RoleName == rolename && row.UserName == string.Empty)
+ if (string.Compare(row.RoleName, rolename, StringComparison.Ordinal) == 0 && string.IsNullOrEmpty(row.UserName))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ private static bool IsStaleRole(List l, out RoleRow role)
+ {
+ role = null;
+ if (l == null || l.Count == 0)
+ {
+ return false;
+ }
+ string rolename = l.ElementAt(0).RoleName;
+ foreach (RoleRow row in l)
+ {
+ Debug.Assert(row.RoleName == rolename);
+ if (string.IsNullOrEmpty(row.UserName))
+ {
+ role = row;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private RoleRow GetUserInRole(DataServiceContext svc, string rolename, string username)
+ {
+ SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
+ SecUtility.CheckParameter(ref rolename, true, true, true, MaxTableRoleNameLength, "rolename");
+
+ try
+ {
+ DataServiceQuery queryObj = svc.CreateQuery(_tableName);
+
+ IEnumerable query = from user in queryObj
+ where user.PartitionKey == SecUtility.CombineToKey(_applicationName, username) &&
+ user.RowKey == SecUtility.Escape(rolename)
+ select user;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ try
+ {
+ IEnumerable userRows = q.ExecuteAllWithRetries();
+ return userRows.First();
+ }
+ catch (DataServiceQueryException e)
+ {
+ HttpStatusCode s;
+ if (TableStorageHelpers.EvaluateException(e, out s) && s == HttpStatusCode.NotFound)
+ {
+ return null;
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error while accessing the data store.", e);
+ }
+ }
+
+ #endregion
+ }
+
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageSessionStateProvider.cs b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageSessionStateProvider.cs
new file mode 100644
index 0000000..9338574
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageSessionStateProvider.cs
@@ -0,0 +1,882 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+// This file contains an implementation of a session state provider that uses both blob and table storage.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Configuration.Provider;
+using System.Data.Services.Client;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Security;
+using System.Web;
+using System.Web.SessionState;
+using Microsoft.Samples.ServiceHosting.StorageClient;
+
+
+namespace Microsoft.Samples.ServiceHosting.AspProviders
+{
+ ///
+ /// This class allows DevtableGen to generate the correct table (named 'Sessions')
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses",
+ Justification="Class is used by the devtablegen tool to generate a database for the development storage tool")]
+ internal class SessionDataServiceContext : TableStorageDataServiceContext
+ {
+ public IQueryable Sessions
+ {
+ get
+ {
+ return this.CreateQuery("Sessions");
+ }
+ }
+ }
+
+ [CLSCompliant(false)]
+ public class SessionRow : TableStorageEntity
+ {
+ private string _id;
+ private string _applicationName;
+ private string _blobName;
+ private DateTime _expires;
+ private DateTime _created;
+ private DateTime _lockDate;
+
+
+ // application name + session id is partitionKey
+ public SessionRow(string sessionId, string applicationName)
+ : base()
+ {
+ SecUtility.CheckParameter(ref sessionId, true, true, true, TableStorageConstants.MaxStringPropertySizeInChars, "sessionId");
+ SecUtility.CheckParameter(ref applicationName, true, true, true, Constants.MaxTableApplicationNameLength, "applicationName");
+
+ PartitionKey = SecUtility.CombineToKey(applicationName, sessionId);
+ RowKey = string.Empty;
+
+ Id = sessionId;
+ ApplicationName = applicationName;
+ ExpiresUtc = TableStorageConstants.MinSupportedDateTime;
+ LockDateUtc = TableStorageConstants.MinSupportedDateTime;
+ CreatedUtc = TableStorageConstants.MinSupportedDateTime;
+ Timeout = 0;
+ BlobName = string.Empty;
+ }
+
+ public SessionRow()
+ : base()
+ {
+ }
+
+ public string Id
+ {
+ set {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _id = value;
+ PartitionKey = SecUtility.CombineToKey(ApplicationName, Id);
+ }
+ get
+ {
+ return _id;
+ }
+ }
+
+ public string ApplicationName
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _applicationName = value;
+ PartitionKey = SecUtility.CombineToKey(ApplicationName, Id);
+ }
+ get
+ {
+ return _applicationName;
+ }
+ }
+
+ public int Timeout
+ {
+ set;
+ get;
+ }
+
+ public DateTime ExpiresUtc
+ {
+ set
+ {
+ SecUtility.SetUtcTime(value, out _expires);
+ }
+ get
+ {
+ return _expires;
+ }
+ }
+
+ public DateTime CreatedUtc
+ {
+ set
+ {
+ SecUtility.SetUtcTime(value, out _created);
+ }
+ get
+ {
+ return _created;
+ }
+ }
+
+ public DateTime LockDateUtc
+ {
+ set
+ {
+ SecUtility.SetUtcTime(value, out _lockDate);
+ }
+ get
+ {
+ return _lockDate;
+ }
+ }
+
+ public bool Locked
+ {
+ set;
+ get;
+ }
+
+ public int Lock
+ {
+ set;
+ get;
+ }
+
+ public string BlobName
+ {
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
+ }
+ _blobName = value;
+ }
+ get
+ {
+ return _blobName;
+ }
+ }
+
+ public bool Initialized
+ {
+ set;
+ get;
+ }
+
+ }
+
+ public class TableStorageSessionStateProvider : SessionStateStoreProviderBase
+ {
+
+ #region Member variables and constants
+
+ private string _applicationName;
+ private string _accountName;
+ private string _sharedKey;
+ private string _tableName;
+ private string _tableServiceBaseUri;
+ private string _blobServiceBaseUri;
+ private string _containerName;
+ private TableStorage _tableStorage;
+ private BlobProvider _blobProvider;
+ private const int NumRetries = 3;
+ private RetryPolicy _tableRetry = RetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
+ private ProviderRetryPolicy _providerRetry = ProviderRetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
+
+ #endregion
+
+
+ #region public methods
+
+ public override void Initialize(string name, NameValueCollection config)
+ {
+ // Verify that config isn't null
+ if (config == null)
+ {
+ throw new ArgumentNullException("config");
+ }
+
+ // Assign the provider a default name if it doesn't have one
+ if (String.IsNullOrEmpty(name))
+ {
+ name = "TableServiceSessionStateProvider";
+ }
+
+ // Add a default "description" attribute to config if the
+ // attribute doesn't exist or is empty
+ if (string.IsNullOrEmpty(config["description"]))
+ {
+ config.Remove("description");
+ config.Add("description", "Session state provider using table storage");
+ }
+
+ // Call the base class's Initialize method
+ base.Initialize(name, config);
+
+ bool allowInsecureRemoteEndpoints = Configuration.GetBooleanValue(config, "allowInsecureRemoteEndpoints", false);
+
+ // structure storage-related properties
+ _applicationName = Configuration.GetStringValueWithGlobalDefault(config, "applicationName",
+ Configuration.DefaultProviderApplicationNameConfigurationString,
+ Configuration.DefaultProviderApplicationName, false);
+ _accountName = Configuration.GetStringValue(config, "accountName", null, true);
+ _sharedKey = Configuration.GetStringValue(config, "sharedKey", null, true);
+ _tableName = Configuration.GetStringValueWithGlobalDefault(config, "sessionTableName",
+ Configuration.DefaultSessionTableNameConfigurationString,
+ Configuration.DefaultSessionTableName, false);
+ _tableServiceBaseUri = Configuration.GetStringValue(config, "tableServiceBaseUri", null, true);
+ _containerName = Configuration.GetStringValueWithGlobalDefault(config, "containerName",
+ Configuration.DefaultSessionContainerNameConfigurationString,
+ Configuration.DefaultSessionContainerName, false);
+ if (!SecUtility.IsValidContainerName(_containerName))
+ {
+ throw new ProviderException("The provider configuration for the TableStorageSessionStateProvider does not contain a valid container name. " +
+ "Please refer to the documentation for the concrete rules for valid container names." +
+ "The current container name is: " + _containerName);
+ }
+ _blobServiceBaseUri = Configuration.GetStringValue(config, "blobServiceBaseUri", null, true);
+
+ config.Remove("allowInsecureRemoteEndpoints");
+ config.Remove("accountName");
+ config.Remove("sharedKey");
+ config.Remove("containerName");
+ config.Remove("applicationName");
+ config.Remove("blobServiceBaseUri");
+ config.Remove("tableServiceBaseUri");
+ config.Remove("sessionTableName");
+
+ // Throw an exception if unrecognized attributes remain
+ if (config.Count > 0)
+ {
+ string attr = config.GetKey(0);
+ if (!String.IsNullOrEmpty(attr))
+ throw new ProviderException
+ ("Unrecognized attribute: " + attr);
+ }
+
+ StorageAccountInfo tableInfo = null;
+ StorageAccountInfo blobInfo = null;
+ try
+ {
+ tableInfo = StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration(true);
+ blobInfo = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration(true);
+ if (_tableServiceBaseUri != null)
+ {
+ tableInfo.BaseUri = new Uri(_tableServiceBaseUri);
+ }
+ if (_blobServiceBaseUri != null)
+ {
+ blobInfo.BaseUri = new Uri(_blobServiceBaseUri);
+ }
+ if (_accountName != null)
+ {
+ tableInfo.AccountName = _accountName;
+ blobInfo.AccountName = _accountName;
+ }
+ if (_sharedKey != null)
+ {
+ tableInfo.Base64Key = _sharedKey;
+ blobInfo.Base64Key = _sharedKey;
+ }
+ tableInfo.CheckComplete();
+ SecUtility.CheckAllowInsecureEndpoints(allowInsecureRemoteEndpoints, tableInfo);
+ blobInfo.CheckComplete();
+ SecUtility.CheckAllowInsecureEndpoints(allowInsecureRemoteEndpoints, blobInfo);
+ _tableStorage = TableStorage.Create(tableInfo);
+ _tableStorage.RetryPolicy = _tableRetry;
+ _tableStorage.TryCreateTable(_tableName);
+ _blobProvider = new BlobProvider(blobInfo, _containerName);
+ }
+ catch (SecurityException)
+ {
+ throw;
+ }
+ // catch InvalidOperationException as well as StorageException
+ catch (Exception e)
+ {
+ string exceptionDescription = Configuration.GetInitExceptionDescription(tableInfo, blobInfo);
+ string tableName = (_tableName == null) ? "no session table name specified" : _tableName;
+ string containerName = (_containerName == null) ? "no container name specified" : _containerName;
+ Log.Write(EventKind.Error, "Initialization of data service structures (tables and/or blobs) failed!" +
+ exceptionDescription + Environment.NewLine +
+ "Configured blob container: " + containerName + Environment.NewLine +
+ "Configured table name: " + tableName + Environment.NewLine +
+ e.Message + Environment.NewLine + e.StackTrace);
+ throw new ProviderException("Initialization of data service structures (tables and/or blobs) failed!" +
+ "The most probable reason for this is that " +
+ "the storage endpoints are not configured correctly. Please look at the configuration settings " +
+ "in your .cscfg and Web.config files. More information about this error " +
+ "can be found in the logs when running inside the hosting environment or in the output " +
+ "window of Visual Studio.", e);
+ }
+ Debug.Assert(_blobProvider != null);
+ }
+
+ public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
+ {
+ Debug.Assert(context != null);
+ return new SessionStateStoreData(new SessionStateItemCollection(),
+ SessionStateUtility.GetSessionStaticObjects(context),
+ timeout);
+ }
+
+ public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
+ {
+ Debug.Assert(context != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+ if (timeout < 0)
+ {
+ throw new ArgumentException("Parameter timeout must be a non-negative integer!");
+ }
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ SessionRow session = new SessionRow(id, _applicationName);
+
+ session.Lock = 0; // no lock
+ session.Initialized = false;
+ session.Id = id;
+ session.Timeout = timeout;
+ session.ExpiresUtc = DateTime.UtcNow.AddMinutes(timeout);
+ svc.AddObject(_tableName, session);
+ svc.SaveChangesWithRetries();
+ } catch (InvalidOperationException e) {
+ throw new ProviderException("Error accessing the data store.", e);
+ }
+ }
+
+ public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge,
+ out object lockId, out SessionStateActions actions)
+ {
+ Debug.Assert(context != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+
+ return GetSession(context, id, out locked, out lockAge, out lockId, out actions, false);
+ }
+
+ public override SessionStateStoreData GetItemExclusive(HttpContext context, string id,
+ out bool locked, out TimeSpan lockAge, out object lockId,
+ out SessionStateActions actions)
+ {
+ Debug.Assert(context != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+
+ return GetSession(context, id, out locked, out lockAge, out lockId, out actions, true);
+ }
+
+ public override void SetAndReleaseItemExclusive(HttpContext context, string id,
+ SessionStateStoreData item, object lockId, bool newItem)
+ {
+ Debug.Assert(context != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+
+ _providerRetry(() =>
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ SessionRow session;
+
+ if (!newItem)
+ {
+ session = GetSession(id, svc);
+ if (session == null || session.Lock != (int)lockId)
+ {
+ Debug.Assert(false);
+ return;
+ }
+ }
+ else
+ {
+ session = new SessionRow(id, _applicationName);
+ session.Lock = 1;
+ session.LockDateUtc = DateTime.UtcNow;
+ }
+ session.Initialized = true;
+ Debug.Assert(session.Timeout >= 0);
+ session.Timeout = item.Timeout;
+ session.ExpiresUtc = DateTime.UtcNow.AddMinutes(session.Timeout);
+ session.Locked = false;
+
+ // yes, we always create a new blob here
+ session.BlobName = GetBlobNamePrefix(id) + Guid.NewGuid().ToString("N");
+
+
+ // Serialize the session and write the blob
+ byte[] items, statics;
+ SerializeSession(item, out items, out statics);
+ string serializedItems = Convert.ToBase64String(items);
+ string serializedStatics = Convert.ToBase64String(statics);
+ MemoryStream output = new MemoryStream();
+ StreamWriter writer = new StreamWriter(output);
+
+ try
+ {
+ writer.WriteLine(serializedItems);
+ writer.WriteLine(serializedStatics);
+ writer.Flush();
+ // for us, it shouldn't matter whether newItem is set to true or false
+ // because we always create the entire blob and cannot append to an
+ // existing one
+ _blobProvider.UploadStream(session.BlobName, output);
+ writer.Close();
+ output.Close();
+ }
+ catch (Exception e)
+ {
+ if (!newItem)
+ {
+ ReleaseItemExclusive(svc, session, lockId);
+ }
+ throw new ProviderException("Error accessing the data store.", e);
+ }
+ finally
+ {
+ if (writer != null)
+ {
+ writer.Close();
+ }
+ if (output != null)
+ {
+ output.Close();
+ }
+ }
+
+ if (newItem)
+ {
+ svc.AddObject(_tableName, session);
+ svc.SaveChangesWithRetries();
+ }
+ else
+ {
+ // Unlock the session and save changes
+ ReleaseItemExclusive(svc, session, lockId);
+ }
+ });
+ }
+
+ public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
+ {
+ Debug.Assert(context != null);
+ Debug.Assert(lockId != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ SessionRow session = GetSession(id, svc);
+ ReleaseItemExclusive(svc, session, lockId);
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error accessing the data store!", e);
+ }
+ }
+
+ public override void ResetItemTimeout(HttpContext context, string id)
+ {
+ Debug.Assert(context != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+
+ _providerRetry(() => {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ SessionRow session = GetSession(id, svc);
+ session.ExpiresUtc = DateTime.UtcNow.AddMinutes(session.Timeout);
+ svc.UpdateObject(session);
+ svc.SaveChangesWithRetries();
+ });
+ }
+
+ public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
+ {
+ Debug.Assert(context != null);
+ Debug.Assert(lockId != null);
+ Debug.Assert(_blobProvider != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ SessionRow session = GetSession(id, svc);
+ if (session == null)
+ {
+ Debug.Assert(false);
+ return;
+ }
+ if (session.Lock != (int)lockId)
+ {
+ Debug.Assert(false);
+ return;
+ }
+ svc.DeleteObject(session);
+ svc.SaveChangesWithRetries();
+ }
+ catch (InvalidOperationException e)
+ {
+ throw new ProviderException("Error accessing the data store!", e);
+ }
+
+ // delete associated blobs
+ try
+ {
+ IEnumerable e = _blobProvider.ListBlobs(GetBlobNamePrefix(id));
+ if (e == null)
+ {
+ return;
+ }
+ IEnumerator props = e.GetEnumerator();
+ if (props == null)
+ {
+ return;
+ }
+ while (props.MoveNext())
+ {
+ if (props.Current != null)
+ {
+ if (!_blobProvider.DeleteBlob(props.Current.Name)) {
+ // ignore this; it is possible that another thread could try to delete the blob
+ // at the same time
+ }
+ }
+ }
+ } catch(StorageException e) {
+ throw new ProviderException("Error accessing blob storage.", e);
+ }
+ }
+
+ public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
+ {
+ // This provider doesn't support expiration callbacks
+ // so simply return false here
+ return false;
+ }
+
+ public override void InitializeRequest(HttpContext context)
+ {
+ // no specific logic for initializing requests in this provider
+ }
+
+ public override void EndRequest(HttpContext context)
+ {
+ // no specific logic for ending requests in this provider
+ }
+
+ // nothing can be done here because there might be session managers at different machines involved in
+ // handling sessions
+ public override void Dispose()
+ {
+ }
+
+ #endregion
+
+ #region Helper methods
+
+ private TableStorageDataServiceContext CreateDataServiceContext()
+ {
+ return _tableStorage.GetDataServiceContext();
+ }
+
+ private static void ReleaseItemExclusive(TableStorageDataServiceContext svc, SessionRow session, object lockId)
+ {
+ if ((int)lockId != session.Lock)
+ {
+ // obviously that can happen, but let's see when at least in Debug mode
+ Debug.Assert(false);
+ return;
+ }
+
+ session.ExpiresUtc = DateTime.UtcNow.AddMinutes(session.Timeout);
+ session.Locked = false;
+ svc.UpdateObject(session);
+ svc.SaveChangesWithRetries();
+ }
+
+
+ private SessionRow GetSession(string id)
+ {
+ DataServiceContext svc = CreateDataServiceContext();
+ return GetSession(id, svc);
+ }
+
+
+ private SessionRow GetSession(string id, DataServiceContext context)
+ {
+ Debug.Assert(context != null);
+ Debug.Assert(id != null && id.Length <= TableStorageConstants.MaxStringPropertySizeInChars);
+
+ try
+ {
+ DataServiceQuery queryObj = context.CreateQuery(_tableName);
+ IEnumerable query = from session in queryObj
+ where session.PartitionKey == SecUtility.CombineToKey(_applicationName, id)
+ select session;
+ TableStorageDataServiceQuery q = new TableStorageDataServiceQuery(query as DataServiceQuery, _tableRetry);
+ IEnumerable sessions = q.ExecuteWithRetries();
+
+ // enumerate the result and store it in a list
+ List sessionList = new List(sessions);
+ if (sessionList != null && sessionList.Count() == 1)
+ {
+ return sessionList.First();
+ } else if (sessionList != null && sessionList.Count() > 1) {
+ throw new ProviderException("Multiple sessions with the same name!");
+ }
+ else
+ {
+ return null;
+ }
+ }
+ catch (Exception e)
+ {
+ throw new ProviderException("Error accessing storage.", e);
+ }
+ }
+
+
+ // we don't use the retry policy itself in this function because out parameters are not well handled by
+ // retry policies
+ private SessionStateStoreData GetSession(HttpContext context, string id, out bool locked, out TimeSpan lockAge,
+ out object lockId, out SessionStateActions actions,
+ bool exclusive)
+ {
+ Debug.Assert(context != null);
+ SecUtility.CheckParameter(ref id, true, true, false, TableStorageConstants.MaxStringPropertySizeInChars, "id");
+
+ SessionRow session = null;
+
+ int curRetry = 0;
+ bool retry = false;
+
+ // Assign default values to out parameters
+ locked = false;
+ lockId = null;
+ lockAge = TimeSpan.Zero;
+ actions = SessionStateActions.None;
+
+ do
+ {
+ retry = false;
+ try
+ {
+ TableStorageDataServiceContext svc = CreateDataServiceContext();
+ session = GetSession(id, svc);
+
+ // Assign default values to out parameters
+ locked = false;
+ lockId = null;
+ lockAge = TimeSpan.Zero;
+ actions = SessionStateActions.None;
+
+ // if the blob does not exist, we return null
+ // ASP.NET will call the corresponding method for creating the session
+ if (session == null)
+ {
+ return null;
+ }
+ if (session.Initialized == false)
+ {
+ Debug.Assert(session.Locked == false);
+ actions = SessionStateActions.InitializeItem;
+ session.Initialized = true;
+ }
+ session.ExpiresUtc = DateTime.UtcNow.AddMinutes(session.Timeout);
+ if (exclusive)
+ {
+ if (!session.Locked)
+ {
+ if (session.Lock == Int32.MaxValue)
+ {
+ session.Lock = 0;
+ }
+ else
+ {
+ session.Lock++;
+ }
+ session.LockDateUtc = DateTime.UtcNow;
+ }
+ lockId = session.Lock;
+ locked = session.Locked;
+ session.Locked = true;
+ }
+ lockAge = DateTime.UtcNow.Subtract(session.LockDateUtc);
+ lockId = session.Lock;
+
+ if (locked == true)
+ {
+ return null;
+ }
+
+ // let's try to write this back to the data store
+ // in between, someone else could have written something to the store for the same session
+ // we retry a number of times; if all fails, we throw an exception
+ svc.UpdateObject(session);
+ svc.SaveChangesWithRetries();
+ }
+ catch (InvalidOperationException e)
+ {
+ HttpStatusCode status;
+ // precondition fails indicates problems with the status code
+ if (TableStorageHelpers.EvaluateException(e, out status) && status == HttpStatusCode.PreconditionFailed)
+ {
+ retry = true;
+ }
+ else
+ {
+ throw new ProviderException("Error accessing the data store.", e);
+ }
+ }
+ } while (retry && curRetry++ < NumRetries);
+
+ // ok, now we have successfully written back our state
+ // we can now read the blob
+ // note that we do not need to care about read/write locking when accessing the
+ // blob because each time we write a new session we create a new blob with a different name
+
+ SessionStateStoreData result = null;
+ MemoryStream stream = null;
+ StreamReader reader = null;
+ BlobProperties properties;
+ try
+ {
+ try
+ {
+ stream = _blobProvider.GetBlobContent(session.BlobName, out properties);
+ }
+ catch (StorageException e)
+ {
+ throw new ProviderException("Couldn't read session blob!", e);
+ }
+
+ reader = new StreamReader(stream);
+ if (actions == SessionStateActions.InitializeItem)
+ {
+ // Return an empty SessionStateStoreData
+ result = new SessionStateStoreData(new SessionStateItemCollection(),
+ SessionStateUtility.GetSessionStaticObjects(context), session.Timeout);
+ }
+ else
+ {
+ // Read Items, StaticObjects, and Timeout from the file
+ byte[] items = Convert.FromBase64String(reader.ReadLine());
+ byte[] statics = Convert.FromBase64String(reader.ReadLine());
+ int timeout = session.Timeout;
+ // Deserialize the session
+ result = DeserializeSession(items, statics, timeout);
+ }
+ }
+ finally
+ {
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ if (reader != null)
+ {
+ reader.Close();
+ }
+ }
+ return result;
+ }
+
+ private string GetBlobNamePrefix(string id)
+ {
+ return string.Format(CultureInfo.InstalledUICulture, "{0}{1}", id, _applicationName);
+ }
+
+ private static void SerializeSession(SessionStateStoreData store, out byte[] items, out byte[] statics)
+ {
+ bool hasItems = (store.Items != null && store.Items.Count > 0);
+ bool hasStaticObjects = (store.StaticObjects != null && store.StaticObjects.Count > 0 && !store.StaticObjects.NeverAccessed);
+ items = null;
+ statics = new byte [0];
+
+ using (MemoryStream stream1 = new MemoryStream())
+ {
+ using (BinaryWriter writer1 = new BinaryWriter(stream1))
+ {
+ writer1.Write(hasItems);
+ if (hasItems)
+ {
+ ((SessionStateItemCollection)store.Items).Serialize(writer1);
+ }
+ items = stream1.ToArray();
+ }
+ }
+
+ if (hasStaticObjects)
+ {
+ throw new ProviderException("Static objects are not supported in this provider because of security-related hosting constraints.");
+ }
+ }
+
+ private static SessionStateStoreData DeserializeSession(byte[] items, byte[] statics, int timeout)
+ {
+ SessionStateItemCollection itemCol = null;
+ HttpStaticObjectsCollection staticCol = null;
+
+ using (MemoryStream stream1 = new MemoryStream(items))
+ {
+ using (BinaryReader reader1 = new BinaryReader(stream1))
+ {
+ bool hasItems = reader1.ReadBoolean();
+ if (hasItems)
+ {
+ itemCol = SessionStateItemCollection.Deserialize(reader1);
+ }
+ else
+ {
+ itemCol = new SessionStateItemCollection();
+ }
+ }
+ }
+
+ if (HttpContext.Current != null && HttpContext.Current.Application != null &&
+ HttpContext.Current.Application.StaticObjects != null && HttpContext.Current.Application.StaticObjects.Count > 0) {
+ throw new ProviderException("This provider does not support static session objects because of security-related hosting constraints.");
+ }
+
+ if (statics != null && statics.Count() > 0) {
+ throw new ProviderException("This provider does not support static session objects because of security-related hosting constraints.");
+ }
+
+ return new SessionStateStoreData(itemCol, staticCol, timeout);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/AspProviders.dll b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/AspProviders.dll
new file mode 100644
index 0000000..0b14b98
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/AspProviders.dll differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/AspProviders.pdb b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/AspProviders.pdb
new file mode 100644
index 0000000..7fa2cbe
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/AspProviders.pdb differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.dll b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.dll
new file mode 100644
index 0000000..539fd6d
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.dll differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.pdb b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.pdb
new file mode 100644
index 0000000..f10d989
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.pdb differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.xml b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.xml
new file mode 100644
index 0000000..8adfba6
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Debug/StorageClient.xml
@@ -0,0 +1,2002 @@
+
+
+
+ StorageClient
+
+
+
+
+ This type represents the different constituent parts that make up a resource Uri in the context of cloud services.
+
+
+
+
+ Construct a ResourceUriComponents object.
+
+ The account name that should become part of the URI.
+ The container name (container, queue or table name) that should become part of the URI.
+ Remaining part of the URI.
+
+
+
+ Construct a ResourceUriComponents object.
+
+ The account name that should become part of the URI.
+ The container name (container, queue or table name) that should become part of the URI.
+
+
+
+ Construct a ResourceUriComponents object.
+
+ The account name that should become part of the URI.
+
+
+
+ Construct a ResourceUriComponents object.
+
+
+
+
+ The account name in the URI.
+
+
+
+
+ This is really the first component (delimited by '/') after the account name. Since it happens to
+ be a container name in the context of all our storage services (containers in blob storage,
+ queues in the queue service and table names in table storage), it's named as ContainerName to make it more
+ readable at the cost of slightly being incorrectly named.
+
+
+
+
+ The remaining string in the URI.
+
+
+
+
+ Create a canonicalized string out of HTTP request header contents for signing
+ blob/queue requests with the Shared Authentication scheme.
+
+ The uri address of the HTTP request.
+ Components of the Uri extracted out of the request.
+ The method of the HTTP request (GET/PUT, etc.).
+ The content type of the HTTP request.
+ The date of the HTTP request.
+ Should contain other headers of the HTTP request.
+ A canonicalized string of the HTTP request.
+
+
+
+ Canonicalize HTTP header contents.
+
+ An HttpWebRequest object.
+ Components of the Uri extracted out of the request.
+ The canonicalized string of the given HTTP request's header.
+
+
+
+ Creates a standard datetime string for the shared key lite authentication scheme.
+
+ DateTime value to convert to a string in the expected format.
+
+
+
+
+ An internal class that stores the canonicalized string version of an HTTP request.
+
+
+
+
+ Constructor for the class.
+
+ The first canonicalized element to start the string with.
+
+
+
+ Append additional canonicalized element to the string.
+
+ An additional canonicalized element to append to the string.
+
+
+
+ Property for the canonicalized string.
+
+
+
+
+ Use this class to extract various header values from Http requests.
+
+
+
+
+ A helper function for extracting HTTP header values from a NameValueCollection object.
+
+ A NameValueCollection object that should contain HTTP header name-values pairs.
+ Name of the header that we want to get values of.
+ A array list of values for the header. The values are in the same order as they are stored in the NameValueCollection object.
+
+
+
+ Constructs an URI given all its constituents
+
+
+ This is the service endpoint in case of path-style URIs and a host suffix in case of host-style URIs
+ IMPORTANT: This does NOT include the service name or account name
+
+ Uri constituents
+ Indicates whether to construct a path-style Uri (true) or host-style URI (false)
+ Full uri
+
+
+
+ Constructs a path-style resource URI given all its constituents
+
+
+
+
+ Constructs a host-style resource URI given all its constituents
+
+
+
+
+ Given the host suffix part, service name and account name, this method constructs the account Uri
+
+
+
+
+ Objects of this class contain the credentials (name and key) of a storage account.
+
+
+
+
+ Create a SharedKeyCredentials object given an account name and a shared key.
+
+
+
+
+ Signs the request appropriately to make it an authenticated request.
+ Note that this method takes the URI components as decoding the URI components requires the knowledge
+ of whether the URI is in path-style or host-style and a host-suffix if it's host-style.
+
+
+
+
+ Signs requests using the SharedKeyLite authentication scheme with is used for the table storage service.
+
+
+
+
+ This is the default content-type xStore uses when no content type is specified
+
+
+
+
+ When transmitting a blob that is larger than this constant, this library automatically
+ transmits the blob as individual blocks. I.e., the blob is (1) partitioned
+ into separate parts (these parts are called blocks) and then (2) each of the blocks is
+ transmitted separately.
+ The maximum size of this constant as supported by the real blob storage service is currently
+ 64 MB; the development storage tool currently restricts this value to 2 MB.
+ Setting this constant can have a significant impact on the performance for uploading or
+ downloading blobs.
+ As a general guideline: If you run in a reliable environment increase this constant to reduce
+ the amount of roundtrips. In an unreliable environment keep this constant low to reduce the
+ amount of data that needs to be retransmitted in case of connection failures.
+
+
+
+
+ The size of a single block when transmitting a blob that is larger than the
+ MaximumBlobSizeBeforeTransmittingAsBlocks constant (see above).
+ The maximum size of this constant is currently 4 MB; the development storage
+ tool currently restricts this value to 1 MB.
+ Setting this constant can have a significant impact on the performance for uploading or
+ downloading blobs.
+ As a general guideline: If you run in a reliable environment increase this constant to reduce
+ the amount of roundtrips. In an unreliable environment keep this constant low to reduce the
+ amount of data that needs to be retransmitted in case of connection failures.
+
+
+
+
+ Contains regular expressions for checking whether container and table names conform
+ to the rules of the storage REST protocols.
+
+
+
+
+ Container or queue names that match against this regular expression are valid.
+
+
+
+
+ Table names that match against this regular expression are valid.
+
+
+
+
+ Converts the date time to a valid string form as per HTTP standards
+
+
+
+
+ Parse a string having the date time information in acceptable formats according to HTTP standards
+
+
+
+
+ Copies from one stream to another
+
+ The stream to copy from
+ The stream to copy to
+
+
+
+ Error codes that can be returned by the storage service or the client library.
+ These are divided into server errors and client errors depending on which side
+ the error can be attributed to.
+
+
+
+
+ The base class for storage service exceptions
+
+
+
+
+ Initializes a new instance of the class with
+ serialized data.
+
+ The object that contains serialized object
+ data about the exception being thrown
+ The object that contains contextual information
+ about the source or destionation.
+
+
+
+ Sets the object with additional exception information
+
+ The object that holds the
+ serialized object data.
+ The object that contains contextual information
+ about the source or destionation.
+
+
+
+ The Http status code returned by the storage service
+
+
+
+
+ The specific error code returned by the storage service
+
+
+
+
+
+
+
+
+
+ Server exceptions are those due to server side problems.
+ These may be transient and requests resulting in such exceptions
+ can be retried with the same parameters.
+
+
+
+
+ Initializes a new instance of the class with
+ serialized data.
+
+ The object that contains serialized object
+ data about the exception being thrown
+ The object that contains contextual information
+ about the source or destionation.
+
+
+
+ Client side exceptions are due to incorrect parameters to the request.
+ These requests should not be retried with the same parameters
+
+
+
+
+ Initializes a new instance of the class with
+ serialized data.
+
+ The object that contains serialized object
+ data about the exception being thrown
+ The object that contains contextual information
+ about the source or destionation.
+
+
+
+ Error code strings that are common to all storage services
+
+
+
+
+ Error code strings that are specific to blob service
+
+
+
+
+ Error code strings that are specific to queue service
+
+
+
+
+ Error code strings that are specific to queue service
+
+ public static class TableErrorCodeStrings
+
+
+
+ The entry point of the blob storage API
+
+
+
+
+ Factory method for BlobStorage
+
+ The base URI of the blob storage service
+ If true, path-style URIs (http://baseuri/accountname/containername/objectname) are used.
+ If false host-style URIs (http://accountname.baseuri/containername/objectname) are used,
+ where baseuri is the URI of the service.
+ If null, the choice is made automatically: path-style URIs if host name part of base URI is an
+ IP addres, host-style otherwise.
+ The name of the storage account
+ Authentication key used for signing requests
+ A newly created BlobStorage instance
+
+
+
+ Factory method for BlobStorage
+
+ Account information
+ A newly created BlobStorage instance
+
+
+
+ Get a reference to a newly created BlobContainer object.
+ This method does not make any calls to the storage service.
+
+ The name of the container
+ A reference to a newly created BlobContainer object
+
+
+
+ Lists the containers within the account.
+
+ A list of containers
+
+
+
+ The default timeout
+
+
+
+
+ The default retry policy
+
+
+
+
+ The time out for each request to the storage service.
+
+
+
+
+ The retry policy used for retrying requests
+
+
+
+
+ The base URI of the blob storage service
+
+
+
+
+ The name of the storage account
+
+
+
+
+ Indicates whether to use/generate path-style or host-style URIs
+
+
+
+
+ Get a reference to a BlobContainer object with the given name.
+ This method does not make any calls to the storage service.
+
+ The name of the container
+ A reference to a newly created BlobContainer object
+
+
+
+ Lists the containers within the account.
+
+ A list of containers
+
+
+
+ The blob container class.
+ Used to access and enumerate blobs in the container.
+ Storage key credentials are needed to access private blobs but not for public blobs.
+
+
+
+
+ Use this constructor to access private blobs.
+
+ The base Uri for the storage endpoint
+ Name of the storage account
+ Name of the container
+
+
+
+ Use this constructor to access private blobs.
+
+ The base Uri for the storage endpoint
+
+ If true, path-style URIs (http://baseuri/accountname/containername/objectname) are used and if false
+ host-style URIs (http://accountname.baseuri/containername/objectname) are used, where baseuri is the
+ URI of the service
+
+ Name of the storage account
+ Name of the container
+ Date of last modification
+
+
+
+ Create the container if it does not exist.
+ The container is created with private access control and no metadata.
+
+ true if the container was created. false if the container already exists
+
+
+
+ Create the container with the specified metadata and access control if it does not exist
+
+ The metadata for the container. Can be null to indicate no metadata
+ The access control (public or private) with which to create the container
+ true if the container was created. false if the container already exists
+
+
+
+ Check if the blob container exists
+
+ true if the container exists, false otherwise.
+
+
+
+ Get the properties for the container if it exists.
+
+ The properties for the container if it exists, null otherwise
+
+
+
+ Get the access control permissions associated with the container.
+
+
+
+
+
+ Set the access control permissions associated with the container.
+
+ The permission to set
+
+
+
+ Deletes the current container.
+
+
+
+
+ Check if the blob container exists
+
+ Name of the BLOB.
+ true if the blob exists, false otherwise.
+
+
+
+ Create a new blob or overwrite an existing blob.
+
+ The properties of the blob
+ The contents of the blob
+ Should this request overwrite an existing blob ?
+ true if the blob was created. false if the blob already exists and was set to false
+ The LastModifiedTime property of is set as a result of this call.
+ This method also has an effect on the ETag values that are managed by the service.
+
+
+
+ Updates an existing blob if it has not been modified since the specified time which is typically
+ the last modified time of the blob when you retrieved it.
+ Use this method to implement optimistic concurrency by avoiding clobbering changes to the blob
+ made by another writer.
+
+ The properties of the blob. This object should be one previously
+ obtained from a call to GetBlob or GetBlobProperties and have its LastModifiedTime property set.
+ The contents of the blob. The contents of the blob should be readable
+ true if the blob was updated. false if the blob has changed since the last time
+ The LastModifiedTime property of is set as a result of this call.
+ This method also has an effect on the ETag values that are managed by the service if the update was
+ successful.
+
+
+
+ Get the blob contents and properties if the blob exists
+
+ The name of the blob
+ Object in which the contents are returned.
+ This object should contain a writable stream or should be a default constructed object.
+ Should the blob be gotten in pieces. This requires more round-trips, but will retry smaller pieces in case of failure.
+ The properties of the blob if the blob exists.
+
+
+
+ Gets the blob contents and properties if the blob has not been modified since the time specified.
+ Use this method if you have cached the contents of a blob and want to avoid retrieving the blob
+ if it has not changed since the last time you retrieved it.
+
+ The properties of the blob obtained from an earlier call to GetBlob. This
+ parameter is updated by the call if the blob has been modified
+ Contains the stream to which the contents of the blob are written if it has been
+ modified
+ Should the blob be gotten in pieces. This requires more round-trips, but will retry smaller pieces in case of failure.
+ true if the blob has been modified, false otherwise
+
+
+
+ Get the properties of the blob if it exists.
+ This method is also the simplest way to check if a blob exists.
+
+ The name of the blob
+ The properties of the blob if it exists. null otherwise.
+ The properties for the contents of the blob are not set
+
+
+
+ Set the metadata of an existing blob.
+
+ The blob properties object whose metadata is to be updated
+
+
+
+ Set the metadata of an existing blob if it has not been modified since it was last retrieved.
+
+ The blob properties object whose metadata is to be updated.
+ Typically obtained by a previous call to GetBlob or GetBlobProperties
+ true if the blob metadata was updated. false if it was not updated because the blob
+ has been modified
+
+
+
+ Delete a blob with the given name
+
+ The name of the blob
+ true if the blob exists and was successfully deleted, false if the blob does not exist
+
+
+
+ Delete a blob with the given name if the blob has not been modified since it was last obtained.
+ Use this method for optimistic concurrency to avoid deleting a blob that has been modified since
+ the last time you retrieved it
+
+ A blob object (typically previously obtained from a GetBlob call)
+ This out parameter is set to true if the blob was not deleted because
+ it was modified
+ true if the blob exists and was successfully deleted, false if the blob does not exist or was
+ not deleted because the blob was modified.
+
+
+
+ Enumerates all blobs with a given prefix.
+
+
+ If true common prefixes with "/" as seperator
+ The list of blob properties and common prefixes
+
+
+
+ The time out for each request to the storage service.
+
+
+
+
+ The retry policy used for retrying requests
+
+
+
+
+ The base URI of the blob storage service
+
+
+
+
+ The name of the storage account
+
+
+
+
+ The name of the blob container.
+
+
+
+
+ Indicates whether to use/generate path-style or host-style URIs
+
+
+
+
+ The URI of the container
+
+
+
+
+ The timestamp for last modification of container.
+
+
+
+
+ Create the container with the specified access control if it does not exist
+
+ The metadata for the container. Can be null to indicate no metadata
+ The access control (public or private) with which to create the container
+ true if the container was created. false if the container already exists
+
+
+
+ Get the properties for the container if it exists.
+
+ The metadata for the container if it exists, null otherwise
+
+
+
+ Get the access control permissions associated with the container.
+
+
+
+
+
+ Get the access control permissions associated with the container.
+
+
+
+
+
+ Create a new blob or overwrite an existing blob.
+
+
+ The properties of the blob
+ The contents of the blob
+ Should this request overwrite an existing blob ?
+ true if the blob was created. false if the blob already exists and was set to false
+ The LastModifiedTime property of is set as a result of this call
+
+
+
+ Updates an existing blob if it has not been modified since the specified time which is typically
+ the last modified time of the blob when you retrieved it.
+ Use this method to implement optimistic concurrency by avoiding clobbering changes to the blob
+ made by another writer.
+
+ The properties of the blob. This object should be one previously
+ obtained from a call to GetBlob or GetBlobProperties and have its LastModifiedTime property set.
+ The contents of the blob. The contents of the blob should be readable
+ true if the blob was updated. false if the blob has changed since the last time
+ The LastModifiedTime property of is set as a result of this call
+
+
+
+ Get the blob contents and properties if the blob exisits
+
+ The name of the blob
+ Object in which the contents are returned.
+ This object should contain a writable stream or should be a default constructed object.
+ Should the blob be gotten in pieces. This requires more round-trips, but will retry smaller piecs in case of failure.
+ The properties of the blob if the blob exists.
+
+
+
+ Gets the blob contents and properties if the blob has not been modified since the time specified.
+ Use this method if you have cached the contents of a blob and want to avoid retrieving the blob
+ if it has not changed since the last time you retrieved it.
+
+ The properties of the blob obtained from an earlier call to GetBlob. This
+ parameter is updated by the call if the blob has been modified
+ Contains the stream to which the contents of the blob are written if it has been
+ modified
+ Should the blob be gotten in pieces. This requires more round-trips, but will retry smaller piecs in case of failure.
+ true if the blob has been modified, false otherwise
+
+
+
+ Get the properties of the blob if it exists.
+ This method is also the simplest way to check if a blob exists.
+
+ The name of the blob
+ The properties of the blob if it exists. null otherwise.
+ The properties for the contents of the blob are not set
+
+
+
+ Set the metadata of an existing blob.
+
+ The blob properties object whose metadata is to be updated
+
+
+
+ Set the metadata of an existing blob if it has not been modified since it was last retrieved.
+
+ The blob properties object whose metadata is to be updated.
+ Typically obtained by a previous call to GetBlob or GetBlobProperties
+
+
+
+ Delete a blob with the given name
+
+ The name of the blob
+ true if the blob exists and was successfully deleted, false if the blob does not exist
+
+
+
+ Delete a blob with the given name if the blob has not been modified since it was last obtained.
+ Use this method for optimistic concurrency to avoid deleting a blob that has been modified since
+ the last time you retrieved it
+
+ A blob object (typically previously obtained from a GetBlob call)
+ This out parameter is set to true if the blob was not deleted because
+ it was modified
+ true if the blob exists and was successfully deleted, false if the blob does not exist or was
+ not deleted because the blob was modified.
+
+
+
+ Enumerates all blobs with a given prefix.
+
+
+ If true common prefixes with "/" as seperator
+ The list of blob properties and common prefixes
+
+
+
+ Uploads a blob in chunks.
+
+
+
+
+
+
+
+
+
+ Helper method used for getting blobs, ranges of blobs and blob properties.
+
+ Name of the blob
+ The output stream to write blob data to. Can be null if only retrieving blob properties
+ The If-None-Match header. Used to avoid downloading blob data if the blob has not changed
+ The If-Match header. Used to ensure that all chunks of the blob are of the same blob
+ The offset of the blob data to begin downloading from. Set to 0 to download all data.
+ The length of the blob data to download. Set to 0 to download all data
+ Query paramters to add to the request.
+ Whether the blob had been modfied with respect to the
+
+
+
+
+ Helper class for loading values from an XML segment
+
+
+
+
+ The entry point of the queue storage API
+
+
+
+
+ Factory method for QueueStorage
+
+ The base URI of the queue service
+ If true, path-style URIs (http://baseuri/accountname/containername/objectname) are used.
+ If false host-style URIs (http://accountname.baseuri/containername/objectname) are used,
+ where baseuri is the URI of the service.
+ If null, the choice is made automatically: path-style URIs if host name part of base URI is an
+ IP addres, host-style otherwise.
+ The name of the storage account
+ Authentication key used for signing requests
+ A newly created QueueStorage instance
+
+
+
+ Get a reference to a Queue object with a specified name. This method does not make a call to
+ the queue service.
+
+ The name of the queue
+ A newly created queue object
+
+
+
+ Lists the queues within the account.
+
+ A list of queues
+
+
+
+ Lists the queues within the account that start with the given prefix.
+
+ If prefix is null returns all queues.
+ A list of queues.
+
+
+
+ The default timeout
+
+
+
+
+ The default retry policy
+
+
+
+
+ The time out for each request to the storage service.
+
+
+
+
+ The retry policy used for retrying requests
+
+
+
+
+ The base URI of the blob storage service
+
+
+
+
+ The name of the storage account
+
+
+
+
+ Indicates whether to use/generate path-style or host-style URIs
+
+
+
+
+ Objects of this class represent a single message in the queue.
+
+
+
+
+ The maximum message size in bytes.
+
+
+
+
+ The maximum amount of time a message is kept in the queue. Max value is 7 days.
+ Value is given in seconds.
+
+
+
+
+ This constructor is not publicly exposed.
+
+
+
+
+ Creates a message and initializes the content of the message to be the specified string.
+
+ A string representing the contents of the message.
+
+
+
+ Creates a message and given the specified byte contents.
+ In this implementation, regardless of whether an XML or binary data is passed into this
+ function, message contents are converted to base64 before passing the data to the queue service.
+ When calculating the size of the message, the size of the base64 encoding is thus the important
+ parameter.
+
+
+
+
+
+ Returns the the contents of the message as a string.
+
+
+
+
+ Returns the content of the message as a byte array
+
+
+
+
+ When calling the Get() operation on a queue, the content of messages
+ returned in the REST protocol are represented as Base64-encoded strings.
+ This internal function transforms the Base64 representation into a byte array.
+
+ The Base64-encoded string.
+
+
+
+ Internal method used for creating the XML that becomes part of a REST request
+
+
+
+
+ A unique ID of the message as returned from queue operations.
+
+
+
+
+ When a message is retrieved from a queue, a PopReceipt is returned. The PopReceipt is used when
+ deleting a message from the queue.
+
+
+
+
+ The point in time when the message was put into the queue.
+
+
+
+
+ A message's expiration time.
+
+
+
+
+ The point in time when a message becomes visible again after a Get() operation was called
+ that returned the message.
+
+
+
+
+ Queues in the storage client library expose a functionality for listening for incoming messages.
+ If a message is put into a queue, a corresponding event is issued and this delegate is called. This functionality
+ is implemented internally in this library by periodically polling for incoming messages.
+
+ The queue that has received a new event.
+ The event argument containing the message.
+
+
+
+ The argument class for the MessageReceived event.
+
+
+
+
+ The message itself.
+
+
+
+
+ Constructor for creating a message received argument.
+
+
+
+
+
+ The message received by the queue.
+
+
+
+
+ The approximated amount of messages in the queue.
+
+
+
+
+ Metadata for the queue in the form of name-value pairs.
+
+
+
+
+ Objects of this class represent a queue in a user's storage account.
+
+
+
+
+ The name of the queue.
+
+
+
+
+ The user account this queue lives in.
+
+
+
+
+ This constructor is only called by subclasses.
+
+
+
+
+ Creates a queue in the specified storage account.
+
+ true if a queue with the same name already exists.
+ true if the queue was successfully created.
+
+
+
+ Creates a queue in the specified storage account.
+
+ true if the queue was successfully created.
+
+
+
+ Determines whether a queue with the same name already exists in an account.
+
+ true if a queue with the same name already exists.
+
+
+
+ Deletes the queue. The queue will be deleted regardless of whether there are messages in the
+ queue or not.
+
+ true if the queue was successfully deleted.
+
+
+
+ Sets the properties of a queue.
+
+ The queue's properties to set.
+ true if the properties were successfully written to the queue.
+
+
+
+ Retrieves the queue's properties.
+
+ The queue's properties.
+
+
+
+ Retrieves the approximate number of messages in a queue.
+
+ The approximate number of messages in this queue.
+
+
+
+ Puts a message in the queue.
+
+ The message to store in the queue.
+ true if the message has been successfully enqueued.
+
+
+
+ Puts a message in the queue.
+
+ The message to store in the queue.
+ The time to live for the message in seconds.
+ true if the message has been successfully enqueued.
+
+
+
+ Retrieves a message from the queue.
+
+ The message retrieved or null if the queue is empty.
+
+
+
+ Retrieves a message and sets its visibility timeout to the specified number of seconds.
+
+ Visibility timeout of the message retrieved in seconds.
+
+
+
+
+ Tries to retrieve the given number of messages.
+
+ Maximum number of messages to retrieve.
+ The list of messages retrieved.
+
+
+
+ Tries to retrieve the given number of messages.
+
+ Maximum number of messages to retrieve.
+ The visibility timeout of the retrieved messages in seconds.
+ The list of messages retrieved.
+
+
+
+ Get a message from the queue but do not actually dequeue it. The message will remain visible
+ for other parties requesting messages.
+
+ The message retrieved or null if there are no messages in the queue.
+
+
+
+ Tries to get a copy of messages in the queue without actually dequeuing the messages.
+ The messages will remain visible in the queue.
+
+ Maximum number of message to retrieve.
+ The list of messages retrieved.
+
+
+
+ Deletes a message from the queue.
+
+ The message to retrieve with a valid popreceipt.
+ true if the operation was successful.
+
+
+
+ Delete all messages in a queue.
+
+ true if all messages were deleted successfully.
+
+
+
+ The default time interval between polling the queue for messages.
+ Polling is only enabled if the user has called StartReceiving().
+
+
+
+
+ Starts the automatic reception of messages.
+
+ true if the operation was successful.
+
+
+
+ Stop the automatic reception of messages.
+
+
+
+
+ The name of the queue exposed as a public property.
+
+
+
+
+ The account info object this queue lives in -- exposed as an internal property.
+
+
+
+
+ Indicates whether to use/generate path-style or host-style URIs
+
+
+
+
+ The URI of the queue
+
+
+
+
+ The retry policy used for retrying requests; this is the retry policy of the
+ storage account where this queue was created
+
+
+
+
+ The timeout of requests.
+
+
+
+
+ The poll interval in milliseconds. If not explicitly set, this defaults to
+ the DefaultPollInterval.
+
+
+
+
+ The event users subscribe to in order to automatically receive messages
+ from a queue.
+
+
+
+
+ This delegate define the shape of a retry policy. A retry policy will invoke the given
+ as many times as it wants to in the face of
+ retriable StorageServerExceptions.
+
+ The action to retry
+
+
+
+
+ Provides definitions for some standard retry policies.
+
+
+
+
+ Policy that does no retries i.e., it just invokes exactly once
+
+ The action to retry
+ The return value of
+
+
+
+ Policy that retries a specified number of times with a specified fixed time interval between retries
+
+ The number of times to retry. Should be a non-negative number
+ The time interval between retries. Use TimeSpan.Zero to specify immediate
+ retries
+
+ When is 0 and is
+ TimeSpan.Zero this policy is equivalent to the NoRetry policy
+
+
+
+ Policy that retries a specified number of times with a randomized exponential backoff scheme
+
+ The number of times to retry. Should be a non-negative number.
+ The multiplier in the exponential backoff scheme
+
+ For this retry policy, the minimum amount of milliseconds between retries is given by the
+ StandardMinBackoff constant, and the maximum backoff is predefined by the StandardMaxBackoff constant.
+ Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.
+
+
+
+ Policy that retries a specified number of times with a randomized exponential backoff scheme
+
+ The number of times to retry. Should be a non-negative number
+ The multiplier in the exponential backoff scheme
+ The minimum backoff interval
+ The maximum backoff interval
+
+ For this retry policy, the minimum amount of milliseconds between retries is given by the
+ minBackoff parameter, and the maximum backoff is predefined by the maxBackoff parameter.
+ Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.
+
+
+
+ Access control for containers
+
+
+
+
+ The properties of a blob.
+ No member of this class makes a storage service request.
+
+
+
+
+ Construct a new BlobProperties object
+
+ The name of the blob
+
+
+
+ Name of the blob
+
+
+
+
+ URI of the blob
+
+
+
+
+ Content encoding of the blob if it set, null otherwise.
+
+
+
+
+ Content Type of the blob if it is set, null otherwise.
+
+
+
+
+ Content Language of the blob if it is set, null otherwise.
+
+
+
+
+ The length of the blob content, null otherwise.
+
+
+
+
+ Metadata for the blob in the form of name-value pairs.
+
+
+
+
+ The last modified time for the blob.
+
+
+
+
+ The ETag of the blob. This is an identifier assigned to the blob by the storage service
+ and is used to distinguish contents of two blobs (or versions of the same blob).
+
+
+
+
+ The properties of a container.
+ No member of this class makes a storage service request.
+
+
+
+
+ The contents of the Blob in various forms.
+
+
+
+
+ Construct a new BlobContents object from a stream.
+
+ The stream to/from which blob contents are written/read. The
+ stream should be seekable in order for requests to be retried.
+
+
+
+ Construct a new BlobContents object from a byte array.
+
+ The byte array to/from which contents are written/read.
+
+
+
+ Get the contents of a blob as a byte array.
+
+
+
+
+ Get the contents of a blob as a stream.
+
+
+
+
+ Class representing some important table storage constants.
+
+
+
+
+ Internal constant for querying tables.
+
+
+
+
+ Internal constant for querying tables.
+
+
+
+
+ The maximum size of strings per property/column is 64 kB (that is 32k characters.)
+ Note: This constant is smaller for the development storage table service.
+
+
+
+
+ One character in the standard UTF-16 character presentation is 2 bytes.
+ Note: This constant is smaller for the development storage table service.
+
+
+
+
+ We want to prevent users from the pitfall of mixing up Utc and local time.
+ Because of this we add some time to the minimum supported datetime.
+ As a result, there will be no error condition from the server even
+ if a user converts the minimum supported date time to a local time and
+ stores this in a DateTime field.
+ The local development storage support the SQL range of dates which is narrower than the
+ one for the table storage service and so we use that value here.
+
+
+
+
+ API entry point for using structured storage. The underlying usage pattern is designed to be
+ similar to the one used in blob and queue services in this library.
+ Users create a TableStorage object by calling the static Create() method passing account credential
+ information to this method. The TableStorage object can then be used to create, delete and list tables.
+ There are two methods to get DataServiceContext objects that conform to the appropriate security scheme.
+ The first way is to call the GetDataServiceContext() method on TableStorage objects. The naming is again
+ chosen to conform to the convention in the other APIs for blob and queue services in this library.
+ This class can also be used as an adapter pattern. I.e., DataServiceContext objects can be created
+ independnt from a TableStorage object. Calling the Attach() method will make sure that the appropriate
+ security signing is used on these objects. This design was chosen to support various usage patterns that
+ might become necessary for autogenerated code.
+
+
+
+
+ The default retry policy
+
+
+
+
+ Creates a TableStorage service object. This object is the entry point into the table storage API.
+
+ The base URI of the table storage service.
+ Type of URI scheme used.
+ The account name.
+ Base64 encoded version of the key.
+
+
+
+
+ Creates a TableStorage object.
+
+
+
+
+ Infers a list of tables from a DataServiceContext-derived type and makes sure
+ those tables exist in the given service. The table endpoint information is retrieved from the
+ standard configuration settings.
+
+
+ Tables are inferred by finding all the public properties of type IQueryable<T> in
+ the provided type, where T is a type with an ID (in the case of table storage, this means it either
+ has a [DataServiceKey("PartitionKey", "RowKey")] attribute in the class, or derives from
+ the TableStorageEntity class included in this sample library (which in turn has that attribute).
+
+
+
+
+ Infers a list of tables from a DataServiceContext-derived type and makes sure
+ those tables exist in the given service.
+
+ The DataServiceContext type from which the tables are inferred.
+ A configuration string that is used to determine the table storage endpoint.
+
+
+
+ Infers a list of tables from a DataServiceContext-derived type and makes sure
+ those tables exist in the given service.
+
+ The type of the DataServiceContext.
+ An object containing information about the table storage endpoint to be used.
+
+
+
+ Creates a DataServiceContext object that takes care of implementing the table storage signing process.
+
+
+
+
+ If the adaptor pattern with Attach() shall be used, this function can be used to generate the
+ table service base Uri depending on the path style syntax.
+
+
+
+
+ If the adaptor pattern with Attach() shall be used, this function can be used to generate the
+ table service base Uri depending on the path style syntax.
+
+
+
+
+ If DataServiceContext objects are created at different places, this method can be called to configure the
+ DataServiceContext object to implement the required security scheme.
+
+
+
+
+ Lists all the tables under this service's URL
+
+
+
+
+ Creates a new table in the service
+
+ The name of the table to be created
+
+
+
+ Tries to create a table with the given name.
+ The main difference to the CreateTable method is that this function first queries the
+ table storage service whether the table already exists, before it tries to actually create
+ the table. The reason is that this
+ is more lightweight for the table storage service than always trying to create a table that
+ does already exist. Furthermore, as we expect that applications don't really randomly create
+ tables, the additional roundtrip that is required for creating the table is necessary only very
+ rarely.
+
+ The name of the table.
+ True if the operation was completed successfully. False if the table already exists.
+
+
+
+ Checks whether a table with the same name already exists.
+
+ The name of the table to check.
+ True iff the table already exists.
+
+
+
+ Deletes a table from the service.
+
+ The name of the table to be deleted
+
+
+
+ Tries to delete the table with the given name.
+
+ The name of the table to delete.
+ True if the table was successfully deleted. False if the table does not exists.
+
+
+
+ The retry policy used for retrying requests
+
+
+
+
+ The base URI of the table storage service
+
+
+
+
+ The name of the storage account
+
+
+
+
+ Indicates whether to use/generate path-style or host-style URIs
+
+
+
+
+ The base64 encoded version of the key.
+
+
+
+
+ Checks whether the exception is or contains a DataServiceClientException and extracts the
+ returned http status code and extended error information.
+
+ The exception from which to extract information
+ The Http status code for the exception
+ Extended error information including storage service specific
+ error code and error message
+ True if the exception is or contains a DataServiceClientException.
+
+
+
+ Checks whether the exception is or contains a DataServiceClientException and extracts the
+ returned http status code.
+
+ The exception from which to extract information
+ The Http status code for the exception
+ True if the exception is or contains a DataServiceClientException.
+
+
+
+ Checks whether the exception is either a DataServiceClientException, a DataServiceQueryException or a
+ DataServiceRequestException.
+
+
+
+
+ Only certain classes of errors should be retried. This method evaluates an exception
+ and returns whether this class of exception can be retried.
+
+ The exception to analyze.
+ The HttpStatusCode retrieved from the exception.
+
+
+
+ Overload that does not retrun the HttpStatusCode.
+
+
+
+
+ Checks whether the string can be inserted in a table storage table. Throws an exception if
+ this is not the case.
+
+
+
+
+
+ Checks whether the string can be inserted into a table storage table.
+
+
+
+
+ Creates a table with the specified name.
+
+ The name of the table.
+
+
+
+ The table name.
+
+
+
+
+ This class represents an entity (row) in a table in table storage.
+
+
+
+
+ Creates a TableStorageEntity object.
+
+
+
+
+ Creates a TableStorageEntity object.
+
+
+
+
+ Compares to entities.
+
+
+
+
+ Computes a HashCode for this object.
+
+
+
+
+ The partition key of a table entity. The concatenation of the partition key
+ and row key must be unique per table.
+
+
+
+
+ The row key of a table entity.
+
+
+
+
+ This class can be used for handling continuation tokens in TableStorage.
+
+
+
+
+
+ Objects of this class can be created using this constructor directly or by
+ calling a factory method on the TableStorageDataServiceContext class
+
+
+
+
+ Objects of this class can be created using this constructor directly or by
+ calling a factory method on the TableStorageDataServiceContext class
+
+
+
+
+ Normal Execute() on the query without retry. Just maps to _query.Execute().
+
+
+
+
+
+ Calling Execute() on the query with the current retry policy.
+
+ An IEnumerable respresenting the results of the query.
+
+
+
+ Calling Execute() on the query with the current retry policy.
+
+ The retry policy to be used for this request.
+ An IEnumerable respresenting the results of the query.
+
+
+
+ Returns all results of the query and hides the complexity of continuation if
+ this is desired by a user. Users should be aware that this operation can return
+ many objects. Uses no retries.
+ Important: this function does not call Execute immediately. Instead, it calls Execute() on
+ the query only when the result is enumerated. This is a difference to the normal
+ Execute() and Execute() with retry method.
+
+ An IEnumerable representing the results of the query.
+
+
+
+ Returns all results of the query and hides the complexity of continuation if
+ this is desired by a user. Users should be aware that this operation can return
+ many objects. This operation also uses the current retry policy.
+ Important: this function does not call Execute immediately. Instead, it calls Execute() on
+ the query only when the result is enumerated. This is a difference to the normal
+ Execute() and Execute() with retry method.
+
+ An IEnumerable representing the results of the query.
+
+
+
+ Returns all results of the query and hides the complexity of continuation if
+ this is desired by a user. Users should be aware that this operation can return
+ many objects.
+ Important: this function does not call Execute immediately. Instead, it calls Execute() on
+ the query only when the result is enumerated. This is a difference to the normal
+ Execute() and Execute() with retry method.
+
+ Determines whether to use retries or not.
+ An IEnumerable representing the results of the query.
+
+
+
+ Gets the underlying normal query object.
+
+
+
+
+ The retry policy used for retrying requests
+
+
+
+
+ The table storage-specific DataServiceContext class. It adds functionality for handling
+ the authentication process required by the table storage service.
+
+
+
+
+ Creates a DataServiceContext object and configures it so that it can be used with the table storage service.
+
+ The root URI of the service.
+ The account name.
+ The shared key associated with this service.
+
+
+
+ Creates a DataServiceContext object and configures it so that it can be used with the table storage service.
+
+ A StorageAccountInfo object containing information about how to access the table storage service.
+
+
+
+ Creates a DataServiceContext object and configures it so that it can be used with the table storage service.
+ Information on the table storage endpoint is retrieved by accessing configuration settings in the app config section
+ of a Web.config or app config file, or by accessing settings in cscfg files.
+
+
+
+
+ Calls the SaveChanges() method and applies retry semantics.
+
+
+
+
+ Calls the SaveChanges() method and applies retry semantics.
+
+
+
+
+ Callback method called whenever a request is sent to the table service. This
+ is where the signing of the request takes place.
+
+
+
+
+ The retry policy used for retrying requests
+
+
+
+
+ Helper class to avoid long-lived references to context objects
+
+
+ Need to be careful not to maintain a reference to the context
+ object from the auth adapter, since the adapter is probably
+ long-lived and the context is not. This intermediate helper
+ class is the one subscribing to context events, so when the
+ context can be collected then this will be collectable as well.
+
+
+
+
+ The retry policies for blobs and queues deal with special StorageClient and StorageServer exceptions.
+ In case of tables, we don't want to return these exceptions but instead the normal data service
+ exception. This class serves as a simple wrapper for these exceptions, and indicates that we
+ need retries.
+ Data service exceptions are stored as inner exceptions.
+
+
+
+
+ Creates a TableRetryWrapperException object.
+
+
+
+
+ Creates a TableRetryWrapperException object.
+
+
+
+
+ Creates a TableRetryWrapperException object.
+
+
+
+
+ Creates a TableRetryWrapperException object.
+
+
+
+
+ Creates a TableRetryWrapperException object.
+
+
+
+
+ Objects of this class encapsulate information about a storage account and endpoint configuration.
+ Associated with a storage account is the account name, the base URI of the account and a shared key.
+
+
+
+
+ The default configuration string in configuration files for setting the queue storage endpoint.
+
+
+
+
+ The default configuration string in configuration files for setting the blob storage endpoint.
+
+
+
+
+ The default configuration string in configuration files for setting the table storage endpoint.
+
+
+
+
+ The default configuration string in configuration files for setting the storage account name.
+
+
+
+
+ The default configuration string in configuration files for setting the shared key associated with a storage account.
+
+
+
+
+ The default configuration string in configuration files for setting the UsePathStyleUris option.
+
+
+
+
+ The default prefix string in application config and Web.config files to indicate that this setting should be looked up
+ in the fabric's configuration system.
+
+
+
+
+ Constructor for creating account info objects.
+
+ The account's base URI.
+ If true, path-style URIs (http://baseuri/accountname/containername/objectname) are used.
+ If false host-style URIs (http://accountname.baseuri/containername/objectname) are used,
+ where baseuri is the URI of the service..
+ If null, the choice is made automatically: path-style URIs if host name part of base URI is an
+ IP addres, host-style otherwise.
+ The account name.
+ The account's shared key.
+
+
+
+ Constructor for creating account info objects.
+
+ The account's base URI.
+ If true, path-style URIs (http://baseuri/accountname/containername/objectname) are used.
+ If false host-style URIs (http://accountname.baseuri/containername/objectname) are used,
+ where baseuri is the URI of the service.
+ If null, the choice is made automatically: path-style URIs if host name part of base URI is an
+ IP addres, host-style otherwise.
+ The account name.
+ The account's shared key.
+ true if it shall be allowed to only set parts of the StorageAccountInfo properties.
+
+
+
+ Retrieves account settings for the queue service from the default settings.
+
+
+
+
+ Retrieves account settings for the queue service from the default settings.
+ Throws an exception in case of incomplete settings.
+
+
+
+
+ Retrieves account settings for the table service from the default settings.
+
+
+
+
+ Retrieves account settings for the table service from the default settings.
+ Throws an exception in case of incomplete settings.
+
+
+
+
+ Retrieves account settings for the blob service from the default settings.
+
+
+
+
+ Retrieves account settings for the blob service from the default settings.
+ Throws an exception in case of incomplete settings.
+
+
+
+
+ Gets settings from default configuration names except for the endpoint configuration string.
+
+
+
+
+ Gets settings from default configuration names except for the endpoint configuration string. Throws an exception
+ in the case of incomplete settings.
+
+
+
+
+ Gets a configuration setting from application settings in the Web.config or App.config file.
+ When running in a hosted environment, configuration settings are read from .cscfg
+ files.
+
+
+
+
+ Retrieves account information settings from configuration settings. First, the implementation checks for
+ settings in an application config section of an app.config or Web.config file. These values are overwritten
+ if the same settings appear in a .csdef file.
+ The implementation also supports indirect settings. In this case, indirect settings overwrite all other settings.
+
+ Configuration string for the account name.
+ Configuration string for the key.
+ Configuration string for the endpoint.
+ Configuration string for the path style.
+ If false, an exception is thrown if not all settings are available.
+ StorageAccountInfo object containing the retrieved settings.
+
+
+
+ Checks whether all essential properties of this object are set. Only then, the account info object
+ should be used in ohter APIs of this library.
+
+
+
+
+
+ Checks whether this StorageAccountInfo object is complete in the sense that all properties are set.
+
+
+
+
+ The base URI of the account.
+
+
+
+
+ The account name.
+
+
+
+
+ The account's key.
+
+
+
+
+ If set, returns the UsePathStyleUris properties. If the property has not been explicitly set,
+ the implementation tries to derive the correct value from the base URI.
+
+
+
+
+ Get a reference to a Queue object with a specified name. This method does not make a call to
+ the queue service.
+
+ The name of the queue
+ A newly created queue object
+
+
+
+ Lists all queues with a given prefix within an account.
+
+
+ The list of queue names.
+
+
+
+ Lists the queues within the account.
+
+ A list of queues
+
+
+
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/AspProviders.dll b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/AspProviders.dll
new file mode 100644
index 0000000..467dc56
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/AspProviders.dll differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/AspProviders.pdb b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/AspProviders.pdb
new file mode 100644
index 0000000..fb57601
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/AspProviders.pdb differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/StorageClient.dll b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/StorageClient.dll
new file mode 100644
index 0000000..8376e42
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/StorageClient.dll differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/StorageClient.pdb b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/StorageClient.pdb
new file mode 100644
index 0000000..5f8089a
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/bin/Release/StorageClient.pdb differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.csproj.FileListAbsolute.txt b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.csproj.FileListAbsolute.txt
new file mode 100644
index 0000000..db40c9d
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.csproj.FileListAbsolute.txt
@@ -0,0 +1,16 @@
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\bin\Debug\AspProviders.dll
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\bin\Debug\AspProviders.pdb
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\bin\Debug\StorageClient.dll
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\bin\Debug\StorageClient.pdb
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\bin\Debug\StorageClient.xml
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\obj\Debug\ResolveAssemblyReference.cache
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\obj\Debug\AspProviders.dll
+G:\TrainingKits\WindowsAzurePlatformKit\Labs\BuildingMVCAppsWithWindowsAzure\Assets\AspProviders\obj\Debug\AspProviders.pdb
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Debug\AspProviders.dll
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Debug\AspProviders.pdb
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Debug\StorageClient.dll
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Debug\StorageClient.pdb
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Debug\StorageClient.xml
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\obj\Debug\ResolveAssemblyReference.cache
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\obj\Debug\AspProviders.dll
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\obj\Debug\AspProviders.pdb
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.dll b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.dll
new file mode 100644
index 0000000..0b14b98
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.dll differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.pdb b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.pdb
new file mode 100644
index 0000000..7fa2cbe
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/AspProviders.pdb differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/ResolveAssemblyReference.cache b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/ResolveAssemblyReference.cache
new file mode 100644
index 0000000..50e9397
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Debug/ResolveAssemblyReference.cache differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.csproj.FileListAbsolute.txt b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.csproj.FileListAbsolute.txt
new file mode 100644
index 0000000..9e5632d
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.csproj.FileListAbsolute.txt
@@ -0,0 +1,7 @@
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Release\AspProviders.dll
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Release\AspProviders.pdb
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Release\StorageClient.dll
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\bin\Release\StorageClient.pdb
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\obj\Release\ResolveAssemblyReference.cache
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\obj\Release\AspProviders.dll
+F:\projects\dotnet35\VBParser80\AzureStoreAsp\Assets\AspProviders\obj\Release\AspProviders.pdb
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.dll b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.dll
new file mode 100644
index 0000000..467dc56
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.dll differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.pdb b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.pdb
new file mode 100644
index 0000000..fb57601
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/AspProviders.pdb differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/ResolveAssemblyReference.cache b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/ResolveAssemblyReference.cache
new file mode 100644
index 0000000..03b8949
Binary files /dev/null and b/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/obj/Release/ResolveAssemblyReference.cache differ
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/StorageClient/Authentication.cs b/aspclassiccompiler/AzureStoreAsp/Assets/StorageClient/Authentication.cs
new file mode 100644
index 0000000..b4eeca5
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/StorageClient/Authentication.cs
@@ -0,0 +1,525 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Net;
+using System.Web;
+using System.Security.Cryptography;
+using System.Globalization;
+using System.Diagnostics;
+
+
+namespace Microsoft.Samples.ServiceHosting.StorageClient
+{
+ ///
+ /// This type represents the different constituent parts that make up a resource Uri in the context of cloud services.
+ ///
+ public class ResourceUriComponents
+ {
+ ///
+ /// The account name in the URI.
+ ///
+ public string AccountName { get; set; }
+
+ ///
+ /// This is really the first component (delimited by '/') after the account name. Since it happens to
+ /// be a container name in the context of all our storage services (containers in blob storage,
+ /// queues in the queue service and table names in table storage), it's named as ContainerName to make it more
+ /// readable at the cost of slightly being incorrectly named.
+ ///
+ public string ContainerName { get; set; }
+
+ ///
+ /// The remaining string in the URI.
+ ///
+ public string RemainingPart { get; set; }
+
+ ///
+ /// Construct a ResourceUriComponents object.
+ ///
+ /// The account name that should become part of the URI.
+ /// The container name (container, queue or table name) that should become part of the URI.
+ /// Remaining part of the URI.
+ public ResourceUriComponents(string accountName, string containerName, string remainingPart)
+ {
+ this.AccountName = accountName;
+ this.ContainerName = containerName;
+ this.RemainingPart = remainingPart;
+ }
+
+ ///
+ /// Construct a ResourceUriComponents object.
+ ///
+ /// The account name that should become part of the URI.
+ /// The container name (container, queue or table name) that should become part of the URI.
+ public ResourceUriComponents(string accountName, string containerName)
+ : this(accountName, containerName, null)
+ {
+ }
+
+ ///
+ /// Construct a ResourceUriComponents object.
+ ///
+ /// The account name that should become part of the URI.
+ public ResourceUriComponents(string accountName)
+ : this(accountName, null, null)
+ {
+ }
+
+ ///
+ /// Construct a ResourceUriComponents object.
+ ///
+ public ResourceUriComponents()
+ {
+ }
+ }
+
+ internal static class MessageCanonicalizer
+ {
+ ///
+ /// An internal class that stores the canonicalized string version of an HTTP request.
+ ///
+ private class CanonicalizedString
+ {
+ private StringBuilder canonicalizedString = new StringBuilder();
+
+ ///
+ /// Property for the canonicalized string.
+ ///
+ internal string Value
+ {
+ get
+ {
+ return canonicalizedString.ToString();
+ }
+ }
+
+ ///
+ /// Constructor for the class.
+ ///
+ /// The first canonicalized element to start the string with.
+ internal CanonicalizedString(string initialElement)
+ {
+ canonicalizedString.Append(initialElement);
+ }
+
+ ///
+ /// Append additional canonicalized element to the string.
+ ///
+ /// An additional canonicalized element to append to the string.
+ internal void AppendCanonicalizedElement(string element)
+ {
+ canonicalizedString.Append(StorageHttpConstants.ConstChars.Linefeed);
+ canonicalizedString.Append(element);
+ }
+ }
+
+ ///
+ /// Create a canonicalized string out of HTTP request header contents for signing
+ /// blob/queue requests with the Shared Authentication scheme.
+ ///
+ /// The uri address of the HTTP request.
+ /// Components of the Uri extracted out of the request.
+ /// The method of the HTTP request (GET/PUT, etc.).
+ /// The content type of the HTTP request.
+ /// The date of the HTTP request.
+ /// Should contain other headers of the HTTP request.
+ /// A canonicalized string of the HTTP request.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase",
+ Justification = "Authentication algorithm requires canonicalization by converting to lower case")]
+ internal static string CanonicalizeHttpRequest(
+ Uri address,
+ ResourceUriComponents uriComponents,
+ string method,
+ string contentType,
+ string date,
+ NameValueCollection headers)
+ {
+ // The first element should be the Method of the request.
+ // I.e. GET, POST, PUT, or HEAD.
+ CanonicalizedString canonicalizedString = new CanonicalizedString(method);
+
+ // The second element should be the MD5 value.
+ // This is optional and may be empty.
+ string httpContentMD5Value = string.Empty;
+
+ // First extract all the content MD5 values from the header.
+ ArrayList httpContentMD5Values = HttpRequestAccessor.GetHeaderValues(headers, StorageHttpConstants.HeaderNames.ContentMD5);
+
+ // If we only have one, then set it to the value we want to append to the canonicalized string.
+ if (httpContentMD5Values.Count == 1)
+ {
+ httpContentMD5Value = (string)httpContentMD5Values[0];
+ }
+
+ canonicalizedString.AppendCanonicalizedElement(httpContentMD5Value);
+
+ // The third element should be the content type.
+ canonicalizedString.AppendCanonicalizedElement(contentType);
+
+ // The fourth element should be the request date.
+ // See if there's an storage date header.
+ // If there's one, then don't use the date header.
+ ArrayList httpStorageDateValues = HttpRequestAccessor.GetHeaderValues(headers, StorageHttpConstants.HeaderNames.StorageDateTime);
+ if (httpStorageDateValues.Count > 0)
+ {
+ date = null;
+ }
+
+ canonicalizedString.AppendCanonicalizedElement(date);
+
+ // Look for header names that start with StorageHttpConstants.HeaderNames.PrefixForStorageHeader
+ // Then sort them in case-insensitive manner.
+ ArrayList httpStorageHeaderNameArray = new ArrayList();
+ foreach (string key in headers.Keys)
+ {
+ if (key.ToLowerInvariant().StartsWith(StorageHttpConstants.HeaderNames.PrefixForStorageHeader, StringComparison.Ordinal))
+ {
+ httpStorageHeaderNameArray.Add(key.ToLowerInvariant());
+ }
+ }
+
+ httpStorageHeaderNameArray.Sort();
+
+ // Now go through each header's values in the sorted order and append them to the canonicalized string.
+ foreach (string key in httpStorageHeaderNameArray)
+ {
+ StringBuilder canonicalizedElement = new StringBuilder(key);
+ string delimiter = ":";
+ ArrayList values = HttpRequestAccessor.GetHeaderValues(headers, key);
+
+ // Go through values, unfold them, and then append them to the canonicalized element string.
+ foreach (string value in values)
+ {
+ // Unfolding is simply removal of CRLF.
+ string unfoldedValue = value.Replace(StorageHttpConstants.ConstChars.CarriageReturnLinefeed, string.Empty);
+
+ // Append it to the canonicalized element string.
+ canonicalizedElement.Append(delimiter);
+ canonicalizedElement.Append(unfoldedValue);
+ delimiter = ",";
+ }
+
+ // Now, add this canonicalized element to the canonicalized header string.
+ canonicalizedString.AppendCanonicalizedElement(canonicalizedElement.ToString());
+ }
+
+ // Now we append the canonicalized resource element.
+ string canonicalizedResource = GetCanonicalizedResource(address, uriComponents);
+ canonicalizedString.AppendCanonicalizedElement(canonicalizedResource);
+
+ return canonicalizedString.Value;
+ }
+
+ internal static string GetCanonicalizedResource(Uri address, ResourceUriComponents uriComponents)
+ {
+ // Algorithem is as follows
+ // 1. Start with the empty string ("")
+ // 2. Append the account name owning the resource preceded by a /. This is not
+ // the name of the account making the request but the account that owns the
+ // resource being accessed.
+ // 3. Append the path part of the un-decoded HTTP Request-URI, up-to but not
+ // including the query string.
+ // 4. If the request addresses a particular component of a resource, like?comp=
+ // metadata then append the sub-resource including question mark (like ?comp=
+ // metadata)
+ StringBuilder canonicalizedResource = new StringBuilder(StorageHttpConstants.ConstChars.Slash);
+ canonicalizedResource.Append(uriComponents.AccountName);
+
+ // Note that AbsolutePath starts with a '/'.
+ canonicalizedResource.Append(address.AbsolutePath);
+
+ NameValueCollection queryVariables = HttpUtility.ParseQueryString(address.Query);
+ string compQueryParameterValue = queryVariables[StorageHttpConstants.QueryParams.QueryParamComp];
+ if (compQueryParameterValue != null)
+ {
+ canonicalizedResource.Append(StorageHttpConstants.ConstChars.QuestionMark);
+ canonicalizedResource.Append(StorageHttpConstants.QueryParams.QueryParamComp);
+ canonicalizedResource.Append(StorageHttpConstants.QueryParams.SeparatorForParameterAndValue);
+ canonicalizedResource.Append(compQueryParameterValue);
+ }
+
+ return canonicalizedResource.ToString();
+ }
+
+
+ ///
+ /// Canonicalize HTTP header contents.
+ ///
+ /// An HttpWebRequest object.
+ /// Components of the Uri extracted out of the request.
+ /// The canonicalized string of the given HTTP request's header.
+ internal static string CanonicalizeHttpRequest(HttpWebRequest request, ResourceUriComponents uriComponents)
+ {
+ return CanonicalizeHttpRequest(
+ request.Address, uriComponents, request.Method, request.ContentType, string.Empty, request.Headers);
+ }
+
+ ///
+ /// Creates a standard datetime string for the shared key lite authentication scheme.
+ ///
+ /// DateTime value to convert to a string in the expected format.
+ ///
+ internal static string ConvertDateTimeToHttpString(DateTime dateTime)
+ {
+ // On the wire everything should be represented in UTC. This assert will catch invalid callers who
+ // are violating this rule.
+ Debug.Assert(dateTime == DateTime.MaxValue || dateTime == DateTime.MinValue || dateTime.Kind == DateTimeKind.Utc);
+
+ // 'R' means rfc1123 date which is what the storage services use for all dates...
+ // It will be in the following format:
+ // Sun, 28 Jan 2008 12:11:37 GMT
+ return dateTime.ToString("R", CultureInfo.InvariantCulture);
+ }
+
+ private static string AppendStringToCanonicalizedString(StringBuilder canonicalizedString, string stringToAppend)
+ {
+ canonicalizedString.Append(StorageHttpConstants.ConstChars.Linefeed);
+ canonicalizedString.Append(stringToAppend);
+ return canonicalizedString.ToString();
+ }
+
+ internal static string CanonicalizeHttpRequestForSharedKeyLite(HttpWebRequest request, ResourceUriComponents uriComponents, string date)
+ {
+ StringBuilder canonicalizedString = new StringBuilder(date);
+ AppendStringToCanonicalizedString(canonicalizedString, MessageCanonicalizer.GetCanonicalizedResource(request.Address, uriComponents));
+
+ return canonicalizedString.ToString();
+ }
+ }
+
+ ///
+ /// Use this class to extract various header values from Http requests.
+ ///
+ public static class HttpRequestAccessor
+ {
+ ///
+ /// A helper function for extracting HTTP header values from a NameValueCollection object.
+ ///
+ /// A NameValueCollection object that should contain HTTP header name-values pairs.
+ /// Name of the header that we want to get values of.
+ /// A array list of values for the header. The values are in the same order as they are stored in the NameValueCollection object.
+ internal static ArrayList GetHeaderValues(NameValueCollection headers, string headerName)
+ {
+ ArrayList arrayOfValues = new ArrayList();
+ string[] values = headers.GetValues(headerName);
+
+ if (values != null)
+ {
+ foreach (string value in values)
+ {
+ // canonization formula requires the string to be left trimmed.
+ arrayOfValues.Add(value.TrimStart());
+ }
+ }
+
+ return arrayOfValues;
+ }
+
+
+ ///
+ /// Constructs an URI given all its constituents
+ ///
+ ///
+ /// This is the service endpoint in case of path-style URIs and a host suffix in case of host-style URIs
+ /// IMPORTANT: This does NOT include the service name or account name
+ ///
+ /// Uri constituents
+ /// Indicates whether to construct a path-style Uri (true) or host-style URI (false)
+ /// Full uri
+ public static Uri ConstructResourceUri(Uri endpoint, ResourceUriComponents uriComponents, bool pathStyleUri)
+ {
+ return pathStyleUri ?
+ ConstructPathStyleResourceUri(endpoint, uriComponents) :
+ ConstructHostStyleResourceUri(endpoint, uriComponents);
+ }
+
+ ///
+ /// Constructs a path-style resource URI given all its constituents
+ ///
+ private static Uri ConstructPathStyleResourceUri(Uri endpoint, ResourceUriComponents uriComponents)
+ {
+ StringBuilder path = new StringBuilder(string.Empty);
+ if (uriComponents.AccountName != null)
+ {
+ path.Append(uriComponents.AccountName);
+
+ if (uriComponents.ContainerName != null)
+ {
+ path.Append(StorageHttpConstants.ConstChars.Slash);
+ path.Append(uriComponents.ContainerName);
+
+ if (uriComponents.RemainingPart != null)
+ {
+ path.Append(StorageHttpConstants.ConstChars.Slash);
+ path.Append(uriComponents.RemainingPart);
+ }
+ }
+ }
+
+ return ConstructUriFromUriAndString(endpoint, path.ToString());
+ }
+
+ ///
+ /// Constructs a host-style resource URI given all its constituents
+ ///
+ private static Uri ConstructHostStyleResourceUri(Uri hostSuffix, ResourceUriComponents uriComponents)
+ {
+ if (uriComponents.AccountName == null)
+ {
+ // When there is no account name, full URI is same as hostSuffix
+ return hostSuffix;
+ }
+ else
+ {
+ // accountUri will be something like "http://accountname.hostSuffix/" and then we append
+ // container name and remaining part if they are present.
+ Uri accountUri = ConstructHostStyleAccountUri(hostSuffix, uriComponents.AccountName);
+ StringBuilder path = new StringBuilder(string.Empty);
+ if (uriComponents.ContainerName != null)
+ {
+ path.Append(uriComponents.ContainerName);
+
+ if (uriComponents.RemainingPart != null)
+ {
+ path.Append(StorageHttpConstants.ConstChars.Slash);
+ path.Append(uriComponents.RemainingPart);
+ }
+ }
+
+ return ConstructUriFromUriAndString(accountUri, path.ToString());
+ }
+ }
+
+
+ ///
+ /// Given the host suffix part, service name and account name, this method constructs the account Uri
+ ///
+ private static Uri ConstructHostStyleAccountUri(Uri hostSuffix, string accountName)
+ {
+ // Example:
+ // Input: serviceEndpoint="http://blob.windows.net/", accountName="youraccount"
+ // Output: accountUri="http://youraccount.blob.windows.net/"
+ Uri serviceUri = hostSuffix;
+
+ // serviceUri in our example would be "http://blob.windows.net/"
+ string accountUriString = string.Format(CultureInfo.InvariantCulture,
+ "{0}{1}{2}.{3}:{4}/",
+ serviceUri.Scheme,
+ Uri.SchemeDelimiter,
+ accountName,
+ serviceUri.Host,
+ serviceUri.Port);
+
+ return new Uri(accountUriString);
+ }
+
+ private static Uri ConstructUriFromUriAndString(
+ Uri endpoint,
+ string path)
+ {
+ // This is where we encode the url path to be valid
+ string encodedPath = HttpUtility.UrlPathEncode(path);
+ return new Uri(endpoint, encodedPath);
+ }
+ }
+
+ ///
+ /// Objects of this class contain the credentials (name and key) of a storage account.
+ ///
+ public class SharedKeyCredentials
+ {
+
+ ///
+ /// Create a SharedKeyCredentials object given an account name and a shared key.
+ ///
+ public SharedKeyCredentials(string accountName, byte[] key)
+ {
+ this.accountName = accountName;
+ this.key = key;
+ }
+
+ ///
+ /// Signs the request appropriately to make it an authenticated request.
+ /// Note that this method takes the URI components as decoding the URI components requires the knowledge
+ /// of whether the URI is in path-style or host-style and a host-suffix if it's host-style.
+ ///
+ public void SignRequest(HttpWebRequest request, ResourceUriComponents uriComponents)
+ {
+ if (request == null)
+ throw new ArgumentNullException("request");
+ string message = MessageCanonicalizer.CanonicalizeHttpRequest(request, uriComponents);
+ string computedBase64Signature = ComputeMacSha(message);
+ request.Headers.Add(StorageHttpConstants.HeaderNames.Authorization,
+ string.Format(CultureInfo.InvariantCulture,
+ "{0} {1}:{2}",
+ StorageHttpConstants.AuthenticationSchemeNames.SharedKeyAuthSchemeName,
+ accountName,
+ computedBase64Signature));
+ }
+
+ ///
+ /// Signs requests using the SharedKeyLite authentication scheme with is used for the table storage service.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Lite",
+ Justification = "Name of the authentication scheme in the REST protocol")]
+ public void SignRequestForSharedKeyLite(HttpWebRequest request, ResourceUriComponents uriComponents)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException("request");
+ }
+
+ // add the date header to the request
+ string dateString = MessageCanonicalizer.ConvertDateTimeToHttpString(DateTime.UtcNow);
+ request.Headers.Add(StorageHttpConstants.HeaderNames.StorageDateTime, dateString);
+
+ // compute the signature and add the authentication scheme
+ string message = MessageCanonicalizer.CanonicalizeHttpRequestForSharedKeyLite(request, uriComponents, dateString);
+ string computedBase64Signature = ComputeMacSha(message);
+ request.Headers.Add(StorageHttpConstants.HeaderNames.Authorization,
+ string.Format(CultureInfo.InvariantCulture,
+ "{0} {1}:{2}",
+ StorageHttpConstants.AuthenticationSchemeNames.SharedKeyLiteAuthSchemeName,
+ accountName,
+ computedBase64Signature));
+ }
+
+
+ private string ComputeMacSha(string canonicalizedString)
+ {
+ byte[] dataToMAC = System.Text.Encoding.UTF8.GetBytes(canonicalizedString);
+
+ using (HMACSHA256 hmacsha1 = new HMACSHA256(key))
+ {
+ return System.Convert.ToBase64String(hmacsha1.ComputeHash(dataToMAC));
+ }
+ }
+
+ private string accountName;
+ private byte[] key;
+ }
+}
\ No newline at end of file
diff --git a/aspclassiccompiler/AzureStoreAsp/Assets/StorageClient/BlobStorage.cs b/aspclassiccompiler/AzureStoreAsp/Assets/StorageClient/BlobStorage.cs
new file mode 100644
index 0000000..f9a3660
--- /dev/null
+++ b/aspclassiccompiler/AzureStoreAsp/Assets/StorageClient/BlobStorage.cs
@@ -0,0 +1,924 @@
+// ----------------------------------------------------------------------------------
+// Microsoft Developer & Platform Evangelism
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
+// ----------------------------------------------------------------------------------
+// The example companies, organizations, products, domain names,
+// e-mail addresses, logos, people, places, and events depicted
+// herein are fictitious. No association with any real company,
+// organization, product, domain name, email address, logo, person,
+// places, or events is intended or should be inferred.
+// ----------------------------------------------------------------------------------
+
+//
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Collections.Specialized;
+using System.Threading;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Diagnostics;
+
+[assembly:CLSCompliant(true)]
+
+// disable the generation of warnings for missing documentation elements for
+// public classes/members in this file
+#pragma warning disable 1591
+
+namespace Microsoft.Samples.ServiceHosting.StorageClient
+{
+
+ ///
+ /// This delegate define the shape of a retry policy. A retry policy will invoke the given
+ /// as many times as it wants to in the face of
+ /// retriable StorageServerExceptions.
+ ///
+ /// The action to retry
+ ///
+ public delegate void RetryPolicy(Action action);
+
+ #region Blob Storage API
+ ///
+ /// The entry point of the blob storage API
+ ///
+ public abstract class BlobStorage
+ {
+
+ ///
+ /// Factory method for BlobStorage
+ ///
+ /// The base URI of the blob storage service
+ /// If true, path-style URIs (http://baseuri/accountname/containername/objectname) are used.
+ /// If false host-style URIs (http://accountname.baseuri/containername/objectname) are used,
+ /// where baseuri is the URI of the service.
+ /// If null, the choice is made automatically: path-style URIs if host name part of base URI is an
+ /// IP addres, host-style otherwise.
+ /// The name of the storage account
+ /// Authentication key used for signing requests
+ /// A newly created BlobStorage instance
+ public static BlobStorage Create(
+ Uri baseUri,
+ bool? usePathStyleUris,
+ string accountName,
+ string base64Key
+ )
+ {
+ //We create a StorageAccountInfo and then extract the properties of that object.
+ //This is because the constructor of StorageAccountInfo does normalization of BaseUri.
+ StorageAccountInfo accountInfo = new StorageAccountInfo(
+ baseUri,
+ usePathStyleUris,
+ accountName,
+ base64Key
+ );
+ return new BlobStorageRest(
+ accountInfo.BaseUri,
+ accountInfo.UsePathStyleUris,
+ accountInfo.AccountName,
+ accountInfo.Base64Key
+ );
+ }
+
+ ///
+ /// Factory method for BlobStorage
+ ///
+ /// Account information
+ /// A newly created BlobStorage instance
+ public static BlobStorage Create(StorageAccountInfo accountInfo)
+ {
+ return new BlobStorageRest(
+ accountInfo.BaseUri,
+ accountInfo.UsePathStyleUris,
+ accountInfo.AccountName,
+ accountInfo.Base64Key
+ );
+ }
+
+
+ ///
+ /// Get a reference to a newly created BlobContainer object.
+ /// This method does not make any calls to the storage service.
+ ///
+ /// The name of the container
+ /// A reference to a newly created BlobContainer object
+ public abstract BlobContainer GetBlobContainer(string containerName);
+
+
+ ///
+ /// Lists the containers within the account.
+ ///
+ /// A list of containers
+ public abstract IEnumerable ListBlobContainers();
+
+ ///
+ /// The time out for each request to the storage service.
+ ///
+ public TimeSpan Timeout
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The retry policy used for retrying requests
+ ///
+ public RetryPolicy RetryPolicy
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The base URI of the blob storage service
+ ///
+ public Uri BaseUri
+ {
+ get
+ {
+ return this.baseUri;
+ }
+ }
+
+ ///
+ /// The name of the storage account
+ ///
+ public string AccountName
+ {
+ get
+ {
+ return this.accountName;
+ }
+ }
+
+ ///
+ /// Indicates whether to use/generate path-style or host-style URIs
+ ///
+ public bool UsePathStyleUris
+ {
+ get
+ {
+ return this.usePathStyleUris;
+ }
+ }
+
+ ///
+ /// The default timeout
+ ///
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
+ Justification = "TimeSpan is a non-mutable type")]
+ public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
+
+ ///
+ /// The default retry policy
+ ///
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
+ Justification = "RetryPolicy is a non-mutable type")]
+ public static readonly RetryPolicy DefaultRetryPolicy = RetryPolicies.NoRetry;
+
+
+ internal protected BlobStorage(Uri baseUri,
+ bool? usePathStyleUris,
+ string accountName,
+ string base64Key
+ )
+ {
+ this.baseUri = baseUri;
+ this.accountName = accountName;
+ this.Base64Key = base64Key;
+ if (usePathStyleUris == null)
+ this.usePathStyleUris = Utilities.StringIsIPAddress(baseUri.Host);
+ else
+ this.usePathStyleUris = usePathStyleUris.Value;
+
+ Timeout = DefaultTimeout;
+ RetryPolicy = DefaultRetryPolicy;
+ }
+
+ private bool usePathStyleUris;
+ private Uri baseUri;
+ private string accountName;
+ protected internal string Base64Key
+ {
+ get;
+ set;
+ }
+ }
+
+
+ ///
+ /// Provides definitions for some standard retry policies.
+ ///
+ public static class RetryPolicies
+ {
+
+ public static readonly TimeSpan StandardMinBackoff = TimeSpan.FromMilliseconds(100);
+ public static readonly TimeSpan StandardMaxBackoff = TimeSpan.FromSeconds(30);
+ private static readonly Random random = new Random();
+
+ ///
+ /// Policy that does no retries i.e., it just invokes exactly once
+ ///
+ /// The action to retry
+ /// The return value of
+ public static void NoRetry(Action action)
+ {
+ try
+ {
+ action();
+ }
+ catch (TableRetryWrapperException e)
+ {
+ throw e.InnerException;
+ }
+ }
+
+ ///
+ /// Policy that retries a specified number of times with a specified fixed time interval between retries
+ ///
+ /// The number of times to retry. Should be a non-negative number
+ /// The time interval between retries. Use TimeSpan.Zero to specify immediate
+ /// retries
+ ///
+ /// When is 0 and is
+ /// TimeSpan.Zero this policy is equivalent to the NoRetry policy
+ public static RetryPolicy RetryN(int numberOfRetries, TimeSpan intervalBetweenRetries)
+ {
+ return new RetryPolicy((Action action) =>
+ {
+ RetryNImpl(action, numberOfRetries, intervalBetweenRetries);
+ }
+ );
+ }
+
+ ///
+ /// Policy that retries a specified number of times with a randomized exponential backoff scheme
+ ///
+ /// The number of times to retry. Should be a non-negative number.
+ /// The multiplier in the exponential backoff scheme
+ ///
+ /// For this retry policy, the minimum amount of milliseconds between retries is given by the
+ /// StandardMinBackoff constant, and the maximum backoff is predefined by the StandardMaxBackoff constant.
+ /// Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.
+ public static RetryPolicy RetryExponentialN(int numberOfRetries, TimeSpan deltaBackoff)
+ {
+ return new RetryPolicy((Action action) =>
+ {
+ RetryExponentialNImpl(action, numberOfRetries, StandardMinBackoff, StandardMaxBackoff, deltaBackoff);
+ }
+ );
+ }
+
+ ///
+ /// Policy that retries a specified number of times with a randomized exponential backoff scheme
+ ///
+ /// The number of times to retry. Should be a non-negative number
+ /// The multiplier in the exponential backoff scheme
+ /// The minimum backoff interval
+ /// The maximum backoff interval
+ ///
+ /// For this retry policy, the minimum amount of milliseconds between retries is given by the
+ /// minBackoff parameter, and the maximum backoff is predefined by the maxBackoff parameter.
+ /// Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.
+ public static RetryPolicy RetryExponentialN(int numberOfRetries, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff)
+ {
+ if (minBackoff > maxBackoff)
+ {
+ throw new ArgumentException("The minimum backoff must not be larger than the maximum backoff period.");
+ }
+ if (minBackoff < TimeSpan.Zero)
+ {
+ throw new ArgumentException("The minimum backoff period must not be negative.");
+ }
+
+ return new RetryPolicy((Action action) =>
+ {
+ RetryExponentialNImpl(action, numberOfRetries, minBackoff, maxBackoff, deltaBackoff);
+ }
+ );
+ }
+
+ #region private helper methods
+
+ private static void RetryNImpl(Action action, int numberOfRetries, TimeSpan intervalBetweenRetries)
+ {
+ do
+ {
+ try
+ {
+ action();
+ break;
+ }
+ catch (StorageServerException)
+ {
+ if (numberOfRetries == 0)
+ {
+ throw;
+ }
+ if (intervalBetweenRetries > TimeSpan.Zero)
+ {
+ Thread.Sleep(intervalBetweenRetries);
+ }
+ }
+ catch (TableRetryWrapperException e)
+ {
+ if (numberOfRetries == 0)
+ {
+ throw e.InnerException;
+ }
+ if (intervalBetweenRetries > TimeSpan.Zero)
+ {
+ Thread.Sleep(intervalBetweenRetries);
+ }
+ }
+ }
+ while (numberOfRetries-- > 0);
+ }
+
+ private static void RetryExponentialNImpl(Action action, int numberOfRetries, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff)
+ {
+ int totalNumberOfRetries = numberOfRetries;
+ TimeSpan backoff;
+
+ // sanity check
+ // this is already checked when creating the retry policy in case other than the standard settings are used
+ // because this library is available in source code, the standard settings can be changed and thus we
+ // check again at this point
+ if (minBackoff > maxBackoff)
+ {
+ throw new ArgumentException("The minimum backoff must not be larger than the maximum backoff period.");
+ }
+ if (minBackoff < TimeSpan.Zero)
+ {
+ throw new ArgumentException("The minimum backoff period must not be negative.");
+ }
+
+ do
+ {
+ try
+ {
+ action();
+ break;
+ }
+ catch (StorageServerException)
+ {
+ if (numberOfRetries == 0)
+ {
+ throw;
+ }
+ backoff = CalculateCurrentBackoff(minBackoff, maxBackoff, deltaBackoff, totalNumberOfRetries - numberOfRetries);
+ Debug.Assert(backoff >= minBackoff);
+ Debug.Assert(backoff <= maxBackoff);
+ if (backoff > TimeSpan.Zero) {
+ Thread.Sleep(backoff);
+ }
+ }
+ catch (TableRetryWrapperException e)
+ {
+ if (numberOfRetries == 0)
+ {
+ throw e.InnerException;
+ }
+ backoff = CalculateCurrentBackoff(minBackoff, maxBackoff, deltaBackoff, totalNumberOfRetries - numberOfRetries);
+ Debug.Assert(backoff >= minBackoff);
+ Debug.Assert(backoff <= maxBackoff);
+ if (backoff > TimeSpan.Zero)
+ {
+ Thread.Sleep(backoff);
+ }
+ }
+ }
+ while (numberOfRetries-- > 0);
+ }
+
+ private static TimeSpan CalculateCurrentBackoff(TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, int curRetry)
+ {
+ long backoff;
+
+ if (curRetry > 30)
+ {
+ backoff = maxBackoff.Ticks;
+ }
+ else
+ {
+ try
+ {
+ checked
+ {
+ // only randomize the multiplier here
+ // it would be as correct to randomize the whole backoff result
+ lock (random)
+ {
+ backoff = random.Next((1 << curRetry) + 1);
+ }
+ // Console.WriteLine("backoff:" + backoff);
+ // Console.WriteLine("random range: [0, " + ((1 << curRetry) + 1) + "]");
+ backoff *= deltaBackoff.Ticks;
+ backoff += minBackoff.Ticks;
+ }
+ }
+ catch (OverflowException)
+ {
+ backoff = maxBackoff.Ticks;
+ }
+ if (backoff > maxBackoff.Ticks)
+ {
+ backoff = maxBackoff.Ticks;
+ }
+ }
+ Debug.Assert(backoff >= minBackoff.Ticks);
+ Debug.Assert(backoff <= maxBackoff.Ticks);
+ return TimeSpan.FromTicks(backoff);
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Access control for containers
+ ///
+ public enum ContainerAccessControl
+ {
+ Private,
+ Public
+ }
+
+ ///
+ /// The blob container class.
+ /// Used to access and enumerate blobs in the container.
+ /// Storage key credentials are needed to access private blobs but not for public blobs.
+ ///
+ public abstract class BlobContainer
+ {
+ ///
+ /// Use this constructor to access private blobs.
+ ///
+ /// The base Uri for the storage endpoint
+ /// Name of the storage account
+ /// Name of the container
+ internal protected BlobContainer(Uri baseUri, string accountName, string containerName)
+ : this(baseUri, true, accountName, containerName, DateTime.MinValue)
+ {}
+
+ ///
+ /// Use this constructor to access private blobs.
+ ///
+ /// The base Uri for the storage endpoint
+ ///
+ /// If true, path-style URIs (http://baseuri/accountname/containername/objectname) are used and if false
+ /// host-style URIs (http://accountname.baseuri/containername/objectname) are used, where baseuri is the
+ /// URI of the service
+ ///
+ /// Name of the storage account
+ /// Name of the container
+ /// Date of last modification
+ internal protected BlobContainer(Uri baseUri, bool usePathStyleUris, string accountName, string containerName, DateTime lastModified)
+ {
+ if (!Utilities.IsValidContainerOrQueueName(containerName))
+ {
+ throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The specified container name \"{0}\" is not valid!" +
+ "Please choose a name that conforms to the naming conventions for containers!", containerName));
+ }
+ this.baseUri = baseUri;
+ this.usePathStyleUris = usePathStyleUris;
+ this.accountName = accountName;
+ this.containerName = containerName;
+ this.Timeout = BlobStorage.DefaultTimeout;
+ this.RetryPolicy = BlobStorage.DefaultRetryPolicy;
+ this.LastModifiedTime = lastModified;
+ }
+
+
+ ///
+ /// The time out for each request to the storage service.
+ ///
+ public TimeSpan Timeout
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The retry policy used for retrying requests
+ ///
+ public RetryPolicy RetryPolicy
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The base URI of the blob storage service
+ ///
+ public Uri BaseUri
+ {
+ get
+ {
+ return this.baseUri;
+ }
+ }
+
+ ///
+ /// The name of the storage account
+ ///
+ public string AccountName
+ {
+ get
+ {
+ return this.accountName;
+ }
+ }
+
+ ///
+ /// The name of the blob container.
+ ///
+ public string ContainerName
+ {
+ get
+ {
+ return this.containerName;
+ }
+ }
+
+ ///
+ /// Indicates whether to use/generate path-style or host-style URIs
+ ///
+ public bool UsePathStyleUris
+ {
+ get
+ {
+ return this.usePathStyleUris;
+ }
+ }
+
+ ///
+ /// The URI of the container
+ ///
+ public abstract Uri ContainerUri
+ {
+ get;
+ }
+
+ ///
+ /// The timestamp for last modification of container.
+ ///
+ public DateTime LastModifiedTime
+ {
+ get;
+ protected set;
+ }
+
+ ///
+ /// Create the container if it does not exist.
+ /// The container is created with private access control and no metadata.
+ ///
+ /// true if the container was created. false if the container already exists
+ public abstract bool CreateContainer();
+
+ ///
+ /// Create the container with the specified metadata and access control if it does not exist
+ ///
+ /// The metadata for the container. Can be null to indicate no metadata
+ /// The access control (public or private) with which to create the container
+ /// true if the container was created. false if the container already exists
+ public abstract bool CreateContainer(NameValueCollection metadata, ContainerAccessControl accessControl);
+
+ ///
+ /// Check if the blob container exists
+ ///
+ /// true if the container exists, false otherwise.
+ public abstract bool DoesContainerExist();
+
+ ///
+ /// Get the properties for the container if it exists.
+ ///
+ /// The properties for the container if it exists, null otherwise
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
+ Justification="The method makes a call to the blob service")]
+ public abstract ContainerProperties GetContainerProperties();
+
+ ///
+ /// Get the access control permissions associated with the container.
+ ///
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
+ Justification = "The method makes a call to the blob service")]
+ public abstract ContainerAccessControl GetContainerAccessControl();
+
+ ///
+ /// Set the access control permissions associated with the container.
+ ///
+ /// The permission to set
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
+ Justification = "The method makes a call to the blob service")]
+ public abstract void SetContainerAccessControl(ContainerAccessControl acl);
+
+ ///
+ /// Deletes the current container.
+ ///
+ public abstract bool DeleteContainer();
+
+ ///
+ /// Check if the blob container exists
+ ///
+ /// Name of the BLOB.
+ /// true if the blob exists, false otherwise.
+ public abstract bool DoesBlobExist(string blobName);
+
+ ///
+ /// Create a new blob or overwrite an existing blob.
+ ///
+ /// The properties of the blob
+ /// The contents of the blob
+ /// Should this request overwrite an existing blob ?
+ /// true if the blob was created. false if the blob already exists and was set to false
+ /// The LastModifiedTime property of is set as a result of this call.
+ /// This method also has an effect on the ETag values that are managed by the service.
+ public abstract bool CreateBlob(BlobProperties blobProperties, BlobContents blobContents, bool overwrite);
+
+ ///
+ /// Updates an existing blob if it has not been modified since the specified time which is typically
+ /// the last modified time of the blob when you retrieved it.
+ /// Use this method to implement optimistic concurrency by avoiding clobbering changes to the blob
+ /// made by another writer.
+ ///
+ /// The properties of the blob. This object should be one previously
+ /// obtained from a call to GetBlob or GetBlobProperties and have its LastModifiedTime property set.
+ /// The contents of the blob. The contents of the blob should be readable
+ /// true if the blob was updated. false if the blob has changed since the last time
+ /// The LastModifiedTime property of is set as a result of this call.
+ /// This method also has an effect on the ETag values that are managed by the service if the update was
+ /// successful.
+ public abstract bool UpdateBlobIfNotModified(BlobProperties blob, BlobContents contents);
+
+ ///
+ /// Get the blob contents and properties if the blob exists
+ ///
+ /// The name of the blob
+ /// Object in which the contents are returned.
+ /// This object should contain a writable stream or should be a default constructed object.
+ /// Should the blob be gotten in pieces. This requires more round-trips, but will retry smaller pieces in case of failure.
+ /// The properties of the blob if the blob exists.
+ public abstract BlobProperties GetBlob(string name, BlobContents blobContents, bool transferAsChunks);
+
+ ///
+ /// Gets the blob contents and properties if the blob has not been modified since the time specified.
+ /// Use this method if you have cached the contents of a blob and want to avoid retrieving the blob
+ /// if it has not changed since the last time you retrieved it.
+ ///
+ /// The properties of the blob obtained from an earlier call to GetBlob. This
+ /// parameter is updated by the call if the blob has been modified
+ /// Contains the stream to which the contents of the blob are written if it has been
+ /// modified
+ /// Should the blob be gotten in pieces. This requires more round-trips, but will retry smaller pieces in case of failure.
+ /// true if the blob has been modified, false otherwise
+ public abstract bool GetBlobIfModified(BlobProperties blobProperties, BlobContents blobContents, bool transferAsChunks);
+
+ ///
+ /// Get the properties of the blob if it exists.
+ /// This method is also the simplest way to check if a blob exists.
+ ///
+ /// The name of the blob
+ /// The properties of the blob if it exists. null otherwise.
+ /// The properties for the contents of the blob are not set
+ public abstract BlobProperties GetBlobProperties(string name);
+
+ ///
+ /// Set the metadata of an existing blob.
+ ///
+ /// The blob properties object whose metadata is to be updated
+ public abstract void UpdateBlobMetadata(BlobProperties blobProperties);
+
+ ///
+ /// Set the metadata of an existing blob if it has not been modified since it was last retrieved.
+ ///
+ /// The blob properties object whose metadata is to be updated.
+ /// Typically obtained by a previous call to GetBlob or GetBlobProperties
+ /// true if the blob metadata was updated. false if it was not updated because the blob
+ /// has been modified
+ public abstract bool UpdateBlobMetadataIfNotModified(BlobProperties blobProperties);
+
+ ///
+ /// Delete a blob with the given name
+ ///
+ /// The name of the blob
+ /// true if the blob exists and was successfully deleted, false if the blob does not exist
+ public abstract bool DeleteBlob(string name);
+
+ ///
+ /// Delete a blob with the given name if the blob has not been modified since it was last obtained.
+ /// Use this method for optimistic concurrency to avoid deleting a blob that has been modified since
+ /// the last time you retrieved it
+ ///
+ /// A blob object (typically previously obtained from a GetBlob call)
+ /// This out parameter is set to true if the blob was not deleted because
+ /// it was modified
+ /// true if the blob exists and was successfully deleted, false if the blob does not exist or was
+ /// not deleted because the blob was modified.
+ public abstract bool DeleteBlobIfNotModified(BlobProperties blob, out bool modified);
+
+ ///
+ /// Enumerates all blobs with a given prefix.
+ ///
+ ///
+ /// If true common prefixes with "/" as seperator
+ /// The list of blob properties and common prefixes
+ public abstract IEnumerable