From e9724c8785b40197e59b78d064f4aef89698c7cf Mon Sep 17 00:00:00 2001 From: mikkelhm Date: Sun, 30 Oct 2016 20:46:18 +0100 Subject: [PATCH 01/12] 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 a70f37a53eeeb57364d31bc8468906576f498c1f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Nov 2016 16:46:06 +0100 Subject: [PATCH 02/12] 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 c4d36f06f8e7d6024989b69ec727ffc2560d9aeb Mon Sep 17 00:00:00 2001 From: Damiaan Date: Fri, 18 Nov 2016 10:47:32 +0100 Subject: [PATCH 03/12] 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 8ba6cb3abf6c5fe1ce965b35788c03ab1f60067b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Nov 2016 13:38:49 +0100 Subject: [PATCH 04/12] 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 05/12] 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 06/12] 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 07/12] 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 08/12] 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 09/12] 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 10/12] 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 11/12] 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 12/12] 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; }