mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Implementing work context and accessor
Projects access of per-unit-of-work components (request lifetime scope) for singleton (per shell lifetime scope) components Preparing to rationalize and eliminate the various ah-hoc container and scoping abstractions --HG-- branch : dev
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Orchard.Tests.Utility;
|
||||
|
||||
namespace Orchard.Tests.DisplayManagement.Descriptors {
|
||||
namespace Orchard.Tests {
|
||||
public class ContainerTestBase {
|
||||
|
||||
protected IContainer _container;
|
182
src/Orchard.Tests/Environment/DefaultWorkContextAccessorTests.cs
Normal file
182
src/Orchard.Tests/Environment/DefaultWorkContextAccessorTests.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Autofac;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Mvc;
|
||||
using Orchard.Tests.Stubs;
|
||||
using Orchard.Tests.Utility;
|
||||
|
||||
namespace Orchard.Tests.Environment {
|
||||
[TestFixture]
|
||||
public class DefaultWorkContextAccessorTests : ContainerTestBase {
|
||||
|
||||
HttpContextBase _httpContextCurrent;
|
||||
|
||||
public override void Init() {
|
||||
_httpContextCurrent = null;
|
||||
base.Init();
|
||||
}
|
||||
|
||||
protected override void Register(ContainerBuilder builder) {
|
||||
builder.RegisterType<DefaultWorkContextAccessor>().As<IWorkContextAccessor>();
|
||||
builder.RegisterAutoMocking();
|
||||
}
|
||||
|
||||
protected override void Resolve(IContainer container) {
|
||||
container.Mock<IHttpContextAccessor>()
|
||||
.Setup(x => x.Current())
|
||||
.Returns(() => _httpContextCurrent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScopeIsCreatedAndCanBeRetrievedFromHttpContextBase() {
|
||||
var accessor = _container.Resolve<IWorkContextAccessor>();
|
||||
var httpContext = new StubHttpContext();
|
||||
|
||||
var workContextScope = accessor.CreateContextScope(httpContext);
|
||||
Assert.That(workContextScope.WorkContext, Is.Not.Null);
|
||||
|
||||
var workContext = accessor.GetContext(httpContext);
|
||||
Assert.That(workContext, Is.SameAs(workContextScope.WorkContext));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DifferentHttpContextWillHoldDifferentWorkContext() {
|
||||
var accessor = _container.Resolve<IWorkContextAccessor>();
|
||||
var httpContext1 = new StubHttpContext();
|
||||
var workContextScope1 = accessor.CreateContextScope(httpContext1);
|
||||
var workContext1 = accessor.GetContext(httpContext1);
|
||||
|
||||
var httpContext2 = new StubHttpContext();
|
||||
var workContextScope2 = accessor.CreateContextScope(httpContext2);
|
||||
var workContext2 = accessor.GetContext(httpContext2);
|
||||
|
||||
Assert.That(workContext1, Is.Not.Null);
|
||||
Assert.That(workContext1, Is.SameAs(workContextScope1.WorkContext));
|
||||
Assert.That(workContext2, Is.Not.Null);
|
||||
Assert.That(workContext2, Is.SameAs(workContextScope2.WorkContext));
|
||||
Assert.That(workContext1, Is.Not.SameAs(workContext2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContextIsNullAfterDisposingScope() {
|
||||
var accessor = _container.Resolve<IWorkContextAccessor>();
|
||||
var httpContext = new StubHttpContext();
|
||||
|
||||
Assert.That(accessor.GetContext(httpContext), Is.Null);
|
||||
|
||||
var scope = accessor.CreateContextScope(httpContext);
|
||||
Assert.That(accessor.GetContext(httpContext), Is.Not.Null);
|
||||
|
||||
scope.Dispose();
|
||||
Assert.That(accessor.GetContext(httpContext), Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DifferentChildScopesWillNotCollideInTheSameHttpContext() {
|
||||
var shell1 = _container.BeginLifetimeScope();
|
||||
var accessor1 = shell1.Resolve<IWorkContextAccessor>();
|
||||
|
||||
var shell2 = _container.BeginLifetimeScope();
|
||||
var accessor2 = shell2.Resolve<IWorkContextAccessor>();
|
||||
|
||||
var httpContext = new StubHttpContext();
|
||||
|
||||
Assert.That(accessor1.GetContext(httpContext), Is.Null);
|
||||
Assert.That(accessor2.GetContext(httpContext), Is.Null);
|
||||
|
||||
var scope1 = accessor1.CreateContextScope(httpContext);
|
||||
Assert.That(accessor1.GetContext(httpContext), Is.Not.Null);
|
||||
Assert.That(accessor2.GetContext(httpContext), Is.Null);
|
||||
|
||||
var scope2 = accessor2.CreateContextScope(httpContext);
|
||||
Assert.That(accessor1.GetContext(httpContext), Is.Not.Null);
|
||||
Assert.That(accessor2.GetContext(httpContext), Is.Not.Null);
|
||||
|
||||
scope1.Dispose();
|
||||
Assert.That(accessor1.GetContext(httpContext), Is.Null);
|
||||
Assert.That(accessor2.GetContext(httpContext), Is.Not.Null);
|
||||
|
||||
scope2.Dispose();
|
||||
Assert.That(accessor1.GetContext(httpContext), Is.Null);
|
||||
Assert.That(accessor2.GetContext(httpContext), Is.Null);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void FunctionsByDefaultAgainstAmbientHttpContext() {
|
||||
var accessor = _container.Resolve<IWorkContextAccessor>();
|
||||
|
||||
var explicitHttpContext = new StubHttpContext();
|
||||
var ambientHttpContext = new StubHttpContext();
|
||||
|
||||
_httpContextCurrent = ambientHttpContext;
|
||||
|
||||
Assert.That(accessor.GetContext(), Is.Null);
|
||||
Assert.That(accessor.GetContext(ambientHttpContext), Is.Null);
|
||||
Assert.That(accessor.GetContext(explicitHttpContext), Is.Null);
|
||||
|
||||
var scope = accessor.CreateContextScope();
|
||||
Assert.That(accessor.GetContext(), Is.Not.Null);
|
||||
Assert.That(accessor.GetContext(ambientHttpContext), Is.Not.Null);
|
||||
Assert.That(accessor.GetContext(explicitHttpContext), Is.Null);
|
||||
Assert.That(accessor.GetContext(), Is.SameAs(accessor.GetContext(ambientHttpContext)));
|
||||
|
||||
_httpContextCurrent = explicitHttpContext;
|
||||
Assert.That(accessor.GetContext(), Is.Null);
|
||||
|
||||
_httpContextCurrent = ambientHttpContext;
|
||||
Assert.That(accessor.GetContext(), Is.Not.Null);
|
||||
|
||||
scope.Dispose();
|
||||
Assert.That(accessor.GetContext(), Is.Null);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void StillFunctionsWithoutAmbientHttpContext() {
|
||||
var accessor = _container.Resolve<IWorkContextAccessor>();
|
||||
|
||||
Assert.That(accessor.GetContext(), Is.Null);
|
||||
|
||||
var scope = accessor.CreateContextScope();
|
||||
Assert.That(accessor.GetContext(), Is.Not.Null);
|
||||
|
||||
scope.Dispose();
|
||||
Assert.That(accessor.GetContext(), Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DifferentChildScopesWillNotCollideWithoutAmbientHttpContext() {
|
||||
var shell1 = _container.BeginLifetimeScope();
|
||||
var accessor1 = shell1.Resolve<IWorkContextAccessor>();
|
||||
|
||||
var shell2 = _container.BeginLifetimeScope();
|
||||
var accessor2 = shell2.Resolve<IWorkContextAccessor>();
|
||||
|
||||
Assert.That(accessor1.GetContext(), Is.Null);
|
||||
Assert.That(accessor2.GetContext(), Is.Null);
|
||||
|
||||
var scope1 = accessor1.CreateContextScope();
|
||||
Assert.That(accessor1.GetContext(), Is.Not.Null);
|
||||
Assert.That(accessor2.GetContext(), Is.Null);
|
||||
|
||||
var scope2 = accessor2.CreateContextScope();
|
||||
Assert.That(accessor1.GetContext(), Is.Not.Null);
|
||||
Assert.That(accessor2.GetContext(), Is.Not.Null);
|
||||
|
||||
scope1.Dispose();
|
||||
Assert.That(accessor1.GetContext(), Is.Null);
|
||||
Assert.That(accessor2.GetContext(), Is.Not.Null);
|
||||
|
||||
scope2.Dispose();
|
||||
Assert.That(accessor1.GetContext(), Is.Null);
|
||||
Assert.That(accessor2.GetContext(), Is.Null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -194,7 +194,7 @@
|
||||
<Compile Include="Data\StubLocator.cs" />
|
||||
<Compile Include="DisplayManagement\ArgsUtility.cs" />
|
||||
<Compile Include="DisplayManagement\DefaultDisplayManagerTests.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\ContainerTestBase.cs" />
|
||||
<Compile Include="ContainerTestBase.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\DefaultShapeTableFactoryTests.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\DefaultShapeTableManagerTests.cs" />
|
||||
<Compile Include="DisplayManagement\Descriptors\ShapeAttributeBindingStrategyTests.cs" />
|
||||
@@ -204,6 +204,7 @@
|
||||
<Compile Include="DisplayManagement\ShapeHelperTests.cs" />
|
||||
<Compile Include="Environment\AutofacUtil\AutofacTests.cs" />
|
||||
<Compile Include="Environment\AutofacUtil\DynamicProxy2\DynamicProxyTests.cs" />
|
||||
<Compile Include="Environment\DefaultWorkContextAccessorTests.cs" />
|
||||
<Compile Include="Environment\Extensions\ExtensionLoaderCoordinatorTests.cs" />
|
||||
<Compile Include="Environment\State\DefaultProcessingEngineTests.cs" />
|
||||
<Compile Include="Environment\RunningShellTableTests.cs" />
|
||||
|
146
src/Orchard/Environment/DefaultWorkContextAccessor.cs
Normal file
146
src/Orchard/Environment/DefaultWorkContextAccessor.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using Autofac;
|
||||
using Orchard.Mvc;
|
||||
|
||||
namespace Orchard.Environment {
|
||||
public class DefaultWorkContextAccessor : IWorkContextAccessor {
|
||||
readonly ILifetimeScope _lifetimeScope;
|
||||
|
||||
readonly IHttpContextAccessor _httpContextAccessor;
|
||||
// a different symbolic key is used for each tenant.
|
||||
// this guarantees the correct accessor is being resolved.
|
||||
readonly object _workContextKey = new object();
|
||||
|
||||
[ThreadStatic]
|
||||
static readonly ConcurrentDictionary<object, WorkContext> _threadStaticContexts = new ConcurrentDictionary<object, WorkContext>();
|
||||
|
||||
public DefaultWorkContextAccessor(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILifetimeScope lifetimeScope) {
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_lifetimeScope = lifetimeScope;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public WorkContext GetContext(HttpContextBase httpContext) {
|
||||
return httpContext.Items[_workContextKey] as WorkContext;
|
||||
}
|
||||
|
||||
public WorkContext GetContext() {
|
||||
var httpContext = _httpContextAccessor.Current();
|
||||
if (httpContext != null)
|
||||
return GetContext(httpContext);
|
||||
|
||||
WorkContext workContext;
|
||||
return _threadStaticContexts.TryGetValue(_workContextKey, out workContext) ? workContext : null;
|
||||
}
|
||||
|
||||
public IWorkContextScope CreateContextScope(HttpContextBase httpContext) {
|
||||
|
||||
var workLifetime = SpawnWorkLifetime(builder => {
|
||||
builder.Register(ctx => httpContext)
|
||||
.As<HttpContextBase>();
|
||||
|
||||
builder.Register(ctx => new WorkContextImplementation(ctx))
|
||||
.As<WorkContext>()
|
||||
.InstancePerMatchingLifetimeScope("work");
|
||||
});
|
||||
return new HttpContextScopeImplementation(
|
||||
workLifetime,
|
||||
httpContext,
|
||||
_workContextKey);
|
||||
}
|
||||
|
||||
|
||||
public IWorkContextScope CreateContextScope() {
|
||||
var httpContext = _httpContextAccessor.Current();
|
||||
if (httpContext != null)
|
||||
return CreateContextScope(httpContext);
|
||||
|
||||
var workLifetime = SpawnWorkLifetime(builder => {
|
||||
builder.Register(ctx => httpContext)
|
||||
.As<HttpContextBase>();
|
||||
|
||||
builder.Register(ctx => new WorkContextImplementation(ctx))
|
||||
.As<WorkContext>()
|
||||
.InstancePerMatchingLifetimeScope("work");
|
||||
});
|
||||
return new ThreadStaticScopeImplementation(
|
||||
workLifetime,
|
||||
_threadStaticContexts,
|
||||
_workContextKey);
|
||||
}
|
||||
|
||||
private ILifetimeScope SpawnWorkLifetime(Action<ContainerBuilder> configurationAction) {
|
||||
return _lifetimeScope.BeginLifetimeScope("work", configurationAction);
|
||||
}
|
||||
|
||||
|
||||
class WorkContextImplementation : WorkContext {
|
||||
private readonly IComponentContext _componentContext;
|
||||
|
||||
public WorkContextImplementation(IComponentContext componentContext) {
|
||||
_componentContext = componentContext;
|
||||
}
|
||||
|
||||
public override T Service<T>() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override T State<T>() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
class HttpContextScopeImplementation : IWorkContextScope {
|
||||
readonly WorkContext _workContext;
|
||||
readonly Action _disposer;
|
||||
|
||||
public HttpContextScopeImplementation(ILifetimeScope lifetimeScope, HttpContextBase httpContext, object workContextKey) {
|
||||
_workContext = lifetimeScope.Resolve<WorkContext>();
|
||||
httpContext.Items[workContextKey] = _workContext;
|
||||
_disposer = () => {
|
||||
httpContext.Items.Remove(workContextKey);
|
||||
lifetimeScope.Dispose();
|
||||
};
|
||||
}
|
||||
|
||||
void IDisposable.Dispose() {
|
||||
_disposer();
|
||||
}
|
||||
|
||||
public WorkContext WorkContext {
|
||||
get { return _workContext; }
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadStaticScopeImplementation : IWorkContextScope {
|
||||
readonly WorkContext _workContext;
|
||||
readonly Action _disposer;
|
||||
|
||||
public ThreadStaticScopeImplementation(ILifetimeScope lifetimeScope, ConcurrentDictionary<object, WorkContext> contexts, object workContextKey) {
|
||||
_workContext = lifetimeScope.Resolve<WorkContext>();
|
||||
contexts.AddOrUpdate(workContextKey, _workContext, (a, b) => _workContext);
|
||||
_disposer = () => {
|
||||
WorkContext removedContext;
|
||||
contexts.TryRemove(workContextKey, out removedContext);
|
||||
lifetimeScope.Dispose();
|
||||
};
|
||||
}
|
||||
|
||||
void IDisposable.Dispose() {
|
||||
_disposer();
|
||||
}
|
||||
|
||||
public WorkContext WorkContext {
|
||||
get { return _workContext; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,8 +1,16 @@
|
||||
using System.Web;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard {
|
||||
public interface IWorkContextAccessor : IDependency {
|
||||
WorkContext GetContext();
|
||||
public interface IWorkContextAccessor : ISingletonDependency {
|
||||
WorkContext GetContext(HttpContextBase httpContext);
|
||||
IWorkContextScope CreateContextScope(HttpContextBase httpContext);
|
||||
|
||||
WorkContext GetContext();
|
||||
IWorkContextScope CreateContextScope();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IWorkContextScope : IDisposable {
|
||||
WorkContext WorkContext { get; }
|
||||
}
|
||||
}
|
||||
|
7
src/Orchard/Mvc/IHttpContextAccessor.cs
Normal file
7
src/Orchard/Mvc/IHttpContextAccessor.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.Mvc {
|
||||
public interface IHttpContextAccessor {
|
||||
HttpContextBase Current();
|
||||
}
|
||||
}
|
@@ -402,10 +402,12 @@
|
||||
<Compile Include="DisplayManagement\Descriptors\Interfaces.cs" />
|
||||
<Compile Include="DisplayManagement\Implementation\ShapeHelper.cs" />
|
||||
<Compile Include="DisplayManagement\Implementation\ShapeHelperFactory.cs" />
|
||||
<Compile Include="Environment\DefaultWorkContextAccessor.cs" />
|
||||
<Compile Include="Environment\IContextualizable.cs" />
|
||||
<Compile Include="Environment\IHostLocalRestart.cs" />
|
||||
<Compile Include="Environment\IShellContainerRegistrations.cs" />
|
||||
<Compile Include="FileSystems\Dependencies\DynamicModuleVirtualPathProvider.cs" />
|
||||
<Compile Include="Mvc\IHttpContextAccessor.cs" />
|
||||
<Compile Include="IWorkContextAccessor.cs" />
|
||||
<Compile Include="WorkContextExtensions.cs" />
|
||||
<Compile Include="Mvc\Html\Shapes.cs" />
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using Orchard.Security;
|
||||
using System;
|
||||
using Autofac;
|
||||
using Orchard.Security;
|
||||
using Orchard.Settings;
|
||||
using Orchard.UI;
|
||||
|
||||
|
Reference in New Issue
Block a user