Scope - fix issue with EventMessages

This commit is contained in:
Stephan
2017-02-17 13:08:47 +01:00
parent c7783d1cd1
commit 23b3bb37ee
11 changed files with 139 additions and 78 deletions

View File

@@ -0,0 +1,58 @@
using System;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Events
{
/// <summary>
/// Stores the instance of EventMessages in the current scope.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Web;
namespace Umbraco.Core
{
public interface IHttpContextAccessor
{
HttpContextBase Value { get; }
}
}

View File

@@ -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
/// <inheritdoc />
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();
}
}
/// <inheritdoc />

View File

@@ -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();
}
}

View File

@@ -91,6 +91,7 @@ namespace Umbraco.Core.Services
case UmbracoObjectTypes.Member:
case UmbracoObjectTypes.DataType:
case UmbracoObjectTypes.DocumentTypeContainer:
uow.Commit();
return uow.Database.ExecuteScalar<int?>(new Sql().Select("id").From<NodeDto>().Where<NodeDto>(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<Guid?>(new Sql().Select("uniqueID").From<NodeDto>().Where<NodeDto>(dto => dto.NodeId == id));
case UmbracoObjectTypes.RecycleBin:
case UmbracoObjectTypes.Stylesheet:

View File

@@ -315,6 +315,8 @@
<Compile Include="Events\EventDefinitionFilter.cs" />
<Compile Include="Events\IDeletingMediaFilesEventArgs.cs" />
<Compile Include="Events\ScopeEventDispatcherBase.cs" />
<Compile Include="Events\ScopeLifespanMessagesFactory.cs" />
<Compile Include="IHttpContextAccessor.cs" />
<Compile Include="OrderedHashSet.cs" />
<Compile Include="Events\UninstallPackageEventArgs.cs" />
<Compile Include="Models\GridValue.cs" />

View File

@@ -1,5 +1,3 @@
using System.Web;
namespace Umbraco.Web
{
/// <summary>
@@ -8,8 +6,6 @@ namespace Umbraco.Web
/// <remarks>
/// NOTE: This has a singleton lifespan
/// </remarks>
public interface IHttpContextAccessor
{
HttpContextBase Value { get; }
}
public interface IHttpContextAccessor : Core.IHttpContextAccessor
{ }
}

View File

@@ -1,62 +0,0 @@
using System;
using System.Runtime.Remoting.Messaging;
using Umbraco.Core.Events;
namespace Umbraco.Web
{
/// <summary>
/// Stores the instance of EventMessages in the current request so all events will share the same instance
/// </summary>
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);
}
}
}

View File

@@ -374,7 +374,6 @@
<Compile Include="Routing\RedirectTrackingEventHandler.cs" />
<Compile Include="Editors\RedirectUrlManagementController.cs" />
<Compile Include="Models\ContentEditing\RedirectUrlSearchResults.cs" />
<Compile Include="RequestLifespanMessagesFactory.cs" />
<Compile Include="RouteDataExtensions.cs" />
<Compile Include="Routing\ContentFinderByRedirectUrl.cs" />
<Compile Include="Scheduling\LatchedBackgroundTaskBase.cs" />

View File

@@ -53,7 +53,7 @@ namespace Umbraco.Web
/// <returns></returns>
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();
}

View File

@@ -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),