diff --git a/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs b/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs new file mode 100644 index 0000000000..710ee8a129 --- /dev/null +++ b/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs @@ -0,0 +1,58 @@ +using System; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Events +{ + /// + /// Stores the instance of EventMessages in the current scope. + /// + internal class ScopeLifespanMessagesFactory : IEventMessagesFactory + { + public const string ContextKey = "Umbraco.Core.Events.ScopeLifespanMessagesFactory"; + + private readonly IHttpContextAccessor _contextAccessor; + private readonly IScopeProviderInternal _scopeProvider; + + public static ScopeLifespanMessagesFactory Current { get; private set; } + + public ScopeLifespanMessagesFactory(IHttpContextAccessor contextAccesor, IScopeProvider scopeProvider) + { + if (contextAccesor == null) throw new ArgumentNullException("contextAccesor"); + if (scopeProvider == null) throw new ArgumentNullException("scopeProvider"); + if (scopeProvider is IScopeProviderInternal == false) throw new ArgumentException("Not IScopeProviderInternal.", "scopeProvider"); + _contextAccessor = contextAccesor; + _scopeProvider = (IScopeProviderInternal) scopeProvider; + Current = this; + } + + public EventMessages Get() + { + var messages = GetFromHttpContext(); + if (messages != null) return messages; + + var scope = _scopeProvider.GetAmbientOrNoScope(); + return scope.Messages; + } + + public EventMessages GetFromHttpContext() + { + if (_contextAccessor == null || _contextAccessor.Value == null) return null; + return (EventMessages)_contextAccessor.Value.Items[ContextKey]; + } + + public EventMessages TryGet() + { + var messages = GetFromHttpContext(); + if (messages != null) return messages; + + var scope = _scopeProvider.AmbientScope; + return scope == null ? null : scope.MessagesOrNull; + } + + public void Set(EventMessages messages) + { + if (_contextAccessor.Value == null) return; + _contextAccessor.Value.Items[ContextKey] = messages; + } + } +} diff --git a/src/Umbraco.Core/IHttpContextAccessor.cs b/src/Umbraco.Core/IHttpContextAccessor.cs new file mode 100644 index 0000000000..a0873c78a9 --- /dev/null +++ b/src/Umbraco.Core/IHttpContextAccessor.cs @@ -0,0 +1,9 @@ +using System.Web; + +namespace Umbraco.Core +{ + public interface IHttpContextAccessor + { + HttpContextBase Value { get; } + } +} diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs index 2d8fa245b0..a21815173c 100644 --- a/src/Umbraco.Core/Scoping/NoScope.cs +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Scoping private bool _disposed; private UmbracoDatabase _database; + private EventMessages _messages; public NoScope(ScopeProvider scopeProvider) { @@ -61,12 +62,42 @@ namespace Umbraco.Core.Scoping /// public EventMessages Messages { - get { throw new NotSupportedException(); } + get + { + EnsureNotDisposed(); + if (_messages != null) return _messages; + + // see comments in Scope + + var factory = ScopeLifespanMessagesFactory.Current; + if (factory == null) + { + _messages = new EventMessages(); + } + else + { + _messages = factory.GetFromHttpContext(); + if (_messages == null) + factory.Set(_messages = new EventMessages()); + } + + return _messages; + } } public EventMessages MessagesOrNull { - get { throw new NotSupportedException(); } + get + { + EnsureNotDisposed(); + + // see comments in Scope + + if (_messages != null) return _messages; + + var factory = ScopeLifespanMessagesFactory.Current; + return factory == null ? null : factory.GetFromHttpContext(); + } } /// diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index ab66be1bd2..53d35181c3 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -24,6 +24,7 @@ namespace Umbraco.Core.Scoping private IsolatedRuntimeCache _isolatedRuntimeCache; private UmbracoDatabase _database; + private EventMessages _messages; private ICompletable _fscope; private IEventDispatcher _eventDispatcher; @@ -127,6 +128,7 @@ namespace Umbraco.Core.Scoping { // steal everything from NoScope _database = noScope.DatabaseOrNull; + _messages = noScope.MessagesOrNull; // make sure the NoScope can be replaced ie not in a transaction if (_database != null && _database.InTransaction) @@ -271,11 +273,27 @@ namespace Umbraco.Core.Scoping { EnsureNotDisposed(); if (ParentScope != null) return ParentScope.Messages; - //return _messages ?? (_messages = new EventMessages()); - // ok, this isn't pretty, but it works - // TODO kill the message factory and let the scope manage it all - return ApplicationContext.Current.Services.EventMessagesFactory.Get(); + if (_messages != null) return _messages; + + // this is ugly - in v7 for backward compatibility reasons, EventMessages need + // to survive way longer that the scopes, and kinda resides on its own in http + // context, but must also be in scopes for when we do async and lose http context + // TODO refactor in v8 + + var factory = ScopeLifespanMessagesFactory.Current; + if (factory == null) + { + _messages = new EventMessages(); + } + else + { + _messages = factory.GetFromHttpContext(); + if (_messages == null) + factory.Set(_messages = new EventMessages()); + } + + return _messages; } } @@ -284,7 +302,14 @@ namespace Umbraco.Core.Scoping get { EnsureNotDisposed(); - return ParentScope == null ? null : ParentScope.MessagesOrNull; + if (ParentScope != null) return ParentScope.MessagesOrNull; + + // see comments in Messages + + if (_messages != null) return _messages; + + var factory = ScopeLifespanMessagesFactory.Current; + return factory == null ? null : factory.GetFromHttpContext(); } } diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 3ff28aee0a..b414d381e9 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -91,6 +91,7 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: case UmbracoObjectTypes.DocumentTypeContainer: + uow.Commit(); return uow.Database.ExecuteScalar(new Sql().Select("id").From().Where(dto => dto.UniqueId == key)); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: @@ -129,6 +130,7 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.DocumentType: case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: + uow.Commit(); return uow.Database.ExecuteScalar(new Sql().Select("uniqueID").From().Where(dto => dto.NodeId == id)); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6f02366820..8726469f53 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -315,6 +315,8 @@ + + diff --git a/src/Umbraco.Web/IHttpContextAccessor.cs b/src/Umbraco.Web/IHttpContextAccessor.cs index 068783725a..4b5a8ed884 100644 --- a/src/Umbraco.Web/IHttpContextAccessor.cs +++ b/src/Umbraco.Web/IHttpContextAccessor.cs @@ -1,5 +1,3 @@ -using System.Web; - namespace Umbraco.Web { /// @@ -8,8 +6,6 @@ namespace Umbraco.Web /// /// NOTE: This has a singleton lifespan /// - public interface IHttpContextAccessor - { - HttpContextBase Value { get; } - } + public interface IHttpContextAccessor : Core.IHttpContextAccessor + { } } \ No newline at end of file diff --git a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs deleted file mode 100644 index cf75e60755..0000000000 --- a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Runtime.Remoting.Messaging; -using Umbraco.Core.Events; - -namespace Umbraco.Web -{ - /// - /// Stores the instance of EventMessages in the current request so all events will share the same instance - /// - internal class RequestLifespanMessagesFactory : IEventMessagesFactory - { - private const string ContextKey = "Umbraco.Web.RequestLifespanMessagesFactory"; - private readonly IHttpContextAccessor _httpAccessor; - - public RequestLifespanMessagesFactory(IHttpContextAccessor httpAccessor) - { - if (httpAccessor == null) throw new ArgumentNullException("httpAccessor"); - _httpAccessor = httpAccessor; - } - - public EventMessages Get() - { - var httpContext = _httpAccessor.Value; - if (httpContext != null) - { - var eventMessages = httpContext.Items[ContextKey] as EventMessages; - if (eventMessages == null) httpContext.Items[ContextKey] = eventMessages = new EventMessages(); - return eventMessages; - } - - var lccContext = CallContext.LogicalGetData(ContextKey) as EventMessages; - if (lccContext != null) return lccContext; - - throw new Exception("Could not get messages."); - } - - public EventMessages TryGet() - { - var httpContext = _httpAccessor.Value; - return httpContext != null - ? httpContext.Items[ContextKey] as EventMessages - : CallContext.LogicalGetData(ContextKey) as EventMessages; - } - - // Deploy wants to execute things outside of a request, where this factory would fail, - // so the factory is extended so that Deploy can Set/Clear event messages in the logical - // call context (which flows with async) - it needs to be set and cleared because, contrary - // to http context, it's not being cleared at the end of anything. - // - // to be refactored in v8! the whole IEventMessagesFactory is borked anyways - - public void SetLlc() - { - CallContext.LogicalSetData(ContextKey, new EventMessages()); - } - - public void ClearLlc() - { - CallContext.FreeNamedDataSlot(ContextKey); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f3f03d2b83..7987a46b3b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -374,7 +374,6 @@ - diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs index 53ef92690e..3e5c9d355e 100644 --- a/src/Umbraco.Web/UmbracoContextExtensions.cs +++ b/src/Umbraco.Web/UmbracoContextExtensions.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web /// public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext) { - var eventMessagesFactory = umbracoContext.Application.Services.EventMessagesFactory as RequestLifespanMessagesFactory; + var eventMessagesFactory = umbracoContext.Application.Services.EventMessagesFactory as ScopeLifespanMessagesFactory; return eventMessagesFactory == null ? null : eventMessagesFactory.TryGet(); } diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index b0ac28dfe5..8b935b1ed6 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -42,6 +42,7 @@ using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; using Umbraco.Core.Cache; +using Umbraco.Core.Events; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; @@ -92,7 +93,7 @@ namespace Umbraco.Web protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider) { //use a request based messaging factory - var evtMsgs = new RequestLifespanMessagesFactory(new SingletonHttpContextAccessor()); + var evtMsgs = new ScopeLifespanMessagesFactory(new SingletonHttpContextAccessor(), scopeProvider); return new ServiceContext( new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), new PetaPocoUnitOfWorkProvider(scopeProvider),