From 0a49d54a8510ad54afa798f30e38ba783c530c41 Mon Sep 17 00:00:00 2001 From: Tom Pipe Date: Sat, 29 Oct 2016 18:23:25 +0100 Subject: [PATCH 01/52] Fixed issue with member properties sort order --- src/Umbraco.Web/Security/MembershipHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 4f5c14723b..158910a794 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -428,7 +428,8 @@ namespace Umbraco.Web.Security var viewProperties = new List(); foreach (var prop in memberType.PropertyTypes - .Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias))) + .Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias)) + .OrderBy(p => p.SortOrder)) { var value = string.Empty; if (member != null) From e9724c8785b40197e59b78d064f4aef89698c7cf Mon Sep 17 00:00:00 2001 From: mikkelhm Date: Sun, 30 Oct 2016 20:46:18 +0100 Subject: [PATCH 02/52] Updates web.config.install.xdt to re-add the assemblyBinding for HtmlAgilityPack after removing it earlier --- build/NuSpecs/tools/Web.config.install.xdt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 2d5dfb0bdb..cbb60bb949 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -335,7 +335,7 @@ - + From dfbadb155077c26aab0031a4f62112c2a28dc5f2 Mon Sep 17 00:00:00 2001 From: Steve Morgan Date: Thu, 3 Nov 2016 17:03:33 +0000 Subject: [PATCH 03/52] U4-9148 noNodes Splash screen apostrophe char typo fixed (in "Be a part of the community" - we're) --- src/Umbraco.Web.UI/config/splashes/noNodes.aspx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx index 92bf10650c..d46ecd6113 100644 --- a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx +++ b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx @@ -44,7 +44,7 @@

Be a part of the community

-

The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, we’re sure that you can get your answers from the community.

+

The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, we're sure that you can get your answers from the community.

our.Umbraco →
From a70f37a53eeeb57364d31bc8468906576f498c1f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Nov 2016 16:46:06 +0100 Subject: [PATCH 04/52] U4-9185 lastLockoutDate does not get set when too many invalid password attempts are made --- src/Umbraco.Core/Security/BackOfficeUserStore.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index c6714e256a..08c6f54dcf 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -644,6 +644,13 @@ namespace Umbraco.Core.Security { anythingChanged = true; user.IsLockedOut = identityUser.IsLockedOut; + + if (user.IsLockedOut) + { + //need to set the last lockout date + user.LastLockoutDate = DateTime.Now; + } + } if (user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false) { From d0862448202128c8f6ca7b5a5100727d720502f0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 18 Nov 2016 09:28:58 +0100 Subject: [PATCH 05/52] U4-9201 - expose PetaPoco Database type + dispose --- src/Umbraco.Core/Persistence/PetaPoco.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 63278889a2..c016276116 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence CommonConstruct(); } - internal enum DBType + public enum DBType { SqlServer, SqlServerCE, @@ -190,7 +190,9 @@ namespace Umbraco.Core.Persistence Oracle, SQLite } - DBType _dbType = DBType.SqlServer; + private DBType _dbType = DBType.SqlServer; + + public DBType DatabaseType { get { return _dbType; } } // Common initialization private void CommonConstruct() @@ -224,13 +226,18 @@ namespace Umbraco.Core.Persistence // Automatically close one open shared connection public void Dispose() { - // Automatically close one open connection reference - // (Works with KeepConnectionAlive and manually opening a shared connection) - CloseSharedConnection(); + Dispose(true); } - // Set to true to keep the first opened connection alive until this object is disposed - public bool KeepConnectionAlive { get; set; } + protected virtual void Dispose(bool disposing) + { + // Automatically close one open connection reference + // (Works with KeepConnectionAlive and manually opening a shared connection) + CloseSharedConnection(); + } + + // Set to true to keep the first opened connection alive until this object is disposed + public bool KeepConnectionAlive { get; set; } // Open a connection (can be nested) public void OpenSharedConnection() From 13ed3757da4b15d76d3a2316b8a08e0997cc8949 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 18 Nov 2016 09:42:07 +0100 Subject: [PATCH 06/52] U4-9201 - properly manage database instances --- src/Umbraco.Core/DatabaseContext.cs | 140 +++++++---- .../Persistence/DefaultDatabaseFactory.cs | 218 +++++++++++++----- .../Persistence/UmbracoDatabase.cs | 69 +++++- src/Umbraco.Core/UmbracoApplicationBase.cs | 13 +- 4 files changed, 337 insertions(+), 103 deletions(-) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 585f1d51cf..f8f31ee695 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -1,10 +1,10 @@ using System; +using System.Collections.Generic; using System.Configuration; using System.Data.SqlServerCe; using System.IO; using System.Linq; using System.Web; -using System.Web.Configuration; using System.Xml.Linq; using Semver; using Umbraco.Core.Configuration; @@ -30,7 +30,6 @@ namespace Umbraco.Core private readonly ILogger _logger; private readonly SqlSyntaxProviders _syntaxProviders; private bool _configured; - private readonly object _locker = new object(); private string _connectionString; private string _providerName; private DatabaseSchemaResult _result; @@ -40,7 +39,7 @@ namespace Umbraco.Core : this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[] { new MySqlSyntaxProvider(LoggerResolver.Current.Logger), - new SqlCeSyntaxProvider(), + new SqlCeSyntaxProvider(), new SqlServerSyntaxProvider() })) { @@ -80,21 +79,55 @@ namespace Umbraco.Core _configured = true; } +#if DEBUG_DATABASES + public List Databases + { + get + { + var factory = _factory as DefaultDatabaseFactory; + if (factory == null) throw new NotSupportedException(); + return factory.Databases; + } + } +#endif + public ISqlSyntaxProvider SqlSyntax { get; private set; } /// - /// Gets the object for doing CRUD operations - /// against custom tables that resides in the Umbraco database. + /// Gets the object for doing CRUD operations against custom tables that resides in the Umbraco database. /// /// - /// This should not be used for CRUD operations or queries against the - /// standard Umbraco tables! Use the Public services for that. + /// Should not be used for operation against standard Umbraco tables; as services should be used instead. + /// Gets or creates an "ambient" database that is either stored in http context + available for the whole + /// request + auto-disposed at the end of the request, or in call context if there is no http context - in which + /// case it needs to be explicitely disposed so it is removed from call context. /// public virtual UmbracoDatabase Database { get { return _factory.CreateDatabase(); } } + /// + /// Replaces the "ambient" database (if any) and installs a new object. + /// + /// + /// The returned IDisposable *must* be diposed in order to properly disposed the safe database and + /// restore the original "ambient" database (if any). + /// This is to be used in background tasks to ensure that they have an "ambient" database which + /// will be properly removed from call context and does not interfere with anything else. In most case + /// it is not replacing anything, just temporarily installing a database in context. + /// + public virtual IDisposable UsingSafeDatabase + { + get + { + var factory = _factory as DefaultDatabaseFactory; + if (factory == null) throw new NotSupportedException(); + var database = factory.CreateDatabaseInstance(DefaultDatabaseFactory.ContextOwner.None); + return new UsedDatabase(database, _logger); + } + } + /// /// Boolean indicating whether the database has been configured /// @@ -189,7 +222,7 @@ namespace Umbraco.Core // since it's been like this for quite some time //using (var engine = new SqlCeEngine(connectionString)) //{ - // engine.CreateDatabase(); + // engine.CreateDatabase(); //} } @@ -230,14 +263,14 @@ namespace Umbraco.Core /// Database Password /// Type of the provider to be used (Sql, Sql Azure, Sql Ce, MySql) public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider) - { + { string providerName; var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName); SaveConnectionString(connectionString, providerName); Initialize(providerName); } - + public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) { providerName = Constants.DatabaseProviders.SqlServer; @@ -268,18 +301,18 @@ namespace Umbraco.Core public string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName) { - return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName); + return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName); } internal string BuildAzureConnectionString(string server, string databaseName, string user, string password) { if (server.Contains(".") && ServerStartsWithTcp(server) == false) server = string.Format("tcp:{0}", server); - + if (server.Contains(".") == false && ServerStartsWithTcp(server)) { - string serverName = server.Contains(",") - ? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal)) + string serverName = server.Contains(",") + ? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal)) : server; var portAddition = string.Empty; @@ -289,10 +322,10 @@ namespace Umbraco.Core server = string.Format("{0}.database.windows.net{1}", serverName, portAddition); } - + if (ServerStartsWithTcp(server) == false) server = string.Format("tcp:{0}.database.windows.net", server); - + if (server.Contains(",") == false) server = string.Format("{0},1433", server); @@ -336,7 +369,7 @@ namespace Umbraco.Core _connectionString = connectionString; _providerName = providerName; - + var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single(); @@ -381,7 +414,7 @@ namespace Umbraco.Core connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString; } Initialize(providerName, connString); - + } else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) { @@ -418,7 +451,7 @@ namespace Umbraco.Core //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); - + } else { @@ -437,15 +470,15 @@ namespace Umbraco.Core { if (_syntaxProviders != null) { - SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName); + SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName); } else if (SqlSyntax == null) { throw new InvalidOperationException("No " + typeof(ISqlSyntaxProvider) + " specified or no " + typeof(SqlSyntaxProviders) + " instance specified"); } - + SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax; - + _configured = true; } catch (Exception e) @@ -485,7 +518,7 @@ namespace Umbraco.Core } internal Result CreateDatabaseSchemaAndData(ApplicationContext applicationContext) - { + { try { var readyForInstall = CheckReadyForInstall(); @@ -503,7 +536,7 @@ namespace Umbraco.Core // If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks // for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive. - /* + /* var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); if (supportsCaseInsensitiveQueries == false) { @@ -521,9 +554,9 @@ namespace Umbraco.Core message = GetResultMessageForMySql(); var schemaResult = ValidateDatabaseSchema(); - + var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); - + //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0))) { @@ -542,9 +575,9 @@ namespace Umbraco.Core message = "

Upgrading database, this may take some time...

"; return new Result { - RequiresUpgrade = true, - Message = message, - Success = true, + RequiresUpgrade = true, + Message = message, + Success = true, Percentage = "30" }; } @@ -572,7 +605,7 @@ namespace Umbraco.Core _logger.Info("Database upgrade started"); var database = new UmbracoDatabase(_connectionString, ProviderName, _logger); - //var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); + //var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); var message = GetResultMessageForMySql(); @@ -580,32 +613,32 @@ namespace Umbraco.Core var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion()); - var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); - + var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); + var targetVersion = UmbracoVersion.Current; - + //In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will - // return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the + // return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the // migrations are run for the target version if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0))) { //set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision()); } - + //Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum - // version detected between the schema installed and the migrations listed in the migration table. + // version detected between the schema installed and the migrations listed in the migration table. // If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what // is declared in the web.config. - + var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) //Take the minimum version between the detected schema version and the installed migration version ? new[] {installedSchemaVersion, installedMigrationVersion}.Min() //Take the minimum version between the installed migration version and the version specified in the config : new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min(); - //Ok, another edge case here. If the current version is a pre-release, - // then we want to ensure all migrations for the current release are executed. + //Ok, another edge case here. If the current version is a pre-release, + // then we want to ensure all migrations for the current release are executed. if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false) { currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision()); @@ -740,7 +773,7 @@ namespace Umbraco.Core { var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString()); var filePath = datasource.Replace("Data Source=", string.Empty); - sqlCeDatabaseExists = File.Exists(filePath); + sqlCeDatabaseExists = File.Exists(filePath); } } @@ -754,5 +787,32 @@ namespace Umbraco.Core return true; } + + private class UsedDatabase : IDisposable + { + private readonly UmbracoDatabase _database; + private readonly ILogger _logger; + + public UsedDatabase(UmbracoDatabase database, ILogger logger) + { + _logger = logger; + + // replace with ours + _database = DefaultDatabaseFactory.DetachDatabase(); + _logger.Debug("Detached db " + (_database == null ? "x" : _database.InstanceSid) + "."); + DefaultDatabaseFactory.AttachDatabase(database); + _logger.Debug("Attached db " + database.InstanceSid + "."); + } + + public void Dispose() + { + // restore the original DB + var db = DefaultDatabaseFactory.DetachDatabase(); + _logger.Debug("Detached db " + db.InstanceSid + "."); + db.Dispose(); + DefaultDatabaseFactory.AttachDatabase(_database); + _logger.Debug("Attached db " + (_database == null ? "x" : _database.InstanceSid) + "."); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 4ad425d9c9..5139da20d8 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -75,69 +75,168 @@ namespace Umbraco.Core.Persistence public UmbracoDatabase CreateDatabase() { - // no http context, create the call context object - // NOTHING is going to track the object and it is the responsibility of the caller to release it! - // using the ReleaseDatabase method. + UmbracoDatabase database; + + // gets or creates a database, using either the call context (if no http context) or + // the current request context (http context) to store it. once done using the database, + // it should be disposed - which will remove it from whatever context it is currently + // stored in. this is automatic with http context because UmbracoDatabase implements + // IDisposeOnRequestEnd, but NOT with call context. + if (HttpContext.Current == null) { - LogHelper.Debug("Get NON http [T" + Environment.CurrentManagedThreadId + "]"); - var value = NonContextValue; - if (value != null) return value; - lock (Locker) + database = NonContextValue; + if (database == null) { - value = NonContextValue; - if (value != null) return value; - - LogHelper.Debug("Create NON http [T" + Environment.CurrentManagedThreadId + "]"); - NonContextValue = value = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger); - - return value; + lock (Locker) + { + database = NonContextValue; + if (database == null) + { + database = CreateDatabaseInstance(ContextOwner.CallContext); + NonContextValue = database; + } +#if DEBUG_DATABASES + else + { + Log("Get lcc", database); + } +#endif + } } +#if DEBUG_DATABASES + else + { + Log("Get lcc", database); + } +#endif + return database; } - // we have an http context, so only create one per request. - // UmbracoDatabase is marked IDisposeOnRequestEnd and therefore will be disposed when - // UmbracoModule attempts to dispose the relevant HttpContext items. so we DO dispose - // connections at the end of each request. no need to call ReleaseDatabase. - LogHelper.Debug("Get http [T" + Environment.CurrentManagedThreadId + "]"); - if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) - { - LogHelper.Debug("Create http [T" + Environment.CurrentManagedThreadId + "]"); - HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory), - string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger)); + if (HttpContext.Current.Items.Contains(typeof (DefaultDatabaseFactory)) == false) + { + database = CreateDatabaseInstance(ContextOwner.HttpContext); + HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), database); + } + else + { + database = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; +#if DEBUG_DATABASES + Log("Get ctx", database); +#endif } - return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + + return database; + } + + // called by UmbracoDatabase when disposed, so that the factory can de-list it from context + internal void OnDispose(UmbracoDatabase disposing) + { + var value = disposing; + switch (disposing.ContextOwner) + { + case ContextOwner.CallContext: + value = NonContextValue; + break; + case ContextOwner.HttpContext: + value = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)]; + break; + } + + if (value != null && value.InstanceId != disposing.InstanceId) throw new Exception("panic: wrong db."); + + switch (disposing.ContextOwner) + { + case ContextOwner.CallContext: + NonContextValue = null; + break; + case ContextOwner.HttpContext: + HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); + break; + } + + disposing.ContextOwner = ContextOwner.None; + +#if DEBUG_DATABASES + _databases.Remove(value); +#endif } - // releases the "context" database - public void ReleaseDatabase() +#if DEBUG_DATABASES + // helps identifying when non-httpContext databases are created by logging the stack trace + private void LogCallContextStack() { - if (HttpContext.Current == null) - { - var value = NonContextValue; - if (value != null) value.Dispose(); - NonContextValue = null; - } + var trace = Environment.StackTrace; + if (trace.IndexOf("ScheduledPublishing") > 0) + LogHelper.Debug("CallContext: Scheduled Publishing"); + else if (trace.IndexOf("TouchServerTask") > 0) + LogHelper.Debug("CallContext: Server Registration"); + else if (trace.IndexOf("LogScrubber") > 0) + LogHelper.Debug("CallContext: Log Scrubber"); else - { - var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; - if (db != null) db.Dispose(); - HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); - } + LogHelper.Debug("CallContext: " + Environment.StackTrace); } + private readonly List _databases = new List(); + + // helps identifying database leaks by keeping track of all instances + public List Databases { get { return _databases; } } + + private static void Log(string message, UmbracoDatabase database) + { + LogHelper.Debug(message + " (" + (database == null ? "" : database.InstanceSid) + ")."); + } +#endif + + internal enum ContextOwner + { + None, + HttpContext, + CallContext + } + + internal UmbracoDatabase CreateDatabaseInstance(ContextOwner contextOwner) + { + var database = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) + : new UmbracoDatabase(_connectionStringName, _logger); + database.ContextOwner = contextOwner; + database.DatabaseFactory = this; + //database.EnableSqlTrace = true; +#if DEBUG_DATABASES + Log("Create " + contextOwner, database); + if (contextOwner == ContextOwner.CallContext) + LogCallContextStack(); + _databases.Add(database); +#endif + return database; + } + protected override void DisposeResources() { - ReleaseDatabase(); - } + UmbracoDatabase database; + + if (HttpContext.Current == null) + { + database = NonContextValue; +#if DEBUG_DATABASES + Log("Release lcc", database); +#endif + } + else + { + database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)]; +#if DEBUG_DATABASES + Log("Release ctx", database); +#endif + } + + if (database != null) database.Dispose(); // removes it from call context + } // during tests, the thread static var can leak between tests // this method provides a way to force-reset the variable - internal void ResetForTests() + internal void ResetForTests() { var value = NonContextValue; if (value != null) value.Dispose(); @@ -156,26 +255,29 @@ namespace Umbraco.Core.Persistence // detaches the current database // ie returns the database and remove it from whatever is "context" - private static UmbracoDatabase DetachDatabase() + internal static UmbracoDatabase DetachDatabase() { + UmbracoDatabase database; + if (HttpContext.Current == null) { - var db = NonContextValue; + database = NonContextValue; NonContextValue = null; - return db; } else { - var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; - HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); - return db; + database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)]; + HttpContext.Current.Items.Remove(typeof (DefaultDatabaseFactory)); } + + if (database != null) database.ContextOwner = ContextOwner.None; + return database; } // attach a current database // ie assign it to whatever is "context" // throws if there already is a database - private static void AttachDatabase(object o) + internal static void AttachDatabase(object o) { var database = o as UmbracoDatabase; if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o"); @@ -183,12 +285,18 @@ namespace Umbraco.Core.Persistence if (HttpContext.Current == null) { if (NonContextValue != null) throw new InvalidOperationException(); - if (database != null) NonContextValue = database; + if (database == null) return; + + NonContextValue = database; + database.ContextOwner = ContextOwner.CallContext; } else { - if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException(); - if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database; + if (HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null) throw new InvalidOperationException(); + if (database == null) return; + + HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] = database; + database.ContextOwner = ContextOwner.HttpContext; } } diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 7ff9cabe84..62cbfa78c4 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -1,7 +1,9 @@ using System; using System.Data; using System.Data.Common; +using System.Data.SqlClient; using System.Text; +using MySql.Data.MySqlClient; using StackExchange.Profiling; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.SqlSyntax; @@ -21,6 +23,12 @@ namespace Umbraco.Core.Persistence private readonly ILogger _logger; private readonly Guid _instanceId = Guid.NewGuid(); private bool _enableCount; +#if DEBUG_DATABASES + private int _spid = -1; +#endif + + internal DefaultDatabaseFactory.ContextOwner ContextOwner = DefaultDatabaseFactory.ContextOwner.None; + internal DefaultDatabaseFactory DatabaseFactory = null; /// /// Used for testing @@ -30,6 +38,18 @@ namespace Umbraco.Core.Persistence get { return _instanceId; } } + public string InstanceSid + { + get + { +#if DEBUG_DATABASES + return _instanceId.ToString("N").Substring(0, 8) + ":" + _spid; +#else + return _instanceId.ToString("N").Substring(0, 8); +#endif + } + } + /// /// Generally used for testing, will output all SQL statements executed to the logger /// @@ -113,13 +133,44 @@ namespace Umbraco.Core.Persistence { // propagate timeout if none yet +#if DEBUG_DATABASES + if (DatabaseType == DBType.MySql) + { + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT CONNECTION_ID()"; + _spid = Convert.ToInt32(command.ExecuteScalar()); + } + } + else if (DatabaseType == DBType.SqlServer) + { + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT @@SPID"; + _spid = Convert.ToInt32(command.ExecuteScalar()); + } + } + else + { + // includes SqlCE + _spid = 0; + } +#endif + // wrap the connection with a profiling connection that tracks timings return new StackExchange.Profiling.Data.ProfiledDbConnection(connection as DbConnection, MiniProfiler.Current); } +#if DEBUG_DATABASES + public override void OnConnectionClosing(IDbConnection conn) + { + _spid = -1; + } +#endif + public override void OnException(Exception x) { - _logger.Error("Database exception occurred", x); + _logger.Error("Exception (" + InstanceSid + ").", x); base.OnException(x); } @@ -132,14 +183,18 @@ namespace Umbraco.Core.Persistence if (EnableSqlTrace) { var sb = new StringBuilder(); +#if DEBUG_DATABASES + sb.Append(InstanceSid); + sb.Append(": "); +#endif sb.Append(cmd.CommandText); foreach (DbParameter p in cmd.Parameters) { sb.Append(" - "); sb.Append(p.Value); } - - _logger.Debug(sb.ToString()); + + _logger.Debug(sb.ToString().Replace("{", "{{").Replace("}", "}}")); } base.OnExecutingCommand(cmd); @@ -185,9 +240,15 @@ namespace Umbraco.Core.Persistence } } } - + //use the defaults base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); } + + protected override void Dispose(bool disposing) + { + LogHelper.Debug("Dispose (" + InstanceSid + ")."); + if (DatabaseFactory != null) DatabaseFactory.OnDispose(this); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index e4574a7972..c6cdfb072a 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core /// The abstract class for the Umbraco HttpApplication /// /// - /// This is exposed in the core so that we can have the IApplicationEventHandler in the core project so that + /// This is exposed in the core so that we can have the IApplicationEventHandler in the core project so that /// IApplicationEventHandler's can fire/execute outside of the web contenxt (i.e. in console applications) /// public abstract class UmbracoApplicationBase : System.Web.HttpApplication @@ -35,7 +35,7 @@ namespace Umbraco.Core /// internal void StartApplication(object sender, EventArgs e) { - //take care of unhandled exceptions - there is nothing we can do to + //take care of unhandled exceptions - there is nothing we can do to // prevent the entire w3wp process to go down but at least we can try // and log the exception AppDomain.CurrentDomain.UnhandledException += (_, args) => @@ -59,6 +59,11 @@ namespace Umbraco.Core //And now we can dispose of our startup handlers - save some memory ApplicationEventsResolver.Current.Dispose(); + + // after Umbraco has started there is a database in "context" and that context is + // going to stay there and never get destroyed nor reused, so we have to ensure that + // the database is disposed (which will auto-remove it from context). + ApplicationContext.Current.DatabaseContext.Database.Dispose(); } /// @@ -121,7 +126,7 @@ namespace Umbraco.Core throw; } } - + } /// @@ -188,7 +193,7 @@ namespace Umbraco.Core { return; } - + Logger.Error("An unhandled exception occurred", exc); OnApplicationError(sender, e); From 6101b4eddebdd2d07a4f9ff217ddb919a2260eb9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 18 Nov 2016 09:27:17 +0100 Subject: [PATCH 07/52] U4-9201 - use UsingSafeDatabase where appropriate --- src/Umbraco.Web/Scheduling/LogScrubber.cs | 4 +- .../Scheduling/ScheduledPublishing.cs | 22 ++++--- .../ServerRegistrationEventHandler.cs | 14 +++-- .../DataServices/UmbracoContentService.cs | 60 +++++++++---------- 4 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index b3a5f303e3..da49404b4d 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Scheduling private readonly ApplicationContext _appContext; private readonly IUmbracoSettingsSection _settings; - public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, ApplicationContext appContext, IUmbracoSettingsSection settings) : base(runner, delayMilliseconds, periodMilliseconds) { @@ -77,6 +77,8 @@ namespace Umbraco.Web.Scheduling return false; // do NOT repeat, going down } + // running on a background task, requires a safe database (see UsingSafeDatabase doc) + using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 6649c3c474..dc6c27959d 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Scheduling } public override async Task PerformRunAsync(CancellationToken token) - { + { if (_appContext == null) return true; // repeat... switch (_appContext.GetCurrentServerRole()) @@ -70,19 +70,27 @@ namespace Umbraco.Web.Scheduling var url = umbracoAppUrl + "/RestServices/ScheduledPublish/Index"; using (DisposableTimer.DebugDuration( - () => string.Format("Scheduled publishing executing @ {0}", url), + () => string.Format("Scheduled publishing executing @ {0}", url), () => "Scheduled publishing complete")) - { + { try - { + { using (var wc = new HttpClient()) { var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = new StringContent(string.Empty) }; - //pass custom the authorization header - request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); + + // running on a background task, requires a safe database (see UsingSafeDatabase doc) + // + // this is because GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database + // + using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + { + //pass custom the authorization header + request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); + } var result = await wc.SendAsync(request, token); } @@ -100,7 +108,7 @@ namespace Umbraco.Web.Scheduling { get { return true; } } - + public override bool RunsOnShutdown { get { return false; } diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index a6dfa07be1..84b3e220ff 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.Strategies protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { _registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar; - + // only for the DatabaseServerRegistrar if (_registrar == null) return; @@ -65,10 +65,10 @@ namespace Umbraco.Web.Strategies case EnsureRoutableOutcome.IsRoutable: case EnsureRoutableOutcome.NotDocumentRequest: RegisterBackgroundTasks(e); - break; + break; } } - + private void RegisterBackgroundTasks(UmbracoRequestEventArgs e) { //remove handler, we're done @@ -84,7 +84,7 @@ namespace Umbraco.Web.Strategies 15000, //delay before first execution _registrar.Options.RecurringSeconds*1000, //amount of ms between executions svc, _registrar, serverAddress); - + //Perform the rest async, we don't want to block the startup sequence // this will just reoccur on a background thread _backgroundTaskRunner.TryAdd(task); @@ -137,7 +137,11 @@ namespace Umbraco.Web.Strategies { try { - _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + // running on a background task, requires a safe database (see UsingSafeDatabase doc) + using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + { + _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + } return true; // repeat } diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index 8f995a125e..4d2ea5bd9a 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -1,26 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security; -using System.Text; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; -using umbraco; using System.Xml.Linq; -using System.Xml; -using umbraco.cms.businesslogic.web; using System.Collections; using System.Xml.XPath; -using umbraco.DataLayer; -using umbraco.BusinessLogic; -using UmbracoExamine.Config; using Examine.LuceneEngine; -using System.Data.SqlClient; -using System.Diagnostics; namespace UmbracoExamine.DataServices { @@ -31,9 +18,7 @@ namespace UmbracoExamine.DataServices public UmbracoContentService() : this(ApplicationContext.Current) - { - - } + { } public UmbracoContentService(ApplicationContext applicationContext) { @@ -73,13 +58,18 @@ namespace UmbracoExamine.DataServices [Obsolete("This should no longer be used, latest content will be indexed by using the IContentService directly")] public XDocument GetLatestContentByXPath(string xpath) { - var xmlContent = XDocument.Parse(""); - foreach (var c in _applicationContext.Services.ContentService.GetRootContent()) + using (_applicationContext.DatabaseContext.UsingSafeDatabase) { - xmlContent.Root.Add(c.ToDeepXml(_applicationContext.Services.PackagingService)); + var xmlContent = XDocument.Parse(""); + var rootContent = _applicationContext.Services.ContentService.GetRootContent(); + foreach (var c in rootContent) + { + // not sure this uses the database, but better be save + xmlContent.Root.Add(c.ToDeepXml(_applicationContext.Services.PackagingService)); + } + var result = ((IEnumerable)xmlContent.XPathEvaluate(xpath)).Cast(); + return result.ToXDocument(); } - var result = ((IEnumerable)xmlContent.XPathEvaluate(xpath)).Cast(); - return result.ToXDocument(); } /// @@ -90,7 +80,10 @@ namespace UmbracoExamine.DataServices /// public bool IsProtected(int nodeId, string path) { - return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); + using (_applicationContext.DatabaseContext.UsingSafeDatabase) + { + return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); + } } /// @@ -100,16 +93,19 @@ namespace UmbracoExamine.DataServices public IEnumerable GetAllUserPropertyNames() { - try - { - var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); - return result; - } - catch (Exception ex) - { - LogHelper.Error("EXCEPTION OCCURRED reading GetAllUserPropertyNames", ex); - return Enumerable.Empty(); - } + using (_applicationContext.DatabaseContext.UsingSafeDatabase) + { + try + { + var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); + return result; + } + catch (Exception ex) + { + LogHelper.Error("EXCEPTION OCCURRED reading GetAllUserPropertyNames", ex); + return Enumerable.Empty(); + } + } } /// From c4d36f06f8e7d6024989b69ec727ffc2560d9aeb Mon Sep 17 00:00:00 2001 From: Damiaan Date: Fri, 18 Nov 2016 10:47:32 +0100 Subject: [PATCH 08/52] remove spaces behind ## the github markdown will show ## at the end of the line if it is followed by a space --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2758ad3edb..3e9be6cb5f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Note that you can always [download a nightly build](http://nightly.umbraco.org/? [![ScreenShot](http://umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/) -## Umbraco - The Friendly CMS ## +## Umbraco - The Friendly CMS ## For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. @@ -30,7 +30,7 @@ Used by more than 350,000 active websites including Carlsberg, Segway, Amazon an To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies) -## Why Open Source? ## +## Why Open Source? ## As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use. ## Downloading ## @@ -49,4 +49,4 @@ Umbraco is contribution focused and community driven. If you want to contribute Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature). -To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). \ No newline at end of file +To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). From bad0d15c1c780ee50890613862dabd40c9691be7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 18 Nov 2016 13:32:56 +0100 Subject: [PATCH 09/52] U4-9201 - troubleshoot --- src/Umbraco.Core/Persistence/UmbracoDatabase.cs | 1 + src/Umbraco.Core/UmbracoApplicationBase.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 62cbfa78c4..4c7ba0b4c6 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -247,6 +247,7 @@ namespace Umbraco.Core.Persistence protected override void Dispose(bool disposing) { + base.Dispose(disposing); LogHelper.Debug("Dispose (" + InstanceSid + ")."); if (DatabaseFactory != null) DatabaseFactory.OnDispose(this); } diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index c6cdfb072a..1bf90fb154 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -63,7 +63,9 @@ namespace Umbraco.Core // after Umbraco has started there is a database in "context" and that context is // going to stay there and never get destroyed nor reused, so we have to ensure that // the database is disposed (which will auto-remove it from context). - ApplicationContext.Current.DatabaseContext.Database.Dispose(); + var database = ApplicationContext.Current.DatabaseContext.Database; + if (database != null) // never to happen... unless in weird tests + ApplicationContext.Current.DatabaseContext.Database.Dispose(); } /// From 8ba6cb3abf6c5fe1ce965b35788c03ab1f60067b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Nov 2016 13:38:49 +0100 Subject: [PATCH 10/52] Revert "Backport SafeCallContext, DefaultDatabaseFactory fixes from 7.6" This reverts commit 59ace3d8814e74ece37de3d207b039edd608ee02. --- .../Packaging/PackageBinaryInspector.cs | 50 +++-- .../Persistence/DefaultDatabaseFactory.cs | 186 +++++------------- src/Umbraco.Core/SafeCallContext.cs | 94 --------- src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../Persistence/DatabaseContextTests.cs | 6 +- .../DataServices/UmbracoContentService.cs | 1 + 6 files changed, 78 insertions(+), 260 deletions(-) delete mode 100644 src/Umbraco.Core/SafeCallContext.cs diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 116b5ee952..8852475543 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -29,24 +29,20 @@ namespace Umbraco.Core.Packaging /// public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) { - // beware! when toying with domains, use a safe call context! - using (new SafeCallContext()) + var appDomain = GetTempAppDomain(); + var type = typeof(PackageBinaryInspector); + try { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryInspector); - try - { - var value = (PackageBinaryInspector) appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - // do NOT turn PerformScan into static (even if ReSharper says so)! - var result = value.PerformScan(assemblys.ToArray(), out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); - } + var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); + // do NOT turn PerformScan into static (even if ReSharper says so)! + var result = value.PerformScan(assemblys.ToArray(), out errorReport); + return result; + } + finally + { + AppDomain.Unload(appDomain); } } @@ -82,7 +78,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -111,7 +107,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -158,7 +154,7 @@ namespace Umbraco.Core.Packaging //get the list of assembly names to compare below var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); - + //Then load each referenced assembly into the context foreach (var a in loaded) { @@ -174,7 +170,7 @@ namespace Umbraco.Core.Packaging } catch (FileNotFoundException) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package references the assembly '", assemblyName.Name, @@ -183,7 +179,7 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", assemblyName.Name, @@ -201,7 +197,7 @@ namespace Umbraco.Core.Packaging { //now we need to see if they contain any type 'T' var reflectedAssembly = a; - + try { var found = reflectedAssembly.GetExportedTypes() @@ -214,8 +210,8 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //This is a hack that nobody can seem to get around, I've read everything and it seems that - // this is quite a common thing when loading types into reflection only load context, so + //This is a hack that nobody can seem to get around, I've read everything and it seems that + // this is quite a common thing when loading types into reflection only load context, so // we're just going to ignore this specific one for now var typeLoadEx = ex as TypeLoadException; if (typeLoadEx != null) @@ -236,7 +232,7 @@ namespace Umbraco.Core.Packaging LogHelper.Error("An error occurred scanning package assemblies", ex); } } - + } errorReport = errors.ToArray(); @@ -256,7 +252,7 @@ namespace Umbraco.Core.Packaging var contractType = contractAssemblyLoadFrom.GetExportedTypes() .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); - + if (contractType == null) { throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index c96b442e6e..658b8ebabe 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,6 +1,6 @@ using System; -using System.Runtime.Remoting.Messaging; using System.Web; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; namespace Umbraco.Core.Persistence @@ -19,24 +19,13 @@ namespace Umbraco.Core.Persistence private readonly ILogger _logger; public string ConnectionString { get; private set; } public string ProviderName { get; private set; } + + //very important to have ThreadStatic: + // see: http://issues.umbraco.org/issue/U4-2172 + [ThreadStatic] + private static volatile UmbracoDatabase _nonHttpInstance; - // NO! see notes in v8 HybridAccessorBase - //[ThreadStatic] - //private static volatile UmbracoDatabase _nonHttpInstance; - - private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory"; - - private static UmbracoDatabase NonContextValue - { - get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); } - set - { - if (value == null) CallContext.FreeNamedDataSlot(ItemKey); - else CallContext.LogicalSetData(ItemKey, value); - } - } - - private static readonly object Locker = new object(); + private static readonly object Locker = new object(); /// /// Constructor accepting custom connection string @@ -47,10 +36,7 @@ namespace Umbraco.Core.Persistence { if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionStringName, "connectionStringName"); - - //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); - - _connectionStringName = connectionStringName; + _connectionStringName = connectionStringName; _logger = logger; } @@ -65,133 +51,65 @@ namespace Umbraco.Core.Persistence if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionString, "connectionString"); Mandate.ParameterNotNullOrEmpty(providerName, "providerName"); - - //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); - - ConnectionString = connectionString; + ConnectionString = connectionString; ProviderName = providerName; _logger = logger; } public UmbracoDatabase CreateDatabase() { - // no http context, create the call context object - // NOTHING is going to track the object and it is the responsibility of the caller to release it! - // using the ReleaseDatabase method. - if (HttpContext.Current == null) - { - LogHelper.Debug("Get NON http [T" + Environment.CurrentManagedThreadId + "]"); - var value = NonContextValue; - if (value != null) return value; - lock (Locker) - { - value = NonContextValue; - if (value != null) return value; + //no http context, create the singleton global object + if (HttpContext.Current == null) + { + if (_nonHttpInstance == null) + { + lock (Locker) + { + //double check + if (_nonHttpInstance == null) + { + _nonHttpInstance = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) + : new UmbracoDatabase(_connectionStringName, _logger); + } + } + } + return _nonHttpInstance; + } - LogHelper.Debug("Create NON http [T" + Environment.CurrentManagedThreadId + "]"); - NonContextValue = value = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger); - - return value; - } - } - - // we have an http context, so only create one per request. - // UmbracoDatabase is marked IDisposeOnRequestEnd and therefore will be disposed when - // UmbracoModule attempts to dispose the relevant HttpContext items. so we DO dispose - // connections at the end of each request. no need to call ReleaseDatabase. - LogHelper.Debug("Get http [T" + Environment.CurrentManagedThreadId + "]"); - if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) - { - LogHelper.Debug("Create http [T" + Environment.CurrentManagedThreadId + "]"); - HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory), - string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + //we have an http context, so only create one per request + if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) + { + HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), + string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) : new UmbracoDatabase(_connectionStringName, _logger)); - } - return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; - } + } + return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + } - // releases the "context" database - public void ReleaseDatabase() - { - if (HttpContext.Current == null) - { - var value = NonContextValue; - if (value != null) value.Dispose(); - NonContextValue = null; - } - else - { - var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; - if (db != null) db.Dispose(); - HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); - } - } - - protected override void DisposeResources() + protected override void DisposeResources() { - ReleaseDatabase(); + if (HttpContext.Current == null) + { + _nonHttpInstance.Dispose(); + } + else + { + if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory))) + { + ((UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]).Dispose(); + } + } } // during tests, the thread static var can leak between tests // this method provides a way to force-reset the variable internal void ResetForTests() { - var value = NonContextValue; - if (value != null) value.Dispose(); - NonContextValue = null; - } - - #region SafeCallContext - - // see notes in SafeCallContext - need to do this since we are using - // the logical call context... - - static DefaultDatabaseFactory() - { - SafeCallContext.Register(DetachDatabase, AttachDatabase); - } - - // detaches the current database - // ie returns the database and remove it from whatever is "context" - private static UmbracoDatabase DetachDatabase() - { - if (HttpContext.Current == null) - { - var db = NonContextValue; - NonContextValue = null; - return db; - } - else - { - var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; - HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); - return db; - } - } - - // attach a current database - // ie assign it to whatever is "context" - // throws if there already is a database - private static void AttachDatabase(object o) - { - var database = o as UmbracoDatabase; - if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o"); - - if (HttpContext.Current == null) - { - if (NonContextValue != null) throw new InvalidOperationException(); - if (database != null) NonContextValue = database; - } - else - { - if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException(); - if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database; - } - } - - #endregion - } + if (_nonHttpInstance == null) return; + _nonHttpInstance.Dispose(); + _nonHttpInstance = null; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/SafeCallContext.cs b/src/Umbraco.Core/SafeCallContext.cs deleted file mode 100644 index 5ed41d389f..0000000000 --- a/src/Umbraco.Core/SafeCallContext.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; - -namespace Umbraco.Core -{ - internal class SafeCallContext : IDisposable - { - private static readonly List> EnterFuncs = new List>(); - private static readonly List> ExitActions = new List>(); - private static int _count; - private readonly List _objects; - private bool _disposed; - - public static void Register(Func enterFunc, Action exitAction) - { - if (enterFunc == null) throw new ArgumentNullException("enterFunc"); - if (exitAction == null) throw new ArgumentNullException("exitAction"); - - lock (EnterFuncs) - { - if (_count > 0) throw new InvalidOperationException("Cannot register while some SafeCallContext instances exist."); - EnterFuncs.Add(enterFunc); - ExitActions.Add(exitAction); - } - } - - // tried to make the UmbracoDatabase serializable but then it leaks to weird places - // in ReSharper and so on, where Umbraco.Core is not available. Tried to serialize - // as an object instead but then it comes *back* deserialized into the original context - // as an object and of course it breaks everything. Cannot prevent this from flowing, - // and ExecutionContext.SuppressFlow() works for threads but not domains. and we'll - // have the same issue with anything that toys with logical call context... - // - // so this class lets anything that uses the logical call context register itself, - // providing two methods: - // - an enter func that removes and returns whatever is in the logical call context - // - an exit action that restores the value into the logical call context - // whenever a SafeCallContext instance is created, it uses these methods to capture - // and clear the logical call context, and restore it when disposed. - // - // in addition, a static Clear method is provided - which uses the enter funcs to - // remove everything from logical call context - not to be used when the app runs, - // but can be useful during tests - // - // note - // see System.Transactions - // they are using a conditional weak table to store the data, and what they store in - // LLC is the key - which is just an empty MarshalByRefObject that is created with - // the transaction scope - that way, they can "clear current data" provided that - // they have the key - but they need to hold onto a ref to the scope... not ok for us - - public static void Clear() - { - lock (EnterFuncs) - { - foreach (var enter in EnterFuncs) - enter(); - } - } - - public SafeCallContext() - { - lock (EnterFuncs) - { - _count++; - _objects = EnterFuncs.Select(x => x()).ToList(); - } - } - - public void Dispose() - { - if (_disposed) throw new ObjectDisposedException("this"); - _disposed = true; - lock (EnterFuncs) - { - for (var i = 0; i < ExitActions.Count; i++) - ExitActions[i](_objects[i]); - _count--; - } - } - - // for unit tests ONLY - internal static void Reset() - { - lock (EnterFuncs) - { - if (_count > 0) throw new InvalidOperationException("Cannot reset while some SafeCallContext instances exist."); - EnterFuncs.Clear(); - ExitActions.Clear(); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5648d43dd2..7a4c4f2363 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -507,7 +507,6 @@ - diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index a9b016c2b6..94f0010201 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -23,8 +23,6 @@ namespace Umbraco.Tests.Persistence [SetUp] public void Setup() { - SafeCallContext.Clear(); - _dbContext = new DatabaseContext( new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Mock.Of()), Mock.Of(), new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); @@ -36,7 +34,7 @@ namespace Umbraco.Tests.Persistence { DatabaseContext = _dbContext, IsReady = true - }; + }; } [TearDown] @@ -101,7 +99,7 @@ namespace Umbraco.Tests.Persistence var appCtx = new ApplicationContext( new DatabaseContext(Mock.Of(), Mock.Of(), Mock.Of(), "test"), - new ServiceContext(migrationEntryService: Mock.Of()), + new ServiceContext(migrationEntryService: Mock.Of()), CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of())); diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index 8f995a125e..3cd94751ae 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -102,6 +102,7 @@ namespace UmbracoExamine.DataServices { try { + var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); return result; } From 93ee850bcc35a9a25ca187bfa786f0da158d8ebb Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Nov 2016 13:48:18 +0100 Subject: [PATCH 11/52] publicize 2 events on UmbracoModule --- src/Umbraco.Web/BatchedDatabaseServerMessenger.cs | 2 +- src/Umbraco.Web/BatchedWebServiceServerMessenger.cs | 3 ++- src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs | 2 +- src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs | 2 +- src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs | 2 +- src/Umbraco.Web/UmbracoModule.cs | 13 +++++++------ 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index f06a7a332f..23edb2dde2 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web } } - private void UmbracoModule_EndRequest(object sender, EventArgs e) + private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) { // will clear the batch - will remain in HttpContext though - that's ok FlushBatch(); diff --git a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs index 63536ca9f7..805c2393a8 100644 --- a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs +++ b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Web; using Umbraco.Core.Sync; +using Umbraco.Web.Routing; namespace Umbraco.Web { @@ -64,7 +65,7 @@ namespace Umbraco.Web return batch; } - void UmbracoModule_EndRequest(object sender, EventArgs e) + void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) { FlushBatch(); } diff --git a/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs b/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs index fd09f5ff8c..3eefbc4d0c 100644 --- a/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs +++ b/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs @@ -3,7 +3,7 @@ namespace Umbraco.Web.Routing /// /// Represents the outcome of trying to route an incoming request. /// - internal enum EnsureRoutableOutcome + public enum EnsureRoutableOutcome { /// /// Request routes to a document. diff --git a/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs b/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs index 7a7bc37d5c..ee93623252 100644 --- a/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs +++ b/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs @@ -5,7 +5,7 @@ namespace Umbraco.Web.Routing /// /// Event args containing information about why the request was not routable, or if it is routable /// - internal class RoutableAttemptEventArgs : UmbracoRequestEventArgs + public class RoutableAttemptEventArgs : UmbracoRequestEventArgs { public EnsureRoutableOutcome Outcome { get; private set; } diff --git a/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs b/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs index 2c50c972f5..3516277275 100644 --- a/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs +++ b/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Routing /// /// Event args used for event launched during a request (like in the UmbracoModule) /// - internal class UmbracoRequestEventArgs : EventArgs + public class UmbracoRequestEventArgs : EventArgs { public UmbracoContext UmbracoContext { get; private set; } public HttpContextBase HttpContext { get; private set; } diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index a80188f449..f2503b88c6 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -524,9 +524,9 @@ namespace Umbraco.Web "Total milliseconds for umbraco request to process: {0}", () => DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); } - OnEndRequest(new EventArgs()); + OnEndRequest(new UmbracoRequestEventArgs(UmbracoContext.Current, new HttpContextWrapper(httpContext))); - DisposeHttpContextItems(httpContext); + DisposeHttpContextItems(httpContext); }; } @@ -536,18 +536,19 @@ namespace Umbraco.Web } - #endregion + #endregion #region Events - internal static event EventHandler RouteAttempt; + + public static event EventHandler RouteAttempt; private void OnRouteAttempt(RoutableAttemptEventArgs args) { if (RouteAttempt != null) RouteAttempt(this, args); } - internal static event EventHandler EndRequest; - private void OnEndRequest(EventArgs args) + public static event EventHandler EndRequest; + private void OnEndRequest(UmbracoRequestEventArgs args) { if (EndRequest != null) EndRequest(this, args); From 0e57d558f671712c68b6c0d25e3993f23536ebc0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Nov 2016 14:20:54 +0100 Subject: [PATCH 12/52] updates cdf version --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- src/Umbraco.Web.UI/packages.config | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 4 ++-- src/Umbraco.Web/packages.config | 2 +- src/umbraco.cms/packages.config | 2 +- src/umbraco.cms/umbraco.cms.csproj | 4 ++-- src/umbraco.controls/packages.config | 2 +- src/umbraco.controls/umbraco.controls.csproj | 4 ++-- src/umbraco.editorControls/packages.config | 2 +- src/umbraco.editorControls/umbraco.editorControls.csproj | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 791dd9b270..c8c90c2ed5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -115,8 +115,8 @@ ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll True - - ..\packages\ClientDependency.1.9.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll True diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 3850961ea8..008e5e404c 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -1,7 +1,7 @@  - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cf188dab8e..02f973a13e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -105,8 +105,8 @@ ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll True - - ..\packages\ClientDependency.1.9.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll True diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 4f05967575..f8d737194e 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -1,7 +1,7 @@  - + diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config index 14db85833d..46e6d733a1 100644 --- a/src/umbraco.cms/packages.config +++ b/src/umbraco.cms/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 2fd7a19ca9..3ed531db8e 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -106,8 +106,8 @@ false - - ..\packages\ClientDependency.1.9.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll True diff --git a/src/umbraco.controls/packages.config b/src/umbraco.controls/packages.config index 016ca51b2d..eca7107322 100644 --- a/src/umbraco.controls/packages.config +++ b/src/umbraco.controls/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/umbraco.controls/umbraco.controls.csproj b/src/umbraco.controls/umbraco.controls.csproj index a626c8a4dd..49fd505856 100644 --- a/src/umbraco.controls/umbraco.controls.csproj +++ b/src/umbraco.controls/umbraco.controls.csproj @@ -68,8 +68,8 @@ false - - ..\packages\ClientDependency.1.9.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll True diff --git a/src/umbraco.editorControls/packages.config b/src/umbraco.editorControls/packages.config index 016ca51b2d..eca7107322 100644 --- a/src/umbraco.editorControls/packages.config +++ b/src/umbraco.editorControls/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj b/src/umbraco.editorControls/umbraco.editorControls.csproj index 15128a4d51..3bccb0b5f8 100644 --- a/src/umbraco.editorControls/umbraco.editorControls.csproj +++ b/src/umbraco.editorControls/umbraco.editorControls.csproj @@ -114,8 +114,8 @@ {651E1350-91B6-44B7-BD60-7207006D7003} Umbraco.Web - - ..\packages\ClientDependency.1.9.1\lib\net45\ClientDependency.Core.dll + + ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll True From 77e9643a09fda2b6293e77fd266facae0dd738ea Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 21 Nov 2016 08:17:18 +0100 Subject: [PATCH 13/52] Update NuGet CDF dependency --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 5e24fa66c5..7c1ff15cc8 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -27,7 +27,7 @@ - + From d95f818e43a8845291d655b9310a4dfe317ae725 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 22 Nov 2016 14:02:53 +0100 Subject: [PATCH 14/52] Fixes U4-9212 --- src/Umbraco.Web/Editors/MediaController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 6604aee507..a12e91bd48 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -524,7 +524,7 @@ namespace Umbraco.Web.Editors //get the files foreach (var file in result.FileData) { - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"', ' ' }); var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) From a2a4ad39476f4a18c8fe2c04d42f6fa635551b63 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 22 Nov 2016 14:31:29 +0100 Subject: [PATCH 15/52] Fixes U4-9212 --- src/Umbraco.Web/Editors/MediaController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index a12e91bd48..7a7e349a3c 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -524,7 +524,7 @@ namespace Umbraco.Web.Editors //get the files foreach (var file in result.FileData) { - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"', ' ' }); + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) From e9b217550658ccba3347a4251ee41dae134c6362 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Nov 2016 15:16:07 +0100 Subject: [PATCH 16/52] Fixes null check problem in ExamineEvents.ContentTypeCacheRefresherCacheUpdated --- src/Umbraco.Web/Search/ExamineEvents.cs | 27 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index d02850bffe..7fbbf29b89 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -122,10 +122,13 @@ namespace Umbraco.Web.Search foreach (var alias in contentTypesChanged) { var ctType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias); - var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id); - foreach (var contentItem in contentItems) + if (ctType != null) { - ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false); + var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id); + foreach (var contentItem in contentItems) + { + ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false); + } } } } @@ -134,10 +137,13 @@ namespace Umbraco.Web.Search foreach (var alias in mediaTypesChanged) { var ctType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); - var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id); - foreach (var mediaItem in mediaItems) + if (ctType != null) { - ReIndexForMedia(mediaItem, mediaItem.Trashed == false); + var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id); + foreach (var mediaItem in mediaItems) + { + ReIndexForMedia(mediaItem, mediaItem.Trashed == false); + } } } } @@ -146,10 +152,13 @@ namespace Umbraco.Web.Search foreach (var alias in memberTypesChanged) { var ctType = ApplicationContext.Current.Services.MemberTypeService.Get(alias); - var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id); - foreach (var memberItem in memberItems) + if (ctType != null) { - ReIndexForMember(memberItem); + var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id); + foreach (var memberItem in memberItems) + { + ReIndexForMember(memberItem); + } } } } From 1def61432eb2e15539c6a9a092977e86fb701d67 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 23 Nov 2016 10:58:52 +0100 Subject: [PATCH 17/52] U4-9216 - fix saving containers --- src/Umbraco.Core/Services/ContentTypeService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index d80a93a4ab..31e86cd128 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -141,7 +141,7 @@ namespace Umbraco.Core.Services { var evtMsgs = EventMessagesFactory.Get(); - if (container.ContainedObjectType != containerObjectType) + if (container.ContainerObjectType != containerObjectType) { var ex = new InvalidOperationException("Not a " + objectTypeName + " container."); return OperationStatus.Exception(evtMsgs, ex); @@ -799,7 +799,7 @@ namespace Umbraco.Core.Services // of a different type, move them to the recycle bin, then permanently delete the content items. // The main problem with this is that for every content item being deleted, events are raised... // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - + var deletedContentTypes = new List() {contentType}; deletedContentTypes.AddRange(contentType.Descendants().OfType()); @@ -807,7 +807,7 @@ namespace Umbraco.Core.Services { _contentService.DeleteContentOfType(deletedContentType.Id); } - + repository.Delete(contentType); uow.Commit(); From 5784ea96e751f78af7c405cfdb477829f7feca7c Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 23 Nov 2016 11:26:30 +0100 Subject: [PATCH 18/52] UnRevert "Backport SafeCallContext, DefaultDatabaseFactory fixes from 7.6" This reverts commit 8ba6cb3abf6c5fe1ce965b35788c03ab1f60067b. --- .../Packaging/PackageBinaryInspector.cs | 50 ++--- .../Persistence/DefaultDatabaseFactory.cs | 186 +++++++++++++----- src/Umbraco.Core/SafeCallContext.cs | 94 +++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Persistence/DatabaseContextTests.cs | 4 +- .../DataServices/UmbracoContentService.cs | 1 - 6 files changed, 258 insertions(+), 78 deletions(-) create mode 100644 src/Umbraco.Core/SafeCallContext.cs diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 9307e74f21..57664b8a83 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -29,20 +29,24 @@ namespace Umbraco.Core.Packaging /// public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryInspector); - try + // beware! when toying with domains, use a safe call context! + using (new SafeCallContext()) { - var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - // do NOT turn PerformScan into static (even if ReSharper says so)! - var result = value.PerformScan(assemblys.ToArray(), out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); + var appDomain = GetTempAppDomain(); + var type = typeof(PackageBinaryInspector); + try + { + var value = (PackageBinaryInspector) appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); + // do NOT turn PerformScan into static (even if ReSharper says so)! + var result = value.PerformScan(assemblys.ToArray(), out errorReport); + return result; + } + finally + { + AppDomain.Unload(appDomain); + } } } @@ -78,7 +82,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -107,7 +111,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -154,7 +158,7 @@ namespace Umbraco.Core.Packaging //get the list of assembly names to compare below var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); - + //Then load each referenced assembly into the context foreach (var a in loaded) { @@ -170,7 +174,7 @@ namespace Umbraco.Core.Packaging } catch (FileNotFoundException) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package references the assembly '", assemblyName.Name, @@ -179,7 +183,7 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", assemblyName.Name, @@ -197,7 +201,7 @@ namespace Umbraco.Core.Packaging { //now we need to see if they contain any type 'T' var reflectedAssembly = a; - + try { var found = reflectedAssembly.GetExportedTypes() @@ -210,8 +214,8 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //This is a hack that nobody can seem to get around, I've read everything and it seems that - // this is quite a common thing when loading types into reflection only load context, so + //This is a hack that nobody can seem to get around, I've read everything and it seems that + // this is quite a common thing when loading types into reflection only load context, so // we're just going to ignore this specific one for now var typeLoadEx = ex as TypeLoadException; if (typeLoadEx != null) @@ -232,7 +236,7 @@ namespace Umbraco.Core.Packaging LogHelper.Error("An error occurred scanning package assemblies", ex); } } - + } errorReport = errors.ToArray(); @@ -252,7 +256,7 @@ namespace Umbraco.Core.Packaging var contractType = contractAssemblyLoadFrom.GetExportedTypes() .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); - + if (contractType == null) { throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 8b78f290b3..4ad425d9c9 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,6 +1,6 @@ using System; +using System.Runtime.Remoting.Messaging; using System.Web; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; namespace Umbraco.Core.Persistence @@ -19,13 +19,24 @@ namespace Umbraco.Core.Persistence private readonly ILogger _logger; public string ConnectionString { get; private set; } public string ProviderName { get; private set; } - - //very important to have ThreadStatic: - // see: http://issues.umbraco.org/issue/U4-2172 - [ThreadStatic] - private static volatile UmbracoDatabase _nonHttpInstance; - private static readonly object Locker = new object(); + // NO! see notes in v8 HybridAccessorBase + //[ThreadStatic] + //private static volatile UmbracoDatabase _nonHttpInstance; + + private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory"; + + private static UmbracoDatabase NonContextValue + { + get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); } + set + { + if (value == null) CallContext.FreeNamedDataSlot(ItemKey); + else CallContext.LogicalSetData(ItemKey, value); + } + } + + private static readonly object Locker = new object(); /// /// Constructor accepting custom connection string @@ -36,7 +47,10 @@ namespace Umbraco.Core.Persistence { if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionStringName, "connectionStringName"); - _connectionStringName = connectionStringName; + + //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); + + _connectionStringName = connectionStringName; _logger = logger; } @@ -51,65 +65,133 @@ namespace Umbraco.Core.Persistence if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionString, "connectionString"); Mandate.ParameterNotNullOrEmpty(providerName, "providerName"); - ConnectionString = connectionString; + + //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); + + ConnectionString = connectionString; ProviderName = providerName; _logger = logger; } public UmbracoDatabase CreateDatabase() { - //no http context, create the singleton global object - if (HttpContext.Current == null) - { - if (_nonHttpInstance == null) - { - lock (Locker) - { - //double check - if (_nonHttpInstance == null) - { - _nonHttpInstance = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger); - } - } - } - return _nonHttpInstance; - } + // no http context, create the call context object + // NOTHING is going to track the object and it is the responsibility of the caller to release it! + // using the ReleaseDatabase method. + if (HttpContext.Current == null) + { + LogHelper.Debug("Get NON http [T" + Environment.CurrentManagedThreadId + "]"); + var value = NonContextValue; + if (value != null) return value; + lock (Locker) + { + value = NonContextValue; + if (value != null) return value; - //we have an http context, so only create one per request - if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) - { - HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), - string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + LogHelper.Debug("Create NON http [T" + Environment.CurrentManagedThreadId + "]"); + NonContextValue = value = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) + : new UmbracoDatabase(_connectionStringName, _logger); + + return value; + } + } + + // we have an http context, so only create one per request. + // UmbracoDatabase is marked IDisposeOnRequestEnd and therefore will be disposed when + // UmbracoModule attempts to dispose the relevant HttpContext items. so we DO dispose + // connections at the end of each request. no need to call ReleaseDatabase. + LogHelper.Debug("Get http [T" + Environment.CurrentManagedThreadId + "]"); + if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) + { + LogHelper.Debug("Create http [T" + Environment.CurrentManagedThreadId + "]"); + HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory), + string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) : new UmbracoDatabase(_connectionStringName, _logger)); - } - return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; - } + } + return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + } - protected override void DisposeResources() + // releases the "context" database + public void ReleaseDatabase() + { + if (HttpContext.Current == null) + { + var value = NonContextValue; + if (value != null) value.Dispose(); + NonContextValue = null; + } + else + { + var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + if (db != null) db.Dispose(); + HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); + } + } + + protected override void DisposeResources() { - if (HttpContext.Current == null) - { - _nonHttpInstance.Dispose(); - } - else - { - if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory))) - { - ((UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]).Dispose(); - } - } + ReleaseDatabase(); } // during tests, the thread static var can leak between tests // this method provides a way to force-reset the variable internal void ResetForTests() { - if (_nonHttpInstance == null) return; - _nonHttpInstance.Dispose(); - _nonHttpInstance = null; - } - } + var value = NonContextValue; + if (value != null) value.Dispose(); + NonContextValue = null; + } + + #region SafeCallContext + + // see notes in SafeCallContext - need to do this since we are using + // the logical call context... + + static DefaultDatabaseFactory() + { + SafeCallContext.Register(DetachDatabase, AttachDatabase); + } + + // detaches the current database + // ie returns the database and remove it from whatever is "context" + private static UmbracoDatabase DetachDatabase() + { + if (HttpContext.Current == null) + { + var db = NonContextValue; + NonContextValue = null; + return db; + } + else + { + var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); + return db; + } + } + + // attach a current database + // ie assign it to whatever is "context" + // throws if there already is a database + private static void AttachDatabase(object o) + { + var database = o as UmbracoDatabase; + if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o"); + + if (HttpContext.Current == null) + { + if (NonContextValue != null) throw new InvalidOperationException(); + if (database != null) NonContextValue = database; + } + else + { + if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException(); + if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database; + } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/SafeCallContext.cs b/src/Umbraco.Core/SafeCallContext.cs new file mode 100644 index 0000000000..5ed41d389f --- /dev/null +++ b/src/Umbraco.Core/SafeCallContext.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core +{ + internal class SafeCallContext : IDisposable + { + private static readonly List> EnterFuncs = new List>(); + private static readonly List> ExitActions = new List>(); + private static int _count; + private readonly List _objects; + private bool _disposed; + + public static void Register(Func enterFunc, Action exitAction) + { + if (enterFunc == null) throw new ArgumentNullException("enterFunc"); + if (exitAction == null) throw new ArgumentNullException("exitAction"); + + lock (EnterFuncs) + { + if (_count > 0) throw new InvalidOperationException("Cannot register while some SafeCallContext instances exist."); + EnterFuncs.Add(enterFunc); + ExitActions.Add(exitAction); + } + } + + // tried to make the UmbracoDatabase serializable but then it leaks to weird places + // in ReSharper and so on, where Umbraco.Core is not available. Tried to serialize + // as an object instead but then it comes *back* deserialized into the original context + // as an object and of course it breaks everything. Cannot prevent this from flowing, + // and ExecutionContext.SuppressFlow() works for threads but not domains. and we'll + // have the same issue with anything that toys with logical call context... + // + // so this class lets anything that uses the logical call context register itself, + // providing two methods: + // - an enter func that removes and returns whatever is in the logical call context + // - an exit action that restores the value into the logical call context + // whenever a SafeCallContext instance is created, it uses these methods to capture + // and clear the logical call context, and restore it when disposed. + // + // in addition, a static Clear method is provided - which uses the enter funcs to + // remove everything from logical call context - not to be used when the app runs, + // but can be useful during tests + // + // note + // see System.Transactions + // they are using a conditional weak table to store the data, and what they store in + // LLC is the key - which is just an empty MarshalByRefObject that is created with + // the transaction scope - that way, they can "clear current data" provided that + // they have the key - but they need to hold onto a ref to the scope... not ok for us + + public static void Clear() + { + lock (EnterFuncs) + { + foreach (var enter in EnterFuncs) + enter(); + } + } + + public SafeCallContext() + { + lock (EnterFuncs) + { + _count++; + _objects = EnterFuncs.Select(x => x()).ToList(); + } + } + + public void Dispose() + { + if (_disposed) throw new ObjectDisposedException("this"); + _disposed = true; + lock (EnterFuncs) + { + for (var i = 0; i < ExitActions.Count; i++) + ExitActions[i](_objects[i]); + _count--; + } + } + + // for unit tests ONLY + internal static void Reset() + { + lock (EnterFuncs) + { + if (_count > 0) throw new InvalidOperationException("Cannot reset while some SafeCallContext instances exist."); + EnterFuncs.Clear(); + ExitActions.Clear(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e180ed7efc..6da7f4519b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -523,6 +523,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 5919d23acc..1b42daa47c 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence { DatabaseContext = _dbContext, IsReady = true - }; + }; } [TearDown] @@ -102,7 +102,7 @@ namespace Umbraco.Tests.Persistence var appCtx = new ApplicationContext( _dbContext, - new ServiceContext(migrationEntryService: Mock.Of()), + new ServiceContext(migrationEntryService: Mock.Of()), CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of())); diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index 3cd94751ae..8f995a125e 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -102,7 +102,6 @@ namespace UmbracoExamine.DataServices { try { - var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); return result; } From 47c8e6854ee2aafc99fa7fc0b7d901fc59020adb Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 23 Nov 2016 14:24:08 +0100 Subject: [PATCH 19/52] Fixes: U4-9217 - Sanitize tags before storing them in the database --- .../src/views/propertyeditors/tags/tags.controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js index a1e48bbc99..d18ff73bd5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.TagsController", - function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) { + function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element, $sanitize) { var $typeahead; @@ -41,6 +41,7 @@ angular.module("umbraco") //Helper method to add a tag on enter or on typeahead select function addTag(tagToAdd) { + tagToAdd = $sanitize(tagToAdd); if (tagToAdd != null && tagToAdd.length > 0) { if ($scope.model.value.indexOf(tagToAdd) < 0) { $scope.model.value.push(tagToAdd); From 0689bf4f311cf44d374701d2a493a2dee2b3bfbc Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 23 Nov 2016 15:26:41 +0100 Subject: [PATCH 20/52] U4-9201 - fix it all --- src/Umbraco.Core/DatabaseContext.cs | 60 ++++++++++--------- .../Persistence/DefaultDatabaseFactory.cs | 18 +++++- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- src/Umbraco.Web/Scheduling/LogScrubber.cs | 2 +- .../Scheduling/ScheduledPublishing.cs | 6 +- .../ServerRegistrationEventHandler.cs | 2 +- .../DataServices/UmbracoContentService.cs | 9 ++- 7 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index f8f31ee695..27013d8ecd 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -94,13 +94,13 @@ namespace Umbraco.Core public ISqlSyntaxProvider SqlSyntax { get; private set; } /// - /// Gets the object for doing CRUD operations against custom tables that resides in the Umbraco database. + /// Gets an "ambient" database for doing CRUD operations against custom tables that resides in the Umbraco database. /// /// /// Should not be used for operation against standard Umbraco tables; as services should be used instead. /// Gets or creates an "ambient" database that is either stored in http context + available for the whole - /// request + auto-disposed at the end of the request, or in call context if there is no http context - in which - /// case it needs to be explicitely disposed so it is removed from call context. + /// request + auto-disposed at the end of the request, or stored in call context if there is no http context - in which + /// case it *must* be explicitely disposed (which will remove it from call context). /// public virtual UmbracoDatabase Database { @@ -108,24 +108,32 @@ namespace Umbraco.Core } /// - /// Replaces the "ambient" database (if any) and installs a new object. + /// Replaces the "ambient" database (if any) and by a new temp database. /// /// - /// The returned IDisposable *must* be diposed in order to properly disposed the safe database and + /// The returned IDisposable *must* be diposed in order to properly dispose the temp database and /// restore the original "ambient" database (if any). /// This is to be used in background tasks to ensure that they have an "ambient" database which /// will be properly removed from call context and does not interfere with anything else. In most case /// it is not replacing anything, just temporarily installing a database in context. /// - public virtual IDisposable UsingSafeDatabase + public virtual IDisposable UseSafeDatabase(bool force = false) { - get + var factory = _factory as DefaultDatabaseFactory; + if (factory == null) throw new NotSupportedException(); + if (DefaultDatabaseFactory.HasAmbientDatabase) { - var factory = _factory as DefaultDatabaseFactory; - if (factory == null) throw new NotSupportedException(); - var database = factory.CreateDatabaseInstance(DefaultDatabaseFactory.ContextOwner.None); - return new UsedDatabase(database, _logger); + // has ambient, + // if forcing, detach it and replace it with a new, temp, database + // when the UsingDatabase is disposed, the temp database is disposed and the original one is re-instated + // else do nothing (and nothing will be disposed) + return force + ? new UsingDatabase(DefaultDatabaseFactory.DetachAmbientDatabase(), factory.CreateDatabase()) + : new UsingDatabase(null, null); } + + // create a new, temp, database (will be disposed with UsingDatabase) + return new UsingDatabase(null, factory.CreateDatabase()); } /// @@ -788,30 +796,26 @@ namespace Umbraco.Core return true; } - private class UsedDatabase : IDisposable + private class UsingDatabase : IDisposable { - private readonly UmbracoDatabase _database; - private readonly ILogger _logger; + private readonly UmbracoDatabase _orig; + private readonly UmbracoDatabase _temp; - public UsedDatabase(UmbracoDatabase database, ILogger logger) + public UsingDatabase(UmbracoDatabase orig, UmbracoDatabase temp) { - _logger = logger; - - // replace with ours - _database = DefaultDatabaseFactory.DetachDatabase(); - _logger.Debug("Detached db " + (_database == null ? "x" : _database.InstanceSid) + "."); - DefaultDatabaseFactory.AttachDatabase(database); - _logger.Debug("Attached db " + database.InstanceSid + "."); + _orig = orig; + _temp = temp; } public void Dispose() { - // restore the original DB - var db = DefaultDatabaseFactory.DetachDatabase(); - _logger.Debug("Detached db " + db.InstanceSid + "."); - db.Dispose(); - DefaultDatabaseFactory.AttachDatabase(_database); - _logger.Debug("Attached db " + (_database == null ? "x" : _database.InstanceSid) + "."); + if (_temp != null) + { + _temp.Dispose(); + if (_orig != null) + DefaultDatabaseFactory.AttachAmbientDatabase(_orig); + } + GC.SuppressFinalize(this); } } } diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 5139da20d8..2ffee29490 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.Remoting.Messaging; using System.Web; using Umbraco.Core.Logging; @@ -250,12 +251,23 @@ namespace Umbraco.Core.Persistence static DefaultDatabaseFactory() { - SafeCallContext.Register(DetachDatabase, AttachDatabase); + SafeCallContext.Register(DetachAmbientDatabase, AttachAmbientDatabase); } + // gets a value indicating whether there is an ambient database + internal static bool HasAmbientDatabase + { + get + { + return HttpContext.Current == null + ? NonContextValue != null + : HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null; + } + } + // detaches the current database // ie returns the database and remove it from whatever is "context" - internal static UmbracoDatabase DetachDatabase() + internal static UmbracoDatabase DetachAmbientDatabase() { UmbracoDatabase database; @@ -277,7 +289,7 @@ namespace Umbraco.Core.Persistence // attach a current database // ie assign it to whatever is "context" // throws if there already is a database - internal static void AttachDatabase(object o) + internal static void AttachAmbientDatabase(object o) { var database = o as UmbracoDatabase; if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o"); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6da7f4519b..24d889a06f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -21,7 +21,7 @@ full false bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG;DEBUG_DATABASES prompt 4 false diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index da49404b4d..7737101961 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -78,7 +78,7 @@ namespace Umbraco.Web.Scheduling } // running on a background task, requires a safe database (see UsingSafeDatabase doc) - using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase(true)) using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index dc6c27959d..ba4fdddba5 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -83,10 +83,8 @@ namespace Umbraco.Web.Scheduling }; // running on a background task, requires a safe database (see UsingSafeDatabase doc) - // - // this is because GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database - // - using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + // (GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database) + using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase(true)) { //pass custom the authorization header request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 84b3e220ff..15ab8f244b 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -138,7 +138,7 @@ namespace Umbraco.Web.Strategies try { // running on a background task, requires a safe database (see UsingSafeDatabase doc) - using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase(true)) { _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); } diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index 4d2ea5bd9a..df0562f49c 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -13,7 +13,6 @@ namespace UmbracoExamine.DataServices { public class UmbracoContentService : IContentService { - private readonly ApplicationContext _applicationContext; public UmbracoContentService() @@ -58,7 +57,7 @@ namespace UmbracoExamine.DataServices [Obsolete("This should no longer be used, latest content will be indexed by using the IContentService directly")] public XDocument GetLatestContentByXPath(string xpath) { - using (_applicationContext.DatabaseContext.UsingSafeDatabase) + using (_applicationContext.DatabaseContext.UseSafeDatabase()) // reuse current db if any else use & dispose one { var xmlContent = XDocument.Parse(""); var rootContent = _applicationContext.Services.ContentService.GetRootContent(); @@ -80,7 +79,7 @@ namespace UmbracoExamine.DataServices /// public bool IsProtected(int nodeId, string path) { - using (_applicationContext.DatabaseContext.UsingSafeDatabase) + using (_applicationContext.DatabaseContext.UseSafeDatabase()) // reuse current db if any else use & dispose one { return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); } @@ -93,8 +92,8 @@ namespace UmbracoExamine.DataServices public IEnumerable GetAllUserPropertyNames() { - using (_applicationContext.DatabaseContext.UsingSafeDatabase) - { + using (_applicationContext.DatabaseContext.UseSafeDatabase()) // reuse current db if any else use & dispose one + { try { var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); From ed37b84b3b3825a88b4d3c65eafc0342cea71751 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Nov 2016 11:20:01 +0100 Subject: [PATCH 21/52] makes public the WeightedPluginAttribute, uses this for resolving the startup handlers so they could be sorted, adds special event to allow modifying the startup handlers. --- .../ApplicationEventsEventArgs.cs | 15 +++++++ .../ApplicationEventsResolver.cs | 45 +++++++++++++++---- .../WeightedPluginAttribute.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 4 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs new file mode 100644 index 0000000000..a459fc7268 --- /dev/null +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.ObjectResolution +{ + public class ApplicationEventsEventArgs : EventArgs + { + public List Handlers { get; private set; } + + public ApplicationEventsEventArgs(List handlers) + { + Handlers = handlers; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index 78c8f483f2..be94d7a3b9 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.ObjectResolution /// /// This is disposable because after the app has started it should be disposed to release any memory being occupied by instances. /// - internal sealed class ApplicationEventsResolver : ManyObjectsResolverBase, IDisposable + public sealed class ApplicationEventsResolver : ManyObjectsResolverBase, IDisposable { private readonly LegacyStartupHandlerResolver _legacyResolver; @@ -30,7 +30,7 @@ namespace Umbraco.Core.ObjectResolution //create the legacy resolver and only include the legacy types _legacyResolver = new LegacyStartupHandlerResolver( serviceProvider, logger, - applicationEventHandlers.Where(x => !TypeHelper.IsTypeAssignableFrom(x))); + applicationEventHandlers.Where(x => TypeHelper.IsTypeAssignableFrom(x) == false)); } /// @@ -42,14 +42,34 @@ namespace Umbraco.Core.ObjectResolution get { return base.InstanceTypes.Where(TypeHelper.IsTypeAssignableFrom); } } - /// - /// Gets the implementations. - /// - public IEnumerable ApplicationEventHandlers + private List _orderedAndFiltered; + + /// + /// Gets the implementations. + /// + public IEnumerable ApplicationEventHandlers { - get { return Values; } + get + { + if (_orderedAndFiltered == null) + { + _orderedAndFiltered = GetSortedValues().ToList(); + + //raise event so the collection can be modified + OnCollectionResolved(new ApplicationEventsEventArgs(_orderedAndFiltered)); + } + return _orderedAndFiltered; + } } + public event EventHandler CollectionResolved; + + private void OnCollectionResolved(ApplicationEventsEventArgs e) + { + var handler = CollectionResolved; + if (handler != null) handler(this, e); + } + /// /// Create instances of all of the legacy startup handlers /// @@ -146,7 +166,14 @@ namespace Umbraco.Core.ObjectResolution { _legacyResolver.Dispose(); ResetCollections(); + _orderedAndFiltered.Clear(); + _orderedAndFiltered = null; + + //Clear event handlers + CollectionResolved = null; + } - - } + + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs b/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs index 323142bd4c..69c763ed50 100644 --- a/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs +++ b/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.ObjectResolution /// Indicates the relative weight of a resolved object type. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - internal class WeightedPluginAttribute : Attribute + public class WeightedPluginAttribute : Attribute { /// /// Initializes a new instance of the class with a weight. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7a4c4f2363..3c58503b57 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -415,6 +415,7 @@ + From 0179540023cee8671c094f52142751db3f367c93 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 24 Nov 2016 18:13:05 +0100 Subject: [PATCH 22/52] Rename WeightAttribute, change default to 100, cleanup --- .../ApplicationEventsResolver.cs | 14 ++++------- .../LazyManyObjectsResolverbase.cs | 2 +- .../ManyObjectsResolverBase.cs | 8 +++--- .../WeightedPluginAttribute.cs | 25 ------------------- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Resolvers/ManyResolverTests.cs | 2 +- .../FileExtensionIconThumbnailProvider.cs | 2 +- .../ImageThumbnailProvider.cs | 2 +- .../MediaTypeIconThumbnailProvider.cs | 2 +- 9 files changed, 15 insertions(+), 44 deletions(-) delete mode 100644 src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index be94d7a3b9..6933279258 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -15,7 +15,6 @@ namespace Umbraco.Core.ObjectResolution /// public sealed class ApplicationEventsResolver : ManyObjectsResolverBase, IDisposable { - private readonly LegacyStartupHandlerResolver _legacyResolver; /// @@ -23,7 +22,7 @@ namespace Umbraco.Core.ObjectResolution /// /// /// - /// + /// internal ApplicationEventsResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable applicationEventHandlers) : base(serviceProvider, logger, applicationEventHandlers) { @@ -34,7 +33,7 @@ namespace Umbraco.Core.ObjectResolution } /// - /// Override in order to only return types of IApplicationEventHandler and above, + /// Override in order to only return types of IApplicationEventHandler and above, /// do not include the legacy types of IApplicationStartupHandler /// protected override IEnumerable InstanceTypes @@ -69,7 +68,7 @@ namespace Umbraco.Core.ObjectResolution var handler = CollectionResolved; if (handler != null) handler(this, e); } - + /// /// Create instances of all of the legacy startup handlers /// @@ -82,11 +81,11 @@ namespace Umbraco.Core.ObjectResolution protected override bool SupportsClear { get { return false; } - } + } protected override bool SupportsInsert { - get { return false; } + get { return false; } } private class LegacyStartupHandlerResolver : ManyObjectsResolverBase, IDisposable @@ -171,9 +170,6 @@ namespace Umbraco.Core.ObjectResolution //Clear event handlers CollectionResolved = null; - } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs index 2851a70e15..821b93ba03 100644 --- a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs +++ b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.ObjectResolution /// for when there is some processing overhead (i.e. Type finding in assemblies) to return the Types used to instantiate the instances. /// In some these cases we don't want to have to type-find during application startup, only when we need to resolve the instances. /// Important notes about this resolver: it does not support Insert or Remove and therefore does not support any ordering unless - /// the types are marked with the WeightedPluginAttribute. + /// the types are marked with the WeightAttribute. /// public abstract class LazyManyObjectsResolverBase : ManyObjectsResolverBase where TResolved : class diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index 1cfa81228a..3519bf86bc 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.ObjectResolution private readonly List _instanceTypes = new List(); private IEnumerable _sortedValues; - private int _defaultPluginWeight = 10; + private int _defaultPluginWeight = 100; #region Constructors @@ -179,7 +179,7 @@ namespace Umbraco.Core.ObjectResolution /// /// The sorted resolved object instances. /// - /// The order is based upon the WeightedPluginAttribute and DefaultPluginWeight. + /// The order is based upon the WeightAttribute and DefaultPluginWeight. /// Weights are sorted ascendingly (lowest weights come first). /// protected IEnumerable GetSortedValues() @@ -196,7 +196,7 @@ namespace Umbraco.Core.ObjectResolution /// /// Gets or sets the default type weight. /// - /// Determines the weight of types that do not have a WeightedPluginAttribute set on + /// Determines the weight of types that do not have a WeightAttribute set on /// them, when calling GetSortedValues. protected virtual int DefaultPluginWeight { @@ -212,7 +212,7 @@ namespace Umbraco.Core.ObjectResolution protected virtual int GetObjectWeight(object o) { var type = o.GetType(); - var attr = type.GetCustomAttribute(true); + var attr = type.GetCustomAttribute(true); return attr == null ? DefaultPluginWeight : attr.Weight; } diff --git a/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs b/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs deleted file mode 100644 index 69c763ed50..0000000000 --- a/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Indicates the relative weight of a resolved object type. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class WeightedPluginAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a weight. - /// - /// The object type weight. - public WeightedPluginAttribute(int weight) - { - Weight = weight; - } - - /// - /// Gets or sets the weight of the object type. - /// - public int Weight { get; private set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3c58503b57..dbd5199009 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1204,7 +1204,7 @@ - + diff --git a/src/Umbraco.Tests/Resolvers/ManyResolverTests.cs b/src/Umbraco.Tests/Resolvers/ManyResolverTests.cs index 4cf1c3b56f..3ec470de4b 100644 --- a/src/Umbraco.Tests/Resolvers/ManyResolverTests.cs +++ b/src/Umbraco.Tests/Resolvers/ManyResolverTests.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Resolvers public class Resolved1 : Resolved { } - [WeightedPlugin(5)] // default is 10 + [Weight(5)] // default is 100 public class Resolved2 : Resolved { } diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs index 4d6489cb2c..8699592467 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs @@ -8,7 +8,7 @@ using Umbraco.Core.IO; namespace Umbraco.Web.Media.ThumbnailProviders { - [WeightedPlugin(2000)] + [Weight(2000)] public class FileExtensionIconThumbnailProvider : AbstractThumbnailProvider { protected override IEnumerable SupportedExtensions diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs index 6f9b26c79d..56fe6306e3 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs @@ -10,7 +10,7 @@ using Umbraco.Core.IO; namespace Umbraco.Web.Media.ThumbnailProviders { - [WeightedPlugin(1000)] + [Weight(1000)] public class ImageThumbnailProvider : AbstractThumbnailProvider { protected override IEnumerable SupportedExtensions diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs index be1e6b4b42..6112d60471 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs @@ -8,7 +8,7 @@ using Umbraco.Core.IO; namespace Umbraco.Web.Media.ThumbnailProviders { - [WeightedPlugin(3000)] + [Weight(3000)] public class MediaTypeIconThumbnailProvider : AbstractThumbnailProvider { From 672568d1f57a893785f9d13e6992217221e0dba0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 24 Nov 2016 18:16:14 +0100 Subject: [PATCH 23/52] Add missing file --- .../ObjectResolution/WeightAttribute.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/Umbraco.Core/ObjectResolution/WeightAttribute.cs diff --git a/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs b/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs new file mode 100644 index 0000000000..f360443c55 --- /dev/null +++ b/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Umbraco.Core.ObjectResolution +{ + /// + /// Indicates the relative weight of a resolved object type. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class WeightAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a weight. + /// + /// The object type weight. + public WeightAttribute(int weight) + { + Weight = weight; + } + + /// + /// Gets or sets the weight of the object type. + /// + public int Weight { get; private set; } + } +} \ No newline at end of file From 81750c41bd50197fb8556af34212ae3f9cd4ffa1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Nov 2016 10:35:36 +0100 Subject: [PATCH 24/52] Removes events for filtering and instead use a special interface to perform the filtering, updates ctor for WeightAttribute to only accept positive numbers for public, internally it could be negative numbers, updates the filtering to always ensure that core handlers are at the beginning --- .../ApplicationEventsEventArgs.cs | 15 - .../ApplicationEventsResolver.cs | 53 +- .../IApplicationEventsFilter.cs | 12 + .../ManyObjectsResolverBase.cs | 631 +++++++++--------- .../ObjectResolution/WeightAttribute.cs | 19 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- 6 files changed, 384 insertions(+), 348 deletions(-) delete mode 100644 src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs create mode 100644 src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs deleted file mode 100644 index a459fc7268..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.ObjectResolution -{ - public class ApplicationEventsEventArgs : EventArgs - { - public List Handlers { get; private set; } - - public ApplicationEventsEventArgs(List handlers) - { - Handlers = handlers; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index 6933279258..53e8049ee9 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -7,7 +7,7 @@ using umbraco.interfaces; namespace Umbraco.Core.ObjectResolution { - /// + /// /// A resolver to return all IApplicationEvents objects /// /// @@ -52,21 +52,47 @@ namespace Umbraco.Core.ObjectResolution { if (_orderedAndFiltered == null) { - _orderedAndFiltered = GetSortedValues().ToList(); - - //raise event so the collection can be modified - OnCollectionResolved(new ApplicationEventsEventArgs(_orderedAndFiltered)); + _orderedAndFiltered = GetSortedValues().ToList(); + OnCollectionResolved(_orderedAndFiltered); } return _orderedAndFiltered; } } - - public event EventHandler CollectionResolved; - - private void OnCollectionResolved(ApplicationEventsEventArgs e) + + /// + /// Allow any filters to be applied to the event handler list + /// + /// + /// + /// This allows custom logic to execute in order to filter or re-order the event handlers prior to executing, + /// however this also ensures that any core handlers are executed first to ensure the stabiliy of Umbraco. + /// + private void OnCollectionResolved(List handlers) { - var handler = CollectionResolved; - if (handler != null) handler(this, e); + foreach (var filter in handlers.OfType().ToArray()) + { + filter.Filter(handlers); + } + + //find all of the core handlers and their weight, remove them from the main list + var coreItems = new List>(); + foreach (var handler in handlers.ToArray()) + { + //Yuck, but not sure what else we can do + if ( + handler.GetType().Assembly.FullName.StartsWith("Umbraco.", StringComparison.OrdinalIgnoreCase) + || handler.GetType().Assembly.FullName.StartsWith("Concorde.")) + { + coreItems.Add(new Tuple(handler, GetObjectWeight(handler))); + handlers.Remove(handler); + } + } + + //re-add the core handlers to the beginning of the list ordered by their weight + foreach (var coreHandler in coreItems.OrderBy(x => x.Item2)) + { + handlers.Insert(0, coreHandler.Item1); + } } /// @@ -166,10 +192,7 @@ namespace Umbraco.Core.ObjectResolution _legacyResolver.Dispose(); ResetCollections(); _orderedAndFiltered.Clear(); - _orderedAndFiltered = null; - - //Clear event handlers - CollectionResolved = null; + _orderedAndFiltered = null; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs b/src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs new file mode 100644 index 0000000000..e295e791d8 --- /dev/null +++ b/src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.ObjectResolution +{ + /// + /// This can be used to filter or re-order application events before they are executed + /// + public interface IApplicationEventsFilter + { + void Filter(List eventHandlers); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index 3519bf86bc..5e170f47f4 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -14,18 +14,18 @@ namespace Umbraco.Core.ObjectResolution /// The type of the concrete resolver class. /// The type of the resolved objects. public abstract class ManyObjectsResolverBase : ResolverBase - where TResolved : class + where TResolved : class where TResolver : ResolverBase - { - private Lazy> _applicationInstances; - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private readonly string _httpContextKey; - private readonly List _instanceTypes = new List(); - private IEnumerable _sortedValues; + { + private Lazy> _applicationInstances; + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + private readonly string _httpContextKey; + private readonly List _instanceTypes = new List(); + private IEnumerable _sortedValues; - private int _defaultPluginWeight = 100; + private int _defaultPluginWeight = 100; - #region Constructors + #region Constructors /// /// Initializes a new instance of the class with an empty list of objects, @@ -61,11 +61,11 @@ namespace Umbraco.Core.ObjectResolution [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use ctor specifying IServiceProvider instead")] - protected ManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) + protected ManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, scope) - { - - } + { + + } /// /// Initializes a new instance of the class with an empty list of objects, @@ -76,19 +76,19 @@ namespace Umbraco.Core.ObjectResolution /// The HttpContextBase corresponding to the HttpRequest. /// is null. protected ManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, HttpContextBase httpContext) - { + { if (serviceProvider == null) throw new ArgumentNullException("serviceProvider"); if (httpContext == null) throw new ArgumentNullException("httpContext"); CanResolveBeforeFrozen = false; Logger = logger; - LifetimeScope = ObjectLifetimeScope.HttpRequest; - _httpContextKey = GetType().FullName; + LifetimeScope = ObjectLifetimeScope.HttpRequest; + _httpContextKey = GetType().FullName; ServiceProvider = serviceProvider; CurrentHttpContext = httpContext; - _instanceTypes = new List(); + _instanceTypes = new List(); InitializeAppInstances(); - } + } [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use ctor specifying IServiceProvider instead")] @@ -110,57 +110,57 @@ namespace Umbraco.Core.ObjectResolution /// is per HttpRequest but the current HttpContext is null. protected ManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) : this(serviceProvider, logger, scope) - { - _instanceTypes = value.ToList(); - } + { + _instanceTypes = value.ToList(); + } [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use ctor specifying IServiceProvider instead")] protected ManyObjectsResolverBase(IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, value, scope) { - + } - /// - /// Initializes a new instance of the class with an initial list of objects, - /// with creation of objects based on an HttpRequest lifetime scope. - /// - /// The HttpContextBase corresponding to the HttpRequest. - /// The list of object types. - /// is null. + /// + /// Initializes a new instance of the class with an initial list of objects, + /// with creation of objects based on an HttpRequest lifetime scope. + /// + /// The HttpContextBase corresponding to the HttpRequest. + /// The list of object types. + /// is null. [Obsolete("Use ctor specifying IServiceProvider instead")] protected ManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable value) : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, httpContext) - { - _instanceTypes = value.ToList(); - } - #endregion + { + _instanceTypes = value.ToList(); + } + #endregion private void InitializeAppInstances() { _applicationInstances = new Lazy>(() => CreateInstances().ToArray()); } - /// - /// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen. - /// - /// This is false by default and is used for some special internal resolvers. - internal bool CanResolveBeforeFrozen { get; set; } + /// + /// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen. + /// + /// This is false by default and is used for some special internal resolvers. + internal bool CanResolveBeforeFrozen { get; set; } - /// - /// Gets the list of types to create instances from. - /// - protected virtual IEnumerable InstanceTypes - { - get { return _instanceTypes; } - } + /// + /// Gets the list of types to create instances from. + /// + protected virtual IEnumerable InstanceTypes + { + get { return _instanceTypes; } + } - /// - /// Gets or sets the used to initialize this object, if any. - /// - /// If not null, then LifetimeScope will be ObjectLifetimeScope.HttpRequest. - protected HttpContextBase CurrentHttpContext { get; private set; } + /// + /// Gets or sets the used to initialize this object, if any. + /// + /// If not null, then LifetimeScope will be ObjectLifetimeScope.HttpRequest. + protected HttpContextBase CurrentHttpContext { get; private set; } /// /// Returns the service provider used to instantiate objects @@ -174,16 +174,16 @@ namespace Umbraco.Core.ObjectResolution /// protected ObjectLifetimeScope LifetimeScope { get; private set; } - /// - /// Gets the resolved object instances, sorted by weight. - /// - /// The sorted resolved object instances. - /// - /// The order is based upon the WeightAttribute and DefaultPluginWeight. - /// Weights are sorted ascendingly (lowest weights come first). - /// - protected IEnumerable GetSortedValues() - { + /// + /// Gets the resolved object instances, sorted by weight. + /// + /// The sorted resolved object instances. + /// + /// The order is based upon the WeightAttribute and DefaultPluginWeight. + /// Weights are sorted ascendingly (lowest weights come first). + /// + protected IEnumerable GetSortedValues() + { if (_sortedValues == null) { var values = Values.ToList(); @@ -191,18 +191,18 @@ namespace Umbraco.Core.ObjectResolution _sortedValues = values; } return _sortedValues; - } + } - /// - /// Gets or sets the default type weight. - /// - /// Determines the weight of types that do not have a WeightAttribute set on - /// them, when calling GetSortedValues. - protected virtual int DefaultPluginWeight - { - get { return _defaultPluginWeight; } - set { _defaultPluginWeight = value; } - } + /// + /// Gets or sets the default type weight. + /// + /// Determines the weight of types that do not have a WeightAttribute set on + /// them, when calling GetSortedValues. + protected virtual int DefaultPluginWeight + { + get { return _defaultPluginWeight; } + set { _defaultPluginWeight = value; } + } /// /// Returns the weight of an object for user with GetSortedValues @@ -210,22 +210,22 @@ namespace Umbraco.Core.ObjectResolution /// /// protected virtual int GetObjectWeight(object o) - { - var type = o.GetType(); - var attr = type.GetCustomAttribute(true); - return attr == null ? DefaultPluginWeight : attr.Weight; - } + { + var type = o.GetType(); + var attr = type.GetCustomAttribute(true); + return attr == null ? DefaultPluginWeight : attr.Weight; + } - /// - /// Gets the resolved object instances. - /// - /// CanResolveBeforeFrozen is false, and resolution is not frozen. - protected IEnumerable Values - { - get - { - using (Resolution.Reader(CanResolveBeforeFrozen)) - { + /// + /// Gets the resolved object instances. + /// + /// CanResolveBeforeFrozen is false, and resolution is not frozen. + protected IEnumerable Values + { + get + { + using (Resolution.Reader(CanResolveBeforeFrozen)) + { // note: we apply .ToArray() to the output of CreateInstance() because that is an IEnumerable that // comes from the PluginManager we want to be _sure_ that it's not a Linq of some sort, but the // instances have actually been instanciated when we return. @@ -247,7 +247,7 @@ namespace Umbraco.Core.ObjectResolution CurrentHttpContext.Items[_httpContextKey] = instances; } return (TResolved[])CurrentHttpContext.Items[_httpContextKey]; - + case ObjectLifetimeScope.Application: return _applicationInstances.Value; @@ -258,132 +258,132 @@ namespace Umbraco.Core.ObjectResolution return CreateInstances().ToArray(); } } - } - } + } + } - /// - /// Creates the object instances for the types contained in the types collection. - /// - /// A list of objects of type . - protected virtual IEnumerable CreateInstances() - { - return ServiceProvider.CreateInstances(InstanceTypes, Logger); - } + /// + /// Creates the object instances for the types contained in the types collection. + /// + /// A list of objects of type . + protected virtual IEnumerable CreateInstances() + { + return ServiceProvider.CreateInstances(InstanceTypes, Logger); + } - #region Types collection manipulation + #region Types collection manipulation - /// - /// Removes a type. - /// - /// The type to remove. - /// the resolver does not support removing types, or - /// the type is not a valid type for the resolver. - public virtual void RemoveType(Type value) - { - EnsureSupportsRemove(); + /// + /// Removes a type. + /// + /// The type to remove. + /// the resolver does not support removing types, or + /// the type is not a valid type for the resolver. + public virtual void RemoveType(Type value) + { + EnsureSupportsRemove(); - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(value); + using (Resolution.Configuration) + using (var l = new UpgradeableReadLock(_lock)) + { + EnsureCorrectType(value); - l.UpgradeToWriteLock(); - _instanceTypes.Remove(value); - } - } + l.UpgradeToWriteLock(); + _instanceTypes.Remove(value); + } + } - /// - /// Removes a type. - /// - /// The type to remove. - /// the resolver does not support removing types, or - /// the type is not a valid type for the resolver. - public void RemoveType() + /// + /// Removes a type. + /// + /// The type to remove. + /// the resolver does not support removing types, or + /// the type is not a valid type for the resolver. + public void RemoveType() where T : TResolved - { - RemoveType(typeof(T)); - } + { + RemoveType(typeof(T)); + } - /// - /// Adds types. - /// - /// The types to add. - /// The types are appended at the end of the list. - /// the resolver does not support adding types, or - /// a type is not a valid type for the resolver, or a type is already in the collection of types. - protected void AddTypes(IEnumerable types) - { - EnsureSupportsAdd(); + /// + /// Adds types. + /// + /// The types to add. + /// The types are appended at the end of the list. + /// the resolver does not support adding types, or + /// a type is not a valid type for the resolver, or a type is already in the collection of types. + protected void AddTypes(IEnumerable types) + { + EnsureSupportsAdd(); - using (Resolution.Configuration) - using (new WriteLock(_lock)) - { - foreach(var t in types) - { - EnsureCorrectType(t); + using (Resolution.Configuration) + using (new WriteLock(_lock)) + { + foreach (var t in types) + { + EnsureCorrectType(t); if (_instanceTypes.Contains(t)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", t.FullName)); - } - _instanceTypes.Add(t); - } - } - } + { + throw new InvalidOperationException(string.Format( + "Type {0} is already in the collection of types.", t.FullName)); + } + _instanceTypes.Add(t); + } + } + } - /// - /// Adds a type. - /// - /// The type to add. - /// The type is appended at the end of the list. - /// the resolver does not support adding types, or - /// the type is not a valid type for the resolver, or the type is already in the collection of types. - public virtual void AddType(Type value) - { - EnsureSupportsAdd(); + /// + /// Adds a type. + /// + /// The type to add. + /// The type is appended at the end of the list. + /// the resolver does not support adding types, or + /// the type is not a valid type for the resolver, or the type is already in the collection of types. + public virtual void AddType(Type value) + { + EnsureSupportsAdd(); - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(value); + using (Resolution.Configuration) + using (var l = new UpgradeableReadLock(_lock)) + { + EnsureCorrectType(value); if (_instanceTypes.Contains(value)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", value.FullName)); - } + { + throw new InvalidOperationException(string.Format( + "Type {0} is already in the collection of types.", value.FullName)); + } - l.UpgradeToWriteLock(); - _instanceTypes.Add(value); - } - } + l.UpgradeToWriteLock(); + _instanceTypes.Add(value); + } + } - /// - /// Adds a type. - /// - /// The type to add. - /// The type is appended at the end of the list. - /// the resolver does not support adding types, or - /// the type is not a valid type for the resolver, or the type is already in the collection of types. - public void AddType() + /// + /// Adds a type. + /// + /// The type to add. + /// The type is appended at the end of the list. + /// the resolver does not support adding types, or + /// the type is not a valid type for the resolver, or the type is already in the collection of types. + public void AddType() where T : TResolved - { - AddType(typeof(T)); - } + { + AddType(typeof(T)); + } - /// - /// Clears the list of types - /// - /// the resolver does not support clearing types. - public virtual void Clear() - { - EnsureSupportsClear(); + /// + /// Clears the list of types + /// + /// the resolver does not support clearing types. + public virtual void Clear() + { + EnsureSupportsClear(); - using (Resolution.Configuration) - using (new WriteLock(_lock)) - { - _instanceTypes.Clear(); - } - } + using (Resolution.Configuration) + using (new WriteLock(_lock)) + { + _instanceTypes.Clear(); + } + } /// /// WARNING! Do not use this unless you know what you are doing, clear all types registered and instances @@ -399,32 +399,32 @@ namespace Umbraco.Core.ObjectResolution } } - /// - /// Inserts a type at the specified index. - /// - /// The zero-based index at which the type should be inserted. - /// The type to insert. - /// the resolver does not support inserting types, or - /// the type is not a valid type for the resolver, or the type is already in the collection of types. - /// is out of range. - public virtual void InsertType(int index, Type value) - { - EnsureSupportsInsert(); + /// + /// Inserts a type at the specified index. + /// + /// The zero-based index at which the type should be inserted. + /// The type to insert. + /// the resolver does not support inserting types, or + /// the type is not a valid type for the resolver, or the type is already in the collection of types. + /// is out of range. + public virtual void InsertType(int index, Type value) + { + EnsureSupportsInsert(); - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(value); + using (Resolution.Configuration) + using (var l = new UpgradeableReadLock(_lock)) + { + EnsureCorrectType(value); if (_instanceTypes.Contains(value)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", value.FullName)); - } + { + throw new InvalidOperationException(string.Format( + "Type {0} is already in the collection of types.", value.FullName)); + } - l.UpgradeToWriteLock(); - _instanceTypes.Insert(index, value); - } - } + l.UpgradeToWriteLock(); + _instanceTypes.Insert(index, value); + } + } /// /// Inserts a type at the beginning of the list. @@ -445,9 +445,9 @@ namespace Umbraco.Core.ObjectResolution /// is out of range. public void InsertType(int index) where T : TResolved - { - InsertType(index, typeof(T)); - } + { + InsertType(index, typeof(T)); + } /// /// Inserts a type at the beginning of the list. @@ -458,68 +458,68 @@ namespace Umbraco.Core.ObjectResolution { InsertType(0, typeof(T)); } - - /// - /// Inserts a type before a specified, already existing type. - /// - /// The existing type before which to insert. - /// The type to insert. - /// the resolver does not support inserting types, or - /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, - /// or the new type is already in the collection of types. - public virtual void InsertTypeBefore(Type existingType, Type value) - { - EnsureSupportsInsert(); - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(existingType); - EnsureCorrectType(value); + /// + /// Inserts a type before a specified, already existing type. + /// + /// The existing type before which to insert. + /// The type to insert. + /// the resolver does not support inserting types, or + /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, + /// or the new type is already in the collection of types. + public virtual void InsertTypeBefore(Type existingType, Type value) + { + EnsureSupportsInsert(); + + using (Resolution.Configuration) + using (var l = new UpgradeableReadLock(_lock)) + { + EnsureCorrectType(existingType); + EnsureCorrectType(value); if (_instanceTypes.Contains(existingType) == false) - { - throw new InvalidOperationException(string.Format( - "Type {0} is not in the collection of types.", existingType.FullName)); - } + { + throw new InvalidOperationException(string.Format( + "Type {0} is not in the collection of types.", existingType.FullName)); + } if (_instanceTypes.Contains(value)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", value.FullName)); - } + { + throw new InvalidOperationException(string.Format( + "Type {0} is already in the collection of types.", value.FullName)); + } int index = _instanceTypes.IndexOf(existingType); - l.UpgradeToWriteLock(); - _instanceTypes.Insert(index, value); - } - } + l.UpgradeToWriteLock(); + _instanceTypes.Insert(index, value); + } + } - /// - /// Inserts a type before a specified, already existing type. - /// - /// The existing type before which to insert. - /// The type to insert. - /// the resolver does not support inserting types, or - /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, - /// or the new type is already in the collection of types. - public void InsertTypeBefore() + /// + /// Inserts a type before a specified, already existing type. + /// + /// The existing type before which to insert. + /// The type to insert. + /// the resolver does not support inserting types, or + /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, + /// or the new type is already in the collection of types. + public void InsertTypeBefore() where TExisting : TResolved where T : TResolved - { - InsertTypeBefore(typeof(TExisting), typeof(T)); - } + { + InsertTypeBefore(typeof(TExisting), typeof(T)); + } - /// - /// Returns a value indicating whether the specified type is already in the collection of types. - /// - /// The type to look for. - /// A value indicating whether the type is already in the collection of types. - public virtual bool ContainsType(Type value) - { - using (new ReadLock(_lock)) - { - return _instanceTypes.Contains(value); - } - } + /// + /// Returns a value indicating whether the specified type is already in the collection of types. + /// + /// The type to look for. + /// A value indicating whether the type is already in the collection of types. + public virtual bool ContainsType(Type value) + { + using (new ReadLock(_lock)) + { + return _instanceTypes.Contains(value); + } + } /// /// Gets the types in the collection of types. @@ -536,27 +536,27 @@ namespace Umbraco.Core.ObjectResolution return types; } - /// - /// Returns a value indicating whether the specified type is already in the collection of types. - /// - /// The type to look for. - /// A value indicating whether the type is already in the collection of types. - public bool ContainsType() + /// + /// Returns a value indicating whether the specified type is already in the collection of types. + /// + /// The type to look for. + /// A value indicating whether the type is already in the collection of types. + public bool ContainsType() where T : TResolved - { - return ContainsType(typeof(T)); - } + { + return ContainsType(typeof(T)); + } - #endregion + #endregion - /// - /// Returns a WriteLock to use when modifying collections - /// - /// - protected WriteLock GetWriteLock() - { - return new WriteLock(_lock); - } + /// + /// Returns a WriteLock to use when modifying collections + /// + /// + protected WriteLock GetWriteLock() + { + return new WriteLock(_lock); + } #region Type utilities @@ -581,70 +581,71 @@ namespace Umbraco.Core.ObjectResolution /// /// The resolver does not support removing types. protected void EnsureSupportsRemove() - { - if (SupportsRemove == false) + { + if (SupportsRemove == false) throw new InvalidOperationException("This resolver does not support removing types"); - } + } /// /// Ensures that the resolver supports clearing types. /// /// The resolver does not support clearing types. - protected void EnsureSupportsClear() { - if (SupportsClear == false) + protected void EnsureSupportsClear() + { + if (SupportsClear == false) throw new InvalidOperationException("This resolver does not support clearing types"); - } + } /// /// Ensures that the resolver supports adding types. /// /// The resolver does not support adding types. protected void EnsureSupportsAdd() - { - if (SupportsAdd == false) + { + if (SupportsAdd == false) throw new InvalidOperationException("This resolver does not support adding new types"); - } + } /// /// Ensures that the resolver supports inserting types. /// /// The resolver does not support inserting types. protected void EnsureSupportsInsert() - { - if (SupportsInsert == false) + { + if (SupportsInsert == false) throw new InvalidOperationException("This resolver does not support inserting new types"); - } + } /// /// Gets a value indicating whether the resolver supports adding types. /// protected virtual bool SupportsAdd - { - get { return true; } - } + { + get { return true; } + } /// /// Gets a value indicating whether the resolver supports inserting types. /// protected virtual bool SupportsInsert - { - get { return true; } - } + { + get { return true; } + } /// /// Gets a value indicating whether the resolver supports clearing types. /// protected virtual bool SupportsClear - { - get { return true; } - } + { + get { return true; } + } /// /// Gets a value indicating whether the resolver supports removing types. /// protected virtual bool SupportsRemove - { - get { return true; } + { + get { return true; } } #endregion diff --git a/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs b/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs index f360443c55..35f10a4111 100644 --- a/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs +++ b/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs @@ -8,13 +8,28 @@ namespace Umbraco.Core.ObjectResolution [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class WeightAttribute : Attribute { + /// + /// Initializes a new instance of the class with a weight. + /// + /// The object type weight. + /// + /// This internal constructor allows for internal Umbraco products to set a negative number weight + /// + internal WeightAttribute(int weight) + { + Weight = weight; + } + /// /// Initializes a new instance of the class with a weight. /// /// The object type weight. - public WeightAttribute(int weight) + /// + /// The weight must be a positive number + /// + public WeightAttribute(uint weight) { - Weight = weight; + Weight = Convert.ToInt32(weight); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index dbd5199009..2619a1c80c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -415,7 +415,7 @@ - + From a44a670f507ad29d6f38e55c0a1e23537306582c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Nov 2016 14:14:43 +0100 Subject: [PATCH 25/52] Removed the IApplicationEventsFilter interface, adds a delegate property that can be set instead --- .../ApplicationEventsResolver.cs | 17 ++++++++++++----- .../IApplicationEventsFilter.cs | 12 ------------ src/Umbraco.Core/Umbraco.Core.csproj | 1 - 3 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index 53e8049ee9..dd22c2bc28 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -58,7 +58,15 @@ namespace Umbraco.Core.ObjectResolution return _orderedAndFiltered; } } - + + /// + /// A delegate that can be set in the pre-boot phase in order to filter or re-order the event handler collection + /// + /// + /// This can be set on startup in the pre-boot process in either a custom boot manager or global.asax (UmbracoApplication) + /// + public Action> FilterCollection { get; set; } + /// /// Allow any filters to be applied to the event handler list /// @@ -69,10 +77,9 @@ namespace Umbraco.Core.ObjectResolution /// private void OnCollectionResolved(List handlers) { - foreach (var filter in handlers.OfType().ToArray()) - { - filter.Filter(handlers); - } + if (FilterCollection == null) return; + + FilterCollection(handlers); //find all of the core handlers and their weight, remove them from the main list var coreItems = new List>(); diff --git a/src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs b/src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs deleted file mode 100644 index e295e791d8..0000000000 --- a/src/Umbraco.Core/ObjectResolution/IApplicationEventsFilter.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// This can be used to filter or re-order application events before they are executed - /// - public interface IApplicationEventsFilter - { - void Filter(List eventHandlers); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2619a1c80c..e0eb63b265 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -415,7 +415,6 @@ - From 531fa9f25a26ddba4b583feea1ff3a5b866b202c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Nov 2016 14:34:04 +0100 Subject: [PATCH 26/52] Ensures the delegate cannot be set once the event handlers are resolved. --- .../ApplicationEventsResolver.cs | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index dd22c2bc28..880eac9591 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -52,6 +52,7 @@ namespace Umbraco.Core.ObjectResolution { if (_orderedAndFiltered == null) { + _resolved = true; _orderedAndFiltered = GetSortedValues().ToList(); OnCollectionResolved(_orderedAndFiltered); } @@ -59,23 +60,33 @@ namespace Umbraco.Core.ObjectResolution } } - /// - /// A delegate that can be set in the pre-boot phase in order to filter or re-order the event handler collection - /// - /// - /// This can be set on startup in the pre-boot process in either a custom boot manager or global.asax (UmbracoApplication) - /// - public Action> FilterCollection { get; set; } + /// + /// A delegate that can be set in the pre-boot phase in order to filter or re-order the event handler collection + /// + /// + /// This can be set on startup in the pre-boot process in either a custom boot manager or global.asax (UmbracoApplication) + /// + public Action> FilterCollection + { + get { return _filterCollection; } + set + { + if (_resolved) + throw new InvalidOperationException("Cannot set the FilterCollection delegate once the ApplicationEventHandlers are resolved"); - /// - /// Allow any filters to be applied to the event handler list - /// - /// - /// - /// This allows custom logic to execute in order to filter or re-order the event handlers prior to executing, - /// however this also ensures that any core handlers are executed first to ensure the stabiliy of Umbraco. - /// - private void OnCollectionResolved(List handlers) + _filterCollection = value; + } + } + + /// + /// Allow any filters to be applied to the event handler list + /// + /// + /// + /// This allows custom logic to execute in order to filter or re-order the event handlers prior to executing, + /// however this also ensures that any core handlers are executed first to ensure the stabiliy of Umbraco. + /// + private void OnCollectionResolved(List handlers) { if (FilterCollection == null) return; @@ -142,8 +153,10 @@ namespace Umbraco.Core.ObjectResolution private bool _disposed; private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); + private Action> _filterCollection; + private bool _resolved = false; - /// + /// /// Gets a value indicating whether this instance is disposed. /// /// From 0e61d18b9e24a63d5fd1f7d881c05ef4526caefa Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Nov 2016 15:53:08 +0100 Subject: [PATCH 27/52] Ensure core handlers are always first no matter what --- .../ObjectResolution/ApplicationEventsResolver.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index 880eac9591..a90a870c30 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -88,9 +88,10 @@ namespace Umbraco.Core.ObjectResolution /// private void OnCollectionResolved(List handlers) { - if (FilterCollection == null) return; - - FilterCollection(handlers); + if (FilterCollection != null) + { + FilterCollection(handlers); + } //find all of the core handlers and their weight, remove them from the main list var coreItems = new List>(); From 14bf4bbd3253ae9380fe06ce08350e6db3b0f67b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Nov 2016 15:55:33 +0100 Subject: [PATCH 28/52] throws exception if the delegate is already set --- src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index a90a870c30..6cb60e2df0 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -73,6 +73,8 @@ namespace Umbraco.Core.ObjectResolution { if (_resolved) throw new InvalidOperationException("Cannot set the FilterCollection delegate once the ApplicationEventHandlers are resolved"); + if (_filterCollection != null) + throw new InvalidOperationException("Cannot set the FilterCollection delegate once it's already been specified"); _filterCollection = value; } From eeb94e1668433251fa6af2d47e2f14f2d076ec0f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Nov 2016 18:07:27 +0100 Subject: [PATCH 29/52] U4-9222 The in memory XML cache is cloned everytime there is a content save even if there is nothing published --- src/Umbraco.Web/umbraco.presentation/content.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 777fe80782..26076f8727 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -329,6 +329,17 @@ namespace umbraco if (c.Published) return; + //if it's a brand new entity, then this shouldn't affect the sort order of the document + if (c.IsNewEntity()) + return; + + //if the sort order didn't change and the published state didn't change then we don't need to continue + if (c.WasPropertyDirty("SortOrder") == false + && c.WasPropertyDirty("Published") == false) + { + return; + } + using (var safeXml = GetSafeXmlWriter(false)) { //TODO: This can be null: safeXml.Xml!!!! From 5b5e1847eba2dc4e1b5ad3257a54f29e0ecc4588 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Nov 2016 18:08:18 +0100 Subject: [PATCH 30/52] Updates legacy CMSNode and Document to use the PetaPoco Database intead of SqlHelper for the methods that the xml cache uses to update itself (so it's using one connection) --- src/umbraco.cms/businesslogic/CMSNode.cs | 30 +++++++++---------- src/umbraco.cms/businesslogic/web/Document.cs | 10 +++++-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/umbraco.cms/businesslogic/CMSNode.cs b/src/umbraco.cms/businesslogic/CMSNode.cs index 632227a3c5..773c3da4c4 100644 --- a/src/umbraco.cms/businesslogic/CMSNode.cs +++ b/src/umbraco.cms/businesslogic/CMSNode.cs @@ -1103,23 +1103,24 @@ order by level,sortOrder"; protected virtual XmlNode GetPreviewXml(XmlDocument xd, Guid version) { + var xmlDoc = new XmlDocument(); - XmlDocument xmlDoc = new XmlDocument(); - using (XmlReader xmlRdr = SqlHelper.ExecuteXmlReader( - "select xml from cmsPreviewXml where nodeID = @nodeId and versionId = @versionId", - SqlHelper.CreateParameter("@nodeId", Id), - SqlHelper.CreateParameter("@versionId", version))) - { - xmlDoc.Load(xmlRdr); - } + var xmlStr = ApplicationContext.Current.DatabaseContext.Database.ExecuteScalar( + "select xml from cmsPreviewXml where nodeID = @nodeId and versionId = @versionId", + new { nodeId = Id, versionId = version }); + if (xmlStr.IsNullOrWhiteSpace()) return null; + + xmlDoc.LoadXml(xmlStr); + return xd.ImportNode(xmlDoc.FirstChild, true); } protected internal virtual bool PreviewExists(Guid versionId) { - return (SqlHelper.ExecuteScalar("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId=@nodeId and versionId = @versionId", - SqlHelper.CreateParameter("@nodeId", Id), SqlHelper.CreateParameter("@versionId", versionId)) != 0); + return ApplicationContext.Current.DatabaseContext.Database.ExecuteScalar( + "SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId=@nodeId and versionId = @versionId", + new {nodeId = Id, versionId = versionId}) != 0; } @@ -1133,11 +1134,10 @@ order by level,sortOrder"; { string sql = PreviewExists(versionId) ? "UPDATE cmsPreviewXml SET xml = @xml, timestamp = @timestamp WHERE nodeId=@nodeId AND versionId = @versionId" : "INSERT INTO cmsPreviewXml(nodeId, versionId, timestamp, xml) VALUES (@nodeId, @versionId, @timestamp, @xml)"; - SqlHelper.ExecuteNonQuery(sql, - SqlHelper.CreateParameter("@nodeId", Id), - SqlHelper.CreateParameter("@versionId", versionId), - SqlHelper.CreateParameter("@timestamp", DateTime.Now), - SqlHelper.CreateParameter("@xml", x.OuterXml)); + + ApplicationContext.Current.DatabaseContext.Database.Execute( + sql, new {nodeId = Id, versionId = versionId, timestamp = DateTime.Now, xml = x.OuterXml}); + } protected void PopulateCMSNodeFromReader(IRecordsReader dr) diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 43f375671e..7ae25c4731 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1551,9 +1551,13 @@ namespace umbraco.cms.businesslogic.web private XmlNode importXml() { XmlDocument xmlDoc = new XmlDocument(); - XmlReader xmlRdr = SqlHelper.ExecuteXmlReader(string.Format( - "select xml from cmsContentXml where nodeID = {0}", Id)); - xmlDoc.Load(xmlRdr); + + var xmlStr = ApplicationContext.Current.DatabaseContext.Database.ExecuteScalar( + "select xml from cmsContentXml where nodeID = @nodeId", new {nodeId = Id}); + + if (xmlStr.IsNullOrWhiteSpace()) return null; + + xmlDoc.LoadXml(xmlStr); return xmlDoc.FirstChild; } From ee3a4614c3e3c14d4885eee1b312ecaf25f9cc3e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 25 Nov 2016 22:18:35 +0100 Subject: [PATCH 31/52] Refactor all usages of SqlHelper, ExecuteReader and ExecuteXmlReader into using blocks --- src/SQLCE4Umbraco/SqlCEInstaller.cs | 46 +-- src/SQLCE4Umbraco/SqlCETableUtility.cs | 28 +- src/Umbraco.Web/UI/Controls/UmbracoControl.cs | 7 +- .../UI/Controls/UmbracoUserControl.cs | 3 +- .../umbraco.presentation/content.cs | 4 + .../umbraco.presentation/library.cs | 16 +- src/Umbraco.Web/umbraco.presentation/macro.cs | 4 + .../umbraco.presentation/template.cs | 13 +- .../umbraco/Trees/loadMacros.cs | 10 +- .../umbraco/create/macroTasks.cs | 5 +- .../RelationTypes/EditRelationType.aspx.cs | 5 +- .../umbraco/dialogs/editMacro.aspx.cs | 25 +- .../umbraco/dialogs/insertMacro.aspx.cs | 140 +++---- .../umbraco/dialogs/umbracoField.aspx.cs | 37 +- .../plugins/tinymce3/insertMacro.aspx.cs | 16 +- .../umbraco/settings/editTemplate.aspx.cs | 27 +- .../umbraco/uQuery/PreValueExtensions.cs | 10 +- .../umbraco/uQuery/RelationTypeExtensions.cs | 19 +- .../umbraco/uQuery/uQuery-Content.cs | 4 +- .../umbraco/uQuery/uQuery-DataTypes.cs | 3 +- .../umbraco/uQuery/uQuery-PreValues.cs | 49 +-- .../uQuery/uQuery-UmbracoObjectType.cs | 9 +- .../umbraco/uQuery/uQuery.cs | 33 +- .../TagsAutoCompleteHandler.ashx.cs | 60 +-- .../BasePages/BasePage.cs | 4 +- src/umbraco.businesslogic/Log.cs | 131 ++++--- src/umbraco.businesslogic/User.cs | 31 +- src/umbraco.cms/businesslogic/CMSNode.cs | 259 +++++++----- src/umbraco.cms/businesslogic/Content.cs | 174 +++++---- src/umbraco.cms/businesslogic/ContentType.cs | 186 +++++---- src/umbraco.cms/businesslogic/Dictionary.cs | 3 + .../businesslogic/Packager/Package.cs | 118 +++--- .../businesslogic/Property/Property.cs | 64 +-- src/umbraco.cms/businesslogic/RecycleBin.cs | 11 +- src/umbraco.cms/businesslogic/Tags/Tag.cs | 110 +++--- .../businesslogic/datatype/BaseDataType.cs | 21 +- .../datatype/DataEditorSettingsStorage.cs | 26 +- .../businesslogic/datatype/DefaultData.cs | 3 + .../datatype/DefaultPreValueEditor.cs | 28 +- .../businesslogic/datatype/PreValue.cs | 104 ++--- .../businesslogic/datatype/PreValues.cs | 45 +-- .../businesslogic/language/Item.cs | 4 +- .../businesslogic/language/Language.cs | 3 +- src/umbraco.cms/businesslogic/macro/Macro.cs | 6 +- .../businesslogic/macro/MacroProperty.cs | 48 ++- .../businesslogic/macro/macroPropertyType.cs | 4 + .../businesslogic/member/Member.cs | 108 ++--- .../businesslogic/member/MemberGroup.cs | 8 +- .../propertytype/PropertyTypeGroup.cs | 74 ++-- .../propertytype/propertytype.cs | 139 ++++--- .../businesslogic/relation/Relation.cs | 4 + .../businesslogic/relation/RelationType.cs | 7 +- src/umbraco.cms/businesslogic/task/Task.cs | 4 +- .../businesslogic/task/TaskType.cs | 4 + .../businesslogic/template/Template.cs | 3 +- src/umbraco.cms/businesslogic/web/Document.cs | 60 +-- .../businesslogic/web/DocumentType.cs | 20 +- .../businesslogic/workflow/Notification.cs | 3 +- .../SqlHelpers/MySql/MySqlInstaller.cs | 3 +- .../SqlServer/SqlServerTableUtility.cs | 28 +- .../Installer/DefaultInstallerUtility.cs | 9 +- src/umbraco.editorControls/BaseDataType.cs | 128 +++--- src/umbraco.editorControls/DefaultData.cs | 145 +++---- .../DefaultDataKeyValue.cs | 17 +- .../DefaultPrevalueEditor.cs | 59 +-- .../KeyValuePrevalueEditor.cs | 204 +++++----- .../PickerRelationsDataEditor.cs | 13 +- .../PickerRelationsEventHandler.cs | 9 +- .../SettingControls/Pickers/Field.cs | 16 +- .../imagecropper/PrevalueEditor.cs | 32 +- .../macrocontainer/PrevalueEditor.cs | 23 +- .../mediapicker/MediaPickerPrevalueEditor.cs | 48 ++- .../memberpicker/memberPicker.cs | 34 +- .../tags/PrevalueEditor.cs | 46 ++- .../tinymce/tinyMCEPreValueConfigurator.cs | 164 ++++---- .../ultimatePickerPrevalueEditor.cs | 50 +-- .../uploadfield/uploadFieldPreValue.cs | 40 +- .../usercontrolPrevalueEditor.cs | 369 +++++++++--------- 78 files changed, 2143 insertions(+), 1684 deletions(-) diff --git a/src/SQLCE4Umbraco/SqlCEInstaller.cs b/src/SQLCE4Umbraco/SqlCEInstaller.cs index aeb40ed811..80a669e59c 100644 --- a/src/SQLCE4Umbraco/SqlCEInstaller.cs +++ b/src/SQLCE4Umbraco/SqlCEInstaller.cs @@ -21,15 +21,15 @@ namespace SqlCE4Umbraco public class SqlCEInstaller : DefaultInstallerUtility { #region Private Constants - + /// The latest database version this installer supports. private const DatabaseVersion LatestVersionSupported = DatabaseVersion.Version4_8; /// The specifications to determine the database version. private static readonly VersionSpecs[] m_VersionSpecs = new VersionSpecs[] { - new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8), - new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1), - new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4), + new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8), + new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1), + new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4), new VersionSpecs("SELECT description FROM cmsContentType",DatabaseVersion.Version3), new VersionSpecs("SELECT id FROM sysobjects",DatabaseVersion.None) }; @@ -43,8 +43,9 @@ namespace SqlCE4Umbraco public override bool CanConnect { get - { - SqlHelper.CreateEmptyDatabase(); + { + using (var sqlHelper = SqlHelper) + sqlHelper.CreateEmptyDatabase(); return base.CanConnect; } } @@ -93,22 +94,22 @@ namespace SqlCE4Umbraco #region DefaultInstaller Members - /// - /// Returns the sql to do a full install - /// - protected override string FullInstallSql - { - get { return string.Empty; } - } + /// + /// Returns the sql to do a full install + /// + protected override string FullInstallSql + { + get { return string.Empty; } + } - /// - /// Returns the sql to do an upgrade - /// - protected override string UpgradeSql - { - get { return string.Empty; } - } + /// + /// Returns the sql to do an upgrade + /// + protected override string UpgradeSql + { + get { return string.Empty; } + } // We need to override this as the default way of detection a db connection checks for systables that doesn't exist // in a CE db @@ -123,8 +124,9 @@ namespace SqlCE4Umbraco // verify connection try { - if (SqlCeApplicationBlock.VerifyConnection(base.SqlHelper.ConnectionString)) - return DatabaseVersion.None; + using (var sqlHelper = SqlHelper) + if (SqlCeApplicationBlock.VerifyConnection(sqlHelper.ConnectionString)) + return DatabaseVersion.None; } catch (Exception e) { diff --git a/src/SQLCE4Umbraco/SqlCETableUtility.cs b/src/SQLCE4Umbraco/SqlCETableUtility.cs index 7c32252d20..cac6e0d784 100644 --- a/src/SQLCE4Umbraco/SqlCETableUtility.cs +++ b/src/SQLCE4Umbraco/SqlCETableUtility.cs @@ -37,13 +37,15 @@ namespace SqlCE4Umbraco ITable table = null; // get name in correct casing - name = SqlHelper.ExecuteScalar("SELECT name FROM sys.tables WHERE name=@name", - SqlHelper.CreateParameter("name", name)); + using (var sqlHelper = SqlHelper) + name = sqlHelper.ExecuteScalar("SELECT name FROM sys.tables WHERE name=@name", + sqlHelper.CreateParameter("name", name)); if (name != null) { table = new DefaultTable(name); - using (IRecordsReader reader = SqlHelper.ExecuteReader( + using (var sqlHelper = SqlHelper) + using (IRecordsReader reader = sqlHelper.ExecuteReader( @"SELECT c.name AS Name, st.name AS DataType, c.max_length, c.is_nullable, c.is_identity FROM sys.tables AS t JOIN sys.columns AS c ON t.object_id = c.object_id @@ -51,7 +53,7 @@ namespace SqlCE4Umbraco JOIN sys.types AS ty ON ty.user_type_id = c.user_type_id JOIN sys.types st ON ty.system_type_id = st.user_type_id WHERE t.name = @name - ORDER BY c.column_id", SqlHelper.CreateParameter("name", name))) + ORDER BY c.column_id", sqlHelper.CreateParameter("name", name))) { while (reader.Read()) { @@ -112,7 +114,8 @@ namespace SqlCE4Umbraco // create query StringBuilder createTableQuery = new StringBuilder(); - createTableQuery.AppendFormat("CREATE TABLE [{0}] (", SqlHelper.EscapeString(table.Name)); + using (var sqlHelper = SqlHelper) + createTableQuery.AppendFormat("CREATE TABLE [{0}] (", sqlHelper.EscapeString(table.Name)); // add fields while (hasNext) @@ -136,7 +139,8 @@ namespace SqlCE4Umbraco // execute query try { - SqlHelper.ExecuteNonQuery(createTableQuery.ToString()); + using (var sqlHelper = SqlHelper) + sqlHelper.ExecuteNonQuery(createTableQuery.ToString()); } catch (Exception executeException) { @@ -154,13 +158,15 @@ namespace SqlCE4Umbraco Debug.Assert(table != null && field != null); StringBuilder addColumnQuery = new StringBuilder(); - addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}", - SqlHelper.EscapeString(table.Name), - SqlHelper.EscapeString(field.Name), - SqlHelper.EscapeString(GetDatabaseType(field))); + using (var sqlHelper = SqlHelper) + addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}", + sqlHelper.EscapeString(table.Name), + sqlHelper.EscapeString(field.Name), + sqlHelper.EscapeString(GetDatabaseType(field))); try { - SqlHelper.ExecuteNonQuery(addColumnQuery.ToString()); + using (var sqlHelper = SqlHelper) + sqlHelper.ExecuteNonQuery(addColumnQuery.ToString()); } catch (Exception executeException) { diff --git a/src/Umbraco.Web/UI/Controls/UmbracoControl.cs b/src/Umbraco.Web/UI/Controls/UmbracoControl.cs index 4d8bd4ce69..bc633b22a3 100644 --- a/src/Umbraco.Web/UI/Controls/UmbracoControl.cs +++ b/src/Umbraco.Web/UI/Controls/UmbracoControl.cs @@ -84,9 +84,10 @@ namespace Umbraco.Web.UI.Controls get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData()))); } } - /// - /// Returns the legacy SqlHelper - /// + /// + /// Unused, please do not use + /// + [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] protected ISqlHelper SqlHelper { get { return Application.SqlHelper; } diff --git a/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs b/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs index b60c519dd4..2a18413ace 100644 --- a/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs +++ b/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs @@ -123,8 +123,9 @@ namespace Umbraco.Web.UI.Controls } /// - /// Returns the legacy SqlHelper + /// Unused, please do not use /// + [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] protected ISqlHelper SqlHelper { get { return global::umbraco.BusinessLogic.Application.SqlHelper; } diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 777fe80782..7b7a24e63d 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -152,6 +152,10 @@ namespace umbraco get { return _xmlContent == null; } } + /// + /// Unused, please do not use + /// + [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] protected static ISqlHelper SqlHelper { get { return Application.SqlHelper; } diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 5047aef271..8bd94aa5b6 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -77,9 +77,13 @@ namespace umbraco #region Properties + /// + /// Unused, please do not use + /// + [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] protected static ISqlHelper SqlHelper { - get { return umbraco.BusinessLogic.Application.SqlHelper; } + get { return Application.SqlHelper; } } #endregion @@ -1275,8 +1279,9 @@ namespace umbraco XmlDocument xd = new XmlDocument(); xd.LoadXml(""); - using (IRecordsReader dr = SqlHelper.ExecuteReader("Select id, [value] from cmsDataTypeprevalues where DataTypeNodeId = @dataTypeId order by sortorder", - SqlHelper.CreateParameter("@dataTypeId", DataTypeId))) + using (var sqlHelper = Application.SqlHelper) + using (IRecordsReader dr = sqlHelper.ExecuteReader("Select id, [value] from cmsDataTypeprevalues where DataTypeNodeId = @dataTypeId order by sortorder", + sqlHelper.CreateParameter("@dataTypeId", DataTypeId))) { while (dr.Read()) { @@ -1298,8 +1303,9 @@ namespace umbraco { try { - return SqlHelper.ExecuteScalar("select [value] from cmsDataTypePreValues where id = @id", - SqlHelper.CreateParameter("@id", Id)); + using (var sqlHelper = Application.SqlHelper) + return sqlHelper.ExecuteScalar("select [value] from cmsDataTypePreValues where id = @id", + sqlHelper.CreateParameter("@id", Id)); } catch { diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 77027749e2..6d2936cd9a 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -64,6 +64,10 @@ namespace umbraco private const string MacrosAddedKey = "macrosAdded"; public IList Exceptions = new List(); + /// + /// Unused, please do not use + /// + [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] protected static ISqlHelper SqlHelper { get { return Application.SqlHelper; } diff --git a/src/Umbraco.Web/umbraco.presentation/template.cs b/src/Umbraco.Web/umbraco.presentation/template.cs index dfdad324e2..4290ce2acf 100644 --- a/src/Umbraco.Web/umbraco.presentation/template.cs +++ b/src/Umbraco.Web/umbraco.presentation/template.cs @@ -468,12 +468,14 @@ namespace umbraco #endregion - + /// + /// Unused, please do not use + /// + [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] protected static ISqlHelper SqlHelper { get { return Application.SqlHelper; } } - #region constructors public static string GetMasterPageName(int templateID) @@ -497,11 +499,12 @@ namespace umbraco var t = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem