Scope - enable http vs call context switching

This commit is contained in:
Stephan
2017-02-11 09:54:54 +01:00
parent 4f9bbdf7f7
commit 74e8390bda
6 changed files with 141 additions and 66 deletions

View File

@@ -7,6 +7,7 @@ namespace Umbraco.Core.Scoping
internal interface IScopeInternal : IScope
{
IScopeInternal ParentScope { get; }
bool CallContext { get; }
EventsDispatchMode DispatchMode { get; }
IsolationLevel IsolationLevel { get; }
UmbracoDatabase DatabaseOrNull { get; }

View File

@@ -24,7 +24,8 @@ namespace Umbraco.Core.Scoping
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null);
bool? scopeFileSystems = null,
bool callContext = false);
/// <summary>
/// Creates a detached scope.
@@ -44,10 +45,11 @@ namespace Umbraco.Core.Scoping
/// Attaches a scope.
/// </summary>
/// <param name="scope">The scope to attach.</param>
/// <param name="callContext">A value indicating whether to force usage of call context.</param>
/// <remarks>
/// <para>Only a scope created by <see cref="CreateDetachedScope"/> can be attached.</para>
/// </remarks>
void AttachScope(IScope scope);
void AttachScope(IScope scope, bool callContext = false);
/// <summary>
/// Detaches a scope.

View File

@@ -29,6 +29,9 @@ namespace Umbraco.Core.Scoping
public Guid InstanceId { get { return _instanceId; } }
#endif
/// <inheritdoc />
public bool CallContext { get { return false; } }
/// <inheritdoc />
public RepositoryCacheMode RepositoryCacheMode
{
@@ -100,8 +103,7 @@ namespace Umbraco.Core.Scoping
if (_database != null)
_database.Dispose();
_scopeProvider.AmbientScope = null;
_scopeProvider.AmbientContext = null;
_scopeProvider.SetAmbient(null);
_disposed = true;
GC.SuppressFinalize(this);

View File

@@ -19,6 +19,7 @@ namespace Umbraco.Core.Scoping
private readonly EventsDispatchMode _dispatchMode;
private readonly bool? _scopeFileSystem;
private readonly ScopeContext _scopeContext;
private bool _callContext;
private bool _disposed;
private bool? _completed;
@@ -36,7 +37,8 @@ namespace Umbraco.Core.Scoping
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
bool? scopeFileSystems = null,
bool callContext = false)
{
_scopeProvider = scopeProvider;
_scopeContext = scopeContext;
@@ -44,6 +46,7 @@ namespace Umbraco.Core.Scoping
_repositoryCacheMode = repositoryCacheMode;
_dispatchMode = dispatchMode;
_scopeFileSystem = scopeFileSystems;
_callContext = callContext;
Detachable = detachable;
#if DEBUG_SCOPES
@@ -98,20 +101,20 @@ namespace Umbraco.Core.Scoping
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
: this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
{
}
bool? scopeFileSystems = null,
bool callContext = false)
: this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext)
{ }
// initializes a new scope in a nested scopes chain, with its parent
public Scope(ScopeProvider scopeProvider, Scope parent,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
: this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
{
}
bool? scopeFileSystems = null,
bool callContext = false)
: this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext)
{ }
// initializes a new scope, replacing a NoScope instance
public Scope(ScopeProvider scopeProvider, NoScope noScope,
@@ -119,8 +122,9 @@ namespace Umbraco.Core.Scoping
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
: this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
bool? scopeFileSystems = null,
bool callContext = false)
: this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext)
{
// steal everything from NoScope
_database = noScope.DatabaseOrNull;
@@ -135,6 +139,18 @@ namespace Umbraco.Core.Scoping
public Guid InstanceId { get { return _instanceId; } }
#endif
// a value indicating whether to force call-context
public bool CallContext
{
get
{
if (_callContext) return true;
if (ParentScope != null) return ParentScope.CallContext;
return false;
}
set { _callContext = value; }
}
public bool ScopedFileSystems
{
get
@@ -331,7 +347,7 @@ namespace Umbraco.Core.Scoping
#endif
var parent = ParentScope;
_scopeProvider.AmbientScope = parent;
_scopeProvider.SetAmbientScope(parent);
if (parent != null)
parent.ChildCompleted(_completed);
@@ -418,7 +434,7 @@ namespace Umbraco.Core.Scoping
}
finally
{
_scopeProvider.AmbientContext = null;
_scopeProvider.SetAmbient(null);
}
}
}, () =>
@@ -426,8 +442,7 @@ namespace Umbraco.Core.Scoping
if (Detachable)
{
// get out of the way, restore original
_scopeProvider.AmbientScope = OrigScope;
_scopeProvider.AmbientContext = OrigContext;
_scopeProvider.SetAmbient(OrigScope, OrigContext);
}
});
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Web;
using Umbraco.Core.Events;
@@ -24,17 +22,17 @@ namespace Umbraco.Core.Scoping
SafeCallContext.Register(
() =>
{
var scope = StaticAmbientScope;
var context = StaticAmbientContext;
StaticAmbientScope = null;
StaticAmbientContext = null;
var scope = AmbientContextScope;
var context = AmbientContextContext;
AmbientContextScope = null;
AmbientContextContext = null;
return Tuple.Create(scope, context);
},
o =>
{
// cannot re-attached over leaked scope/context
// except of course over NoScope (which leaks)
var ambientScope = StaticAmbientScope;
var ambientScope = AmbientContextScope;
if (ambientScope != null)
{
var ambientNoScope = ambientScope as NoScope;
@@ -44,11 +42,11 @@ namespace Umbraco.Core.Scoping
// this should rollback any pending transaction
ambientNoScope.Dispose();
}
if (StaticAmbientContext != null) throw new Exception("Found leaked context when restoring call context.");
if (AmbientContextContext != null) throw new Exception("Found leaked context when restoring call context.");
var t = (Tuple<IScopeInternal, ScopeContext>)o;
StaticAmbientScope = t.Item1;
StaticAmbientContext = t.Item2;
AmbientContextScope = t.Item1;
AmbientContextContext = t.Item2;
});
}
@@ -58,7 +56,7 @@ namespace Umbraco.Core.Scoping
internal const string ContextItemKey = "Umbraco.Core.Scoping.ScopeContext";
private static ScopeContext CallContextContextValue
private static ScopeContext CallContextContext
{
get { return (ScopeContext)CallContext.LogicalGetData(ContextItemKey); }
set
@@ -68,7 +66,7 @@ namespace Umbraco.Core.Scoping
}
}
private static ScopeContext HttpContextContextValue
private static ScopeContext HttpContextContext
{
get { return (ScopeContext)HttpContext.Current.Items[ContextItemKey]; }
set
@@ -80,23 +78,27 @@ namespace Umbraco.Core.Scoping
}
}
private static ScopeContext StaticAmbientContext
private static ScopeContext AmbientContextContext
{
get { return HttpContext.Current == null ? CallContextContextValue : HttpContextContextValue; }
get
{
// try http context, fallback onto call context
var value = HttpContext.Current == null ? null : HttpContextContext;
return value ?? CallContextContext;
}
set
{
if (HttpContext.Current == null)
CallContextContextValue = value;
else
HttpContextContextValue = value;
// clear both
if (HttpContext.Current != null)
HttpContextContext = value;
CallContextContext = value;
}
}
/// <inheritdoc />
public ScopeContext AmbientContext
{
get { return StaticAmbientContext; }
set { StaticAmbientContext = value; }
get { return AmbientContextContext; }
}
#endregion
@@ -109,7 +111,7 @@ namespace Umbraco.Core.Scoping
// only 1 instance which can be disposed and disposed again
private static readonly ScopeReference StaticScopeReference = new ScopeReference(new ScopeProvider(null));
private static IScopeInternal CallContextValue
private static IScopeInternal CallContextScope
{
get { return (IScopeInternal) CallContext.LogicalGetData(ScopeItemKey); }
set
@@ -125,7 +127,7 @@ namespace Umbraco.Core.Scoping
}
}
private static IScopeInternal HttpContextValue
private static IScopeInternal HttpContextScope
{
get { return (IScopeInternal) HttpContext.Current.Items[ScopeItemKey]; }
set
@@ -150,33 +152,79 @@ namespace Umbraco.Core.Scoping
}
}
private static IScopeInternal StaticAmbientScope
private static IScopeInternal AmbientContextScope
{
get { return HttpContext.Current == null ? CallContextValue : HttpContextValue; }
get
{
// try http context, fallback onto call context
var value = HttpContext.Current == null ? null : HttpContextScope;
return value ?? CallContextScope;
}
set
{
if (HttpContext.Current == null)
CallContextValue = value;
else
HttpContextValue = value;
// clear both
if (HttpContext.Current != null)
HttpContextScope = value;
CallContextScope = value;
}
}
/// <inheritdoc />
public IScopeInternal AmbientScope
{
get { return StaticAmbientScope; }
set { StaticAmbientScope = value; }
get { return AmbientContextScope; }
}
public void SetAmbientScope(IScopeInternal value)
{
if (value != null && value.CallContext)
{
if (HttpContext.Current != null)
HttpContextScope = null; // clear http context
CallContextScope = value; // set call context
}
else
{
CallContextScope = null; // clear call context
AmbientContextScope = value; // set appropriate context (maybe null)
}
}
/// <inheritdoc />
public IScopeInternal GetAmbientOrNoScope()
{
return AmbientScope ?? (AmbientScope = new NoScope(this));
return AmbientScope ?? (AmbientContextScope = new NoScope(this));
}
#endregion
public void SetAmbient(IScopeInternal scope, ScopeContext context = null)
{
if (scope != null && scope.CallContext)
{
// clear http context
if (HttpContext.Current != null)
{
HttpContextScope = null;
HttpContextContext = null;
}
// set call context
CallContextScope = scope;
CallContextContext = context;
}
else
{
// clear call context
CallContextScope = null;
CallContextContext = null;
// set appropriate context (maybe null)
AmbientContextScope = scope;
AmbientContextContext = context;
}
}
/// <inheritdoc />
public IScope CreateDetachedScope(
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
@@ -188,7 +236,7 @@ namespace Umbraco.Core.Scoping
}
/// <inheritdoc />
public void AttachScope(IScope other)
public void AttachScope(IScope other, bool callContext = false)
{
var otherScope = other as Scope;
if (otherScope == null)
@@ -199,8 +247,9 @@ namespace Umbraco.Core.Scoping
otherScope.OrigScope = AmbientScope;
otherScope.OrigContext = AmbientContext;
AmbientScope = otherScope;
AmbientContext = otherScope.Context;
otherScope.CallContext = callContext;
SetAmbient(otherScope, otherScope.Context);
}
/// <inheritdoc />
@@ -221,8 +270,7 @@ namespace Umbraco.Core.Scoping
if (scope.Detachable == false)
throw new InvalidOperationException("Ambient scope is not detachable.");
AmbientScope = scope.OrigScope;
AmbientContext = scope.OrigContext;
SetAmbient(scope.OrigScope, scope.OrigContext);
scope.OrigScope = null;
scope.OrigContext = null;
return scope;
@@ -233,15 +281,18 @@ namespace Umbraco.Core.Scoping
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
bool? scopeFileSystems = null,
bool callContext = false)
{
var ambient = AmbientScope;
if (ambient == null)
{
var context = AmbientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, false, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw!
return AmbientScope = scope;
var ambientContext = AmbientContext;
var newContext = ambientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, false, newContext, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext);
// assign only if scope creation did not throw!
SetAmbient(scope, newContext ?? ambientContext);
return scope;
}
// replace noScope with a real one
@@ -255,16 +306,20 @@ namespace Umbraco.Core.Scoping
var database = noScope.DatabaseOrNull;
if (database != null && database.InTransaction)
throw new Exception("NoScope is in a transaction.");
var context = AmbientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, noScope, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw!
return AmbientScope = scope;
var ambientContext = AmbientContext;
var newContext = ambientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, noScope, newContext, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext);
// assign only if scope creation did not throw!
SetAmbient(scope, newContext ?? ambientContext);
return scope;
}
var ambientScope = ambient as Scope;
if (ambientScope == null) throw new Exception("Ambient scope is not a Scope instance.");
return AmbientScope = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
var nested = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems, callContext);
SetAmbient(nested, AmbientContext);
return nested;
}
/// <inheritdoc />

View File

@@ -76,7 +76,7 @@ namespace Umbraco.Tests.TestHelpers
var scopeProvider = new ScopeProvider(null);
if (scopeProvider.AmbientScope != null)
scopeProvider.AmbientScope.Dispose();
scopeProvider.AmbientScope = null;
scopeProvider.SetAmbientScope(null);
base.Initialize();