Scope - enable http vs call context switching
This commit is contained in:
@@ -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; }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user