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:
Louis DeJardin
2010-09-02 14:02:33 -07:00
parent acaf55c69f
commit 7e02c2a75f
8 changed files with 355 additions and 7 deletions

View File

@@ -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;

View 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);
}
}
}

View File

@@ -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" />

View 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; }
}
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using System.Web;
namespace Orchard.Mvc {
public interface IHttpContextAccessor {
HttpContextBase Current();
}
}

View File

@@ -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" />

View File

@@ -1,4 +1,6 @@
using Orchard.Security;
using System;
using Autofac;
using Orchard.Security;
using Orchard.Settings;
using Orchard.UI;