diff --git a/src/Umbraco.Core/AmbientContext.cs b/src/Umbraco.Core/AmbientContext.cs new file mode 100644 index 0000000000..cce488e036 --- /dev/null +++ b/src/Umbraco.Core/AmbientContext.cs @@ -0,0 +1,102 @@ +//#define CALLCONTEXT +#define LOGICALCONTEXT + +using System.Collections.Generic; +using System.Runtime.Remoting.Messaging; +using System.Web; + +namespace Umbraco.Core +{ + // fixme - not used at the moment, instead we have the IScopeContextAdapter thing + // + // the scope context adapter needs to be created and passed around... whereas... this "just exists" + // and it's possible to do AmbientContext.Get(key) just anywhere and it will use the "ambient" context, + // which is based upon HttpContext in the context of a web request, or CallContext otherwise. + // + // read: + // https://github.com/seesharper/LightInject/issues/170 + // http://stackoverflow.com/questions/22924048/is-it-possible-to-detect-if-you-are-on-the-synchronous-side-of-an-async-method + // http://stackoverflow.com/questions/29917671/lightinject-with-web-api-how-can-i-get-the-httprequestmessage + // http://stackoverflow.com/questions/18459916/httpcontext-current-items-after-an-async-operation + // http://stackoverflow.com/questions/12029091/httpcontext-is-null-after-await-task-factory-fromasyncbeginxxx-endxxx + // + // also note + // CallContext stuff will flow downwards in async but not upwards ie an async call will receive the values + // but any changes it makes will *not* modify the caller's CallContext, so we should be careful when using + // this for caches and stuff + + internal static class AmbientContext + { + /// + /// Gets or sets the values container. + /// + /// For unit tests EXCLUSIVELY. + internal static IDictionary Values { get; set; } + + public static object Get(string key) + { + if (Values != null) + { + object value; + return Values.TryGetValue(key, out value) ? value : null; + } + + return HttpContext.Current == null +#if CALLCONTEXT + ? CallContext.GetData(key) +#elif LOGICALCONTEXT + ? CallContext.LogicalGetData(key) +#endif + : HttpContext.Current.Items[key]; + } + + public static void Set(string key, object value) + { + if (Values != null) + { + if (value != null) + Values[key] = value; + else + Values.Remove(key); + return; + } + + if (HttpContext.Current == null) + { + if (value != null) +#if CALLCONTEXT + CallContext.SetData(key, value); +#elif LOGICALCONTEXT + CallContext.LogicalSetData(key, value); +#endif + else +#if CALLCONTEXT || LOGICALCONTEXT + CallContext.FreeNamedDataSlot(key); // clears both +#endif + } + else + { + if (value != null) + HttpContext.Current.Items[key] = value; + else + HttpContext.Current.Items.Remove(key); + } + } + + public static void Clear(string key) + { + if (Values != null) + { + Values.Remove(key); + return; + } + + if (HttpContext.Current == null) +#if CALLCONTEXT || LOGICALCONTEXT + CallContext.FreeNamedDataSlot(key); // clears both +#endif + else + HttpContext.Current.Items.Remove(key); + } + } +} diff --git a/src/Umbraco.Core/CallContextScope.cs b/src/Umbraco.Core/CallContextScope.cs deleted file mode 100644 index 1eb4589899..0000000000 --- a/src/Umbraco.Core/CallContextScope.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Runtime.Remoting.Messaging; - -namespace Umbraco.Core -{ - /// - /// A place to get/retrieve data in a current context (i.e. http, thread, etc...) - /// - internal class CallContextScope : IScopeContext - { - public object GetData(string key) - { - return CallContext.GetData(key); - } - - public void SetData(string key, object data) - { - CallContext.SetData(key, data); - } - - public void ClearData(string key) - { - CallContext.FreeNamedDataSlot(key); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/DefaultScopeContextAdapter.cs b/src/Umbraco.Core/DefaultScopeContextAdapter.cs new file mode 100644 index 0000000000..94c358b976 --- /dev/null +++ b/src/Umbraco.Core/DefaultScopeContextAdapter.cs @@ -0,0 +1,43 @@ +using System.Runtime.Remoting.Messaging; +using System.Web; + +namespace Umbraco.Core +{ + internal class DefaultScopeContextAdapter : IScopeContextAdapter + { + // fixme - should we use the LogicalCallContext here? + + public object Get(string key) + { + return HttpContext.Current == null + ? CallContext.GetData(key) + : HttpContext.Current.Items[key]; + } + + public void Set(string key, object value) + { + if (HttpContext.Current == null) + { + if (value != null) + CallContext.SetData(key, value); + else + CallContext.FreeNamedDataSlot(key); + } + else + { + if (value != null) + HttpContext.Current.Items[key] = value; + else + HttpContext.Current.Items.Remove(key); + } + } + + public void Clear(string key) + { + if (HttpContext.Current == null) + CallContext.FreeNamedDataSlot(key); + else + HttpContext.Current.Items.Remove(key); + } + } +} diff --git a/src/Umbraco.Core/DefaultScopeContextFactory.cs b/src/Umbraco.Core/DefaultScopeContextFactory.cs deleted file mode 100644 index 1152ee6b41..0000000000 --- a/src/Umbraco.Core/DefaultScopeContextFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Web; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core -{ - /// - /// Default scope context factory - /// - internal class DefaultScopeContextFactory : IScopeContextFactory - { - public IScopeContext GetContext() - { - return HttpContext.Current == null ? (IScopeContext)new CallContextScope() : new HttpContextScope(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs index 58d22f4fee..30647047c5 100644 --- a/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.DependencyInjection container.Register("SqlCeSyntaxProvider"); container.Register("SqlServerSyntaxProvider"); - container.RegisterSingleton(); + container.RegisterSingleton(); // register database factory // will be initialized with syntax providers and a logger, and will try to configure diff --git a/src/Umbraco.Core/HttpContextScope.cs b/src/Umbraco.Core/HttpContextScope.cs deleted file mode 100644 index 594e5be964..0000000000 --- a/src/Umbraco.Core/HttpContextScope.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Web; - -namespace Umbraco.Core -{ - /// - /// A place to get/retrieve data in a current context (i.e. http, thread, etc...) - /// - internal class HttpContextScope : IScopeContext - { - public object GetData(string key) - { - return HttpContext.Current.Items[key]; - } - - public void SetData(string key, object data) - { - HttpContext.Current.Items[key] = data; - } - - public void ClearData(string key) - { - HttpContext.Current.Items.Remove(key); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/IScopeContext.cs b/src/Umbraco.Core/IScopeContext.cs deleted file mode 100644 index 8a1a916acc..0000000000 --- a/src/Umbraco.Core/IScopeContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Umbraco.Core -{ - /// - /// A place to get/retrieve data in a current context (i.e. http, thread, etc...) - /// - internal interface IScopeContext - { - object GetData(string key); - void SetData(string key, object data); - void ClearData(string key); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/IScopeContextAdapter.cs b/src/Umbraco.Core/IScopeContextAdapter.cs new file mode 100644 index 0000000000..c77c9cd53c --- /dev/null +++ b/src/Umbraco.Core/IScopeContextAdapter.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core +{ + internal interface IScopeContextAdapter + { + // ok to get a non-existing key, returns null + + object Get(string key); + void Set(string key, object value); + void Clear(string key); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/IScopeContextFactory.cs b/src/Umbraco.Core/IScopeContextFactory.cs deleted file mode 100644 index e402f23608..0000000000 --- a/src/Umbraco.Core/IScopeContextFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Core -{ - /// - /// Gets an IScopedContext - /// - internal interface IScopeContextFactory - { - IScopeContext GetContext(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index fef60db1d7..da86197f27 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence /// internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory { - private readonly IScopeContextFactory _scopeContextFactory; + private readonly IScopeContextAdapter _scopeContextAdapter; private readonly ISqlSyntaxProvider[] _sqlSyntaxProviders; private readonly ILogger _logger; @@ -57,11 +57,11 @@ namespace Umbraco.Core.Persistence /// /// The collection of available sql syntax providers. /// A logger. - /// + /// /// Used by LightInject. - public DefaultDatabaseFactory(IEnumerable sqlSyntaxProviders, ILogger logger, IScopeContextFactory scopeContextFactory) - : this(GlobalSettings.UmbracoConnectionName, sqlSyntaxProviders, logger, scopeContextFactory) - { + public DefaultDatabaseFactory(IEnumerable sqlSyntaxProviders, ILogger logger, IScopeContextAdapter scopeContextAdapter) + : this(GlobalSettings.UmbracoConnectionName, sqlSyntaxProviders, logger, scopeContextAdapter) + { if (Configured == false) DatabaseContext.GiveLegacyAChance(this, logger); } @@ -72,18 +72,18 @@ namespace Umbraco.Core.Persistence /// The name of the connection string in web.config. /// The collection of available sql syntax providers. /// A logger - /// + /// /// Used by the other ctor and in tests. - public DefaultDatabaseFactory(string connectionStringName, IEnumerable sqlSyntaxProviders, ILogger logger, IScopeContextFactory scopeContextFactory) + public DefaultDatabaseFactory(string connectionStringName, IEnumerable sqlSyntaxProviders, ILogger logger, IScopeContextAdapter scopeContextAdapter) { if (sqlSyntaxProviders == null) throw new ArgumentNullException(nameof(sqlSyntaxProviders)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (scopeContextFactory == null) throw new ArgumentNullException(nameof(scopeContextFactory)); + if (scopeContextAdapter == null) throw new ArgumentNullException(nameof(scopeContextAdapter)); if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentException("Value cannot be null nor empty.", nameof(connectionStringName)); _sqlSyntaxProviders = sqlSyntaxProviders.ToArray(); _logger = logger; - _scopeContextFactory = scopeContextFactory; + _scopeContextAdapter = scopeContextAdapter; _logger.Debug("Created!"); @@ -101,17 +101,17 @@ namespace Umbraco.Core.Persistence /// The name of the database provider. /// The collection of available sql syntax providers. /// A logger. - /// + /// /// Used in tests. - public DefaultDatabaseFactory(string connectionString, string providerName, IEnumerable sqlSyntaxProviders, ILogger logger, IScopeContextFactory scopeContextFactory) + public DefaultDatabaseFactory(string connectionString, string providerName, IEnumerable sqlSyntaxProviders, ILogger logger, IScopeContextAdapter scopeContextAdapter) { if (sqlSyntaxProviders == null) throw new ArgumentNullException(nameof(sqlSyntaxProviders)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (scopeContextFactory == null) throw new ArgumentNullException(nameof(scopeContextFactory)); + if (scopeContextAdapter == null) throw new ArgumentNullException(nameof(scopeContextAdapter)); _sqlSyntaxProviders = sqlSyntaxProviders.ToArray(); _logger = logger; - _scopeContextFactory = scopeContextFactory; + _scopeContextAdapter = scopeContextAdapter; _logger.Debug("Created!"); @@ -162,7 +162,7 @@ namespace Umbraco.Core.Persistence .WithFluentConfig(config)); // with proper configuration if (_databaseFactory == null) throw new NullReferenceException("The call to DatabaseFactory.Config yielded a null DatabaseFactory instance"); - + _logger.Debug("Created _nonHttpInstance"); Configured = true; } @@ -213,16 +213,14 @@ namespace Umbraco.Core.Persistence { EnsureConfigured(); - var scope = _scopeContextFactory.GetContext(); - // check if it's in scope - var db = scope.GetData(HttpItemKey) as UmbracoDatabase; + var db = _scopeContextAdapter.Get(HttpItemKey) as UmbracoDatabase; if (db != null) return db; db = (UmbracoDatabase) _databaseFactory.GetDatabase(); - scope.SetData(HttpItemKey, db); + _scopeContextAdapter.Set(HttpItemKey, db); return db; } - + protected override void DisposeResources() { // this is weird, because _nonHttpInstance is thread-static, so we would need @@ -230,10 +228,9 @@ namespace Umbraco.Core.Persistence // it only disposes the current thread's database instance. // // besides, we don't really want to dispose the factory, which is a singleton... - - var scope = _scopeContextFactory.GetContext(); - var db = scope.GetData(HttpItemKey) as UmbracoDatabase; - scope.ClearData(HttpItemKey); + + var db = _scopeContextAdapter.Get(HttpItemKey) as UmbracoDatabase; + _scopeContextAdapter.Clear(HttpItemKey); db?.Dispose(); Configured = false; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index aeacb4c613..834485deb1 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -143,6 +143,7 @@ + @@ -290,6 +291,7 @@ + @@ -341,6 +343,7 @@ + @@ -377,15 +380,10 @@ - - Component - - - diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index b1545f0c40..08b8d5aa24 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -31,7 +31,7 @@ namespace Umbraco.Tests.Persistence _sqlCeSyntaxProvider = new SqlCeSyntaxProvider(); _sqlSyntaxProviders = new[] { (ISqlSyntaxProvider) _sqlCeSyntaxProvider }; _logger = Mock.Of(); - var dbFactory = new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, _sqlSyntaxProviders, _logger, new TestScopeContextFactory()); + var dbFactory = new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, _sqlSyntaxProviders, _logger, new TestScopeContextAdapter()); _dbContext = new DatabaseContext(dbFactory, _logger); } @@ -79,7 +79,7 @@ namespace Umbraco.Tests.Persistence engine.CreateDatabase(); // re-create the database factory and database context with proper connection string - var dbFactory = new DefaultDatabaseFactory(engine.LocalConnectionString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, new TestScopeContextFactory()); + var dbFactory = new DefaultDatabaseFactory(engine.LocalConnectionString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, new TestScopeContextAdapter()); _dbContext = new DatabaseContext(dbFactory, _logger); // create application context diff --git a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs index 5df66b1f54..f08e9ac85a 100644 --- a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs +++ b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco"; const string providerName = Constants.DbProviderNames.SqlServer; var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy(() => null)) }; - var factory = new DefaultDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), new TestScopeContextFactory()); + var factory = new DefaultDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), new TestScopeContextAdapter()); var database = factory.GetDatabase(); //Act @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco"; const string providerName = Constants.DbProviderNames.SqlServer; var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy(() => null)) }; - var factory = new DefaultDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), new TestScopeContextFactory()); + var factory = new DefaultDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), new TestScopeContextAdapter()); var database = factory.GetDatabase(); //Act diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 9e2b21e3b6..b4f5126988 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1337,7 +1337,7 @@ namespace Umbraco.Tests.Services Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName, TestObjects.GetDefaultSqlSyntaxProviders(Logger), Logger, - new TestScopeContextFactory()); + new TestScopeContextAdapter()); var repositoryFactory = MockRepositoryFactory(); var provider = new NPocoUnitOfWorkProvider(databaseFactory, repositoryFactory); var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 02a0888458..72008524a2 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -107,7 +107,7 @@ namespace Umbraco.Tests.TestHelpers // use a mock factory; otherwise use a real factory. var databaseFactory = DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture ? TestObjects.GetIDatabaseFactoryMock() - : new DefaultDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, Logger, new TestScopeContextFactory()); + : new DefaultDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, Logger, new TestScopeContextAdapter()); // so, using the above code to create a mock IDatabaseFactory if we don't have a real database // but, that will NOT prevent _appContext from NOT being configured, because it cannot connect diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index e1966b7a5c..b88f5d4612 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -221,7 +221,7 @@ namespace Umbraco.Tests.TestHelpers new DatabaseContext(new DefaultDatabaseFactory( Core.Configuration.GlobalSettings.UmbracoConnectionName, TestObjects.GetDefaultSqlSyntaxProviders(Logger), - Logger, new TestScopeContextFactory()), Logger), + Logger, new TestScopeContextAdapter()), Logger), //assign the service context TestObjects.GetServiceContext( Container.GetInstance(), diff --git a/src/Umbraco.Tests/TestHelpers/TestScopeContext.cs b/src/Umbraco.Tests/TestHelpers/TestScopeContext.cs deleted file mode 100644 index cadb3ff84f..0000000000 --- a/src/Umbraco.Tests/TestHelpers/TestScopeContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; - -namespace Umbraco.Tests.TestHelpers -{ - internal class TestScopeContext : IScopeContext - { - private readonly Dictionary _d = new Dictionary(); - - public object GetData(string key) - { - if (_d.ContainsKey(key)) return _d[key]; - return null; - } - - public void SetData(string key, object data) - { - _d[key] = data; - } - - public void ClearData(string key) - { - if (_d.ContainsKey(key)) - _d.Remove(key); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/TestScopeContextAdapter.cs b/src/Umbraco.Tests/TestHelpers/TestScopeContextAdapter.cs new file mode 100644 index 0000000000..8f81692ea6 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/TestScopeContextAdapter.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Umbraco.Core; + +namespace Umbraco.Tests.TestHelpers +{ + internal class TestScopeContextAdapter : IScopeContextAdapter + { + private readonly Dictionary _values = new Dictionary(); + + public object Get(string key) + { + object value; + return _values.TryGetValue(key, out value) ? value : null; + } + + public void Set(string key, object value) + { + _values[key] = value; + } + + public void Clear(string key) + { + _values.Remove(key); + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/TestScopeContextFactory.cs b/src/Umbraco.Tests/TestHelpers/TestScopeContextFactory.cs deleted file mode 100644 index ba16271526..0000000000 --- a/src/Umbraco.Tests/TestHelpers/TestScopeContextFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Umbraco.Core; - -namespace Umbraco.Tests.TestHelpers -{ - internal class TestScopeContextFactory : IScopeContextFactory - { - private readonly bool _transient = false; - private TestScopeContext _ctx; - - public TestScopeContextFactory(bool transient = false) - { - _transient = transient; - } - - public IScopeContext GetContext() - { - return _transient ? new TestScopeContext() : (_ctx ?? (_ctx = new TestScopeContext())); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 8292be117f..1b3e7ac0f4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -187,8 +187,7 @@ - - +