Working towards re-enabling setup context

--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-04-17 19:22:26 -07:00
parent 1cfaae90b8
commit a137d84732
15 changed files with 235 additions and 73 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -15,13 +16,29 @@ namespace Orchard.Specs.Bindings {
public class WebAppHosting {
private WebHost _webHost;
private RequestDetails _details;
private MessageSink _messages;
[Given(@"I have a clean site")]
public void GivenIHaveACleanSite() {
_webHost = new WebHost();
_webHost.Initialize("Orchard.Web", "/");
var sink = new MessageSink();
_webHost.Execute(() => {
HostingTraceListener.SetHook(msg => sink.Receive(msg));
});
_messages = sink;
}
public class MessageSink : MarshalByRefObject {
readonly IList<string> _messages = new List<string>();
public void Receive(string message) {
_messages.Add(message);
}
}
[Given(@"I have module ""(.*)""")]
public void GivenIHaveModule(string moduleName) {
_webHost.CopyExtension("Modules", moduleName);

View File

@@ -0,0 +1,24 @@
using System;
using System.Diagnostics;
namespace Orchard.Specs.Hosting {
public class HostingTraceListener : TraceListener {
private static Action<string> _hook = ignored => { };
private string _message;
public static void SetHook(Action<string> hook) {
_hook = hook;
}
public override void Write(string message) {
var cumulative = _message + message;
_message = cumulative;
}
public override void WriteLine(string message) {
var cumulative = _message + message;
_message = null;
_hook(cumulative);
}
}
}

View File

@@ -0,0 +1,13 @@
<system.diagnostics>
<trace autoflush="true"/>
<sources>
<source name="Default" switchValue="Verbose">
<listeners>
<add name="CaptureTraceMessages" />
</listeners>
</source>
</sources>
<sharedListeners>
<add name="CaptureTraceMessages" type="Orchard.Specs.Hosting.HostingTraceListener, Orchard.Specs" initializeData="" />
</sharedListeners>
</system.diagnostics>

View File

@@ -25,7 +25,7 @@
<appSettings/>
<!--<system.diagnostics configSource="Config\Diagnostics.config" />-->
<system.diagnostics configSource="Config\Diagnostics.config" />
<!--
Set default transaction timeout to 30 minutes so that interactive debugging

View File

@@ -33,6 +33,7 @@ namespace Orchard.Specs.Hosting {
VirtualDirectory = virtualDirectory;
_webHostAgent = (WebHostAgent)ApplicationHost.CreateApplicationHost(typeof(WebHostAgent), VirtualDirectory, PhysicalDirectory);
}
public void CopyExtension(string extensionFolder, string extensionName) {

View File

@@ -86,6 +86,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Hosting\HostingTraceListener.cs" />
<Compile Include="Hosting\Orchard.Web\HelloYetAgainHandler.cs" />
<Compile Include="Hosting\RequestExtensions.cs" />
<Compile Include="Hosting\RequestDetails.cs" />
@@ -126,6 +127,9 @@
<Content Include="Hosting\Orchard.Web\Themes\Web.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Hosting\Orchard.Web\Config\Diagnostics.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="WebHosting.feature">
<Generator>SpecFlowSingleFileGenerator</Generator>
<LastGenOutput>WebHosting.feature.cs</LastGenOutput>

View File

@@ -1,5 +1,6 @@
using Autofac;
using Autofac.Core.Registration;
using Moq;
using NUnit.Framework;
using Orchard.Environment;
using Orchard.Environment.Configuration;
@@ -17,16 +18,16 @@ namespace Orchard.Tests.Environment.ShellBuilders {
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<DefaultShellContextFactory>().As<IShellContextFactory>();
builder.RegisterAutoMocking();
builder.RegisterAutoMocking(Moq.MockBehavior.Strict);
_container = builder.Build();
}
[Test]
public void NormalExecutionReturnsExpectedObjects() {
var settings = new ShellSettings {Name = "Default"};
var topologyDescriptor = new ShellTopologyDescriptor {SerialNumber = 6655321};
var settings = new ShellSettings { Name = "Default" };
var topologyDescriptor = new ShellTopologyDescriptor { SerialNumber = 6655321 };
var topology = new ShellTopology();
ILifetimeScope shellLifetimeScope;
var shellLifetimeScope = _container.BeginLifetimeScope("shell");
_container.Mock<ITopologyDescriptorCache>()
.Setup(x => x.Fetch("Default"))
@@ -38,7 +39,7 @@ namespace Orchard.Tests.Environment.ShellBuilders {
_container.Mock<IShellContainerFactory>()
.Setup(x => x.CreateContainer(topology))
.Returns(shellLifetimeScope = _container.BeginLifetimeScope("shell"));
.Returns(shellLifetimeScope );
_container.Mock<ITopologyDescriptorManager>()
.Setup(x => x.GetTopologyDescriptor())
@@ -54,5 +55,23 @@ namespace Orchard.Tests.Environment.ShellBuilders {
Assert.That(context.LifetimeScope, Is.SameAs(shellLifetimeScope));
Assert.That(context.Shell, Is.SameAs(shellLifetimeScope.Resolve<IOrchardShell>()));
}
[Test]
public void NullSettingsReturnsSetupContext() {
var topology = new ShellTopology();
_container.Mock<ICompositionStrategy>()
.Setup(x => x.Compose(It.IsAny<ShellTopologyDescriptor>()))
.Returns(topology);
_container.Mock<IShellContainerFactory>()
.Setup(x => x.CreateContainer(topology))
.Returns(_container.BeginLifetimeScope("shell"));
var factory = _container.Resolve<IShellContextFactory>();
var context = factory.Create(null);
Assert.That(context.TopologyDescriptor.EnabledFeatures, Has.Some.With.Property("Name").EqualTo("Setup"));
}
}
}

View File

@@ -1,2 +1,5 @@
name: Setup
antiforgery: enabled
antiforgery: enabled
features:
Orchard.Setup:
description: Components needed to initialize a new instance or tenant

View File

@@ -47,6 +47,8 @@ namespace Orchard.Environment {
}
private static IEnumerable<Feature> CoreFeatures() {
var frameworkTypes = typeof(OrchardStarter).Assembly.GetExportedTypes();
var core = new Feature {
Descriptor = new FeatureDescriptor {
Name = "Core",
@@ -56,11 +58,12 @@ namespace Orchard.Environment {
AntiForgery = "enabled",
},
},
ExportedTypes = new[] {
typeof (ContentTypeRecord),
typeof (ContentItemRecord),
typeof (ContentItemVersionRecord),
},
ExportedTypes = frameworkTypes.Where(t => t.IsClass && !t.IsAbstract),
//ExportedTypes = new[] {
// typeof (ContentTypeRecord),
// typeof (ContentItemRecord),
// typeof (ContentItemVersionRecord),
//},
};
return new[] { core };
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.Environment.ShellBuilders;
using Orchard.Logging;
using Orchard.Mvc;
using Orchard.Mvc.ViewEngines;
using Orchard.Utility.Extensions;
@@ -29,13 +30,17 @@ namespace Orchard.Environment {
_tenantManager = tenantManager;
_shellContextFactory = shellContextFactory;
_controllerBuilder = controllerBuilder;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public IList<ShellContext> Current {
get { return BuildCurrent().ToReadOnlyCollection(); }
}
void IOrchardHost.Initialize() {
Logger.Information("Initializing");
ViewEngines.Engines.Insert(0, LayoutViewEngine.CreateShim());
_controllerBuilder.SetControllerFactory(new OrchardControllerFactory());
ServiceLocator.SetLocator(t => _containerProvider.RequestLifetime.Resolve(t));
@@ -57,26 +62,40 @@ namespace Orchard.Environment {
}
IStandaloneEnvironment IOrchardHost.CreateStandaloneEnvironment(ShellSettings shellSettings) {
var shellContext = _shellContextFactory.Create(shellSettings);
Logger.Debug("Creating standalone environment for tenant {0}", shellSettings.Name);
var shellContext = CreateShellContext(shellSettings);
return new StandaloneEnvironment(shellContext.LifetimeScope);
}
IEnumerable<ShellContext> GetCurrent() {
lock (this) {
return _current ?? (_current = BuildCurrent());
}
}
IEnumerable<ShellContext> BuildCurrent() {
return CreateAndActivate().ToArray();
lock (this) {
return _current ?? (_current = CreateAndActivate().ToArray());
}
}
IEnumerable<ShellContext> CreateAndActivate() {
foreach(var settings in _tenantManager.LoadSettings()) {
var context = _shellContextFactory.Create(settings);
context.Shell.Activate();
yield return context;
var allSettings = _tenantManager.LoadSettings();
if (allSettings.Any()) {
return allSettings.Select(
settings => {
var context = CreateShellContext(settings);
context.Shell.Activate();
return context;
});
}
return new[] {CreateSetupContext()};
}
ShellContext CreateSetupContext() {
Logger.Debug("Creating shell context for setup");
return _shellContextFactory.Create(null);
}
ShellContext CreateShellContext(ShellSettings settings) {
Logger.Debug("Creating shell context for tenant {0}", settings.Name);
return _shellContextFactory.Create(settings);
}
protected virtual void BeginRequest() {
@@ -99,7 +118,7 @@ namespace Orchard.Environment {
finally {
containerProvider.EndRequestLifetime();
}
}
}
}
}

View File

@@ -1,9 +0,0 @@
using System.Web.Mvc;
using System.Web.Routing;
namespace Orchard.Environment {
public class HostContext {
public ControllerBuilder ControllerBuilder { get; set; }
public RouteCollection Routes { get; set; }
}
}

View File

@@ -13,52 +13,71 @@ using Orchard.Environment.Extensions.Loaders;
using Orchard.Environment.ShellBuilders;
using Orchard.Environment.Topology;
using Orchard.Events;
using Orchard.Logging;
namespace Orchard.Environment {
public static class OrchardStarter {
public static IContainer CreateHostContainer(Action<ContainerBuilder> registrations) {
var builder = new ContainerBuilder();
builder.RegisterModule(new LoggingModule());
// a single default host implementation is needed for bootstrapping a web app domain
builder.RegisterType<DefaultOrchardHost>().As<IOrchardHost>().SingleInstance();
builder.RegisterType<DefaultCompositionStrategy>().As<ICompositionStrategy_Obsolete>().SingleInstance();
builder.RegisterType<DefaultShellContainerFactory>().As<IShellContainerFactory>().SingleInstance();
builder.RegisterType<AppDataFolder>().As<IAppDataFolder>().SingleInstance();
builder.RegisterType<DefaultTenantManager>().As<ITenantManager>().SingleInstance();
builder.RegisterType<SafeModeShellContainerFactory>().As<IShellContainerFactory_Obsolete>().SingleInstance();
builder.RegisterType<DefaultTopologyDescriptorCache>().As<ITopologyDescriptorCache>().SingleInstance();
builder.RegisterType<DefaultOrchardEventBus>().As<IEventBus>().SingleInstance();
builder.RegisterType<AppDataFolder>().As<IAppDataFolder>().SingleInstance();
builder.RegisterType<DefaultOrchardHost>().As<IOrchardHost>().SingleInstance();
{
builder.RegisterType<DefaultTenantManager>().As<ITenantManager>().SingleInstance();
builder.RegisterType<DefaultShellContextFactory>().As<IShellContextFactory>().SingleInstance();
{
builder.RegisterType<DefaultTopologyDescriptorCache>().As<ITopologyDescriptorCache>().SingleInstance();
builder.RegisterType<DefaultCompositionStrategy>()
.As<ICompositionStrategy>()
.As<ICompositionStrategy_Obsolete>()
.SingleInstance();
{
builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance();
{
builder.RegisterType<ModuleFolders>().As<IExtensionFolders>()
.WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Modules" }))
.SingleInstance();
builder.RegisterType<AreaFolders>().As<IExtensionFolders>()
.WithParameter(new NamedParameter("paths", new[] { "~/Areas" }))
.SingleInstance();
builder.RegisterType<ThemeFolders>().As<IExtensionFolders>()
.WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Themes" }))
.SingleInstance();
builder.RegisterType<AreaExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<CoreExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<ReferencedExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<PrecompiledExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<DynamicExtensionLoader>().As<IExtensionLoader>().SingleInstance();
}
}
builder.RegisterType<DefaultShellContainerFactory>().As<IShellContainerFactory>().SingleInstance();
}
}
builder.RegisterType<DefaultOrchardShell>().As<IOrchardShell>().InstancePerMatchingLifetimeScope("shell");
// The container provider gives you access to the lowest container at the time,
// and dynamically creates a per-request container. The EndRequestLifetime method
// still needs to be called on end request, but that's the host component's job to worry about
builder.RegisterType<ContainerProvider>().As<IContainerProvider>().InstancePerLifetimeScope();
builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance();
builder.RegisterType<AreaExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<CoreExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<ReferencedExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<PrecompiledExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<DynamicExtensionLoader>().As<IExtensionLoader>().SingleInstance();
builder.RegisterType<ModuleFolders>().As<IExtensionFolders>()
.WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Modules" }))
.SingleInstance();
builder.RegisterType<AreaFolders>().As<IExtensionFolders>()
.WithParameter(new NamedParameter("paths", new[] { "~/Areas" }))
.SingleInstance();
builder.RegisterType<ThemeFolders>().As<IExtensionFolders>()
.WithParameter(new NamedParameter("paths", new[] { "~/Core", "~/Themes" }))
.SingleInstance();
registrations(builder);
var autofacSection = ConfigurationManager.GetSection(ConfigurationSettingsReader.DefaultSectionName);
if (autofacSection != null)
builder.RegisterModule(new ConfigurationSettingsReader());
var optionalHostConfig = HostingEnvironment.MapPath("~/Config/Host.config");
if (File.Exists(optionalHostConfig))
builder.RegisterModule(new ConfigurationSettingsReader(ConfigurationSettingsReader.DefaultSectionName, optionalHostConfig));

View File

@@ -1,7 +1,11 @@
using System;
using System.Linq;
using Autofac;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions.Models;
using Orchard.Environment.Topology;
using Orchard.Environment.Topology.Models;
using Orchard.Logging;
namespace Orchard.Environment.ShellBuilders {
public class DefaultShellContextFactory : IShellContextFactory {
@@ -16,30 +20,70 @@ namespace Orchard.Environment.ShellBuilders {
_topologyDescriptorCache = topologyDescriptorCache;
_compositionStrategy = compositionStrategy;
_shellContainerFactory = shellContainerFactory;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public ShellContext Create(ShellSettings settings) {
var cachedTopology = _topologyDescriptorCache.Fetch(settings.Name);
// handle null-(e.g. cache miss)
var topology = _compositionStrategy.Compose(cachedTopology);
var shellScope = _shellContainerFactory.CreateContainer(topology);
ShellTopologyDescriptor currentTopology;
using (var standaloneEnvironment = new StandaloneEnvironment(shellScope)) {
var topologyDescriptorProvider = standaloneEnvironment.Resolve<ITopologyDescriptorManager>();
currentTopology = topologyDescriptorProvider.GetTopologyDescriptor();
if (settings == null) {
return CreateSetupContext();
}
if (cachedTopology.SerialNumber != currentTopology.SerialNumber) {
_topologyDescriptorCache.Store(settings.Name, currentTopology);
topology = _compositionStrategy.Compose(currentTopology);
Logger.Debug("Creating shell context for tenant {0}", settings.Name);
var cachedDescriptor = _topologyDescriptorCache.Fetch(settings.Name);
if (cachedDescriptor == null) {
Logger.Information("No topology cached. Starting with minimum components.");
cachedDescriptor = new ShellTopologyDescriptor {
SerialNumber = 0,
EnabledFeatures = Enumerable.Empty<TopologyFeature>(),
Parameters = Enumerable.Empty<TopologyParameter>(),
};
}
// handle null-(e.g. cache miss)
var topology = _compositionStrategy.Compose(cachedDescriptor);
var shellScope = _shellContainerFactory.CreateContainer(topology);
ShellTopologyDescriptor currentDescriptor;
using (var standaloneEnvironment = new StandaloneEnvironment(shellScope)) {
var topologyDescriptorProvider = standaloneEnvironment.Resolve<ITopologyDescriptorManager>();
currentDescriptor = topologyDescriptorProvider.GetTopologyDescriptor();
}
if (cachedDescriptor.SerialNumber != currentDescriptor.SerialNumber) {
Logger.Information("Newer topology obtained. Rebuilding shell container.");
_topologyDescriptorCache.Store(settings.Name, currentDescriptor);
topology = _compositionStrategy.Compose(currentDescriptor);
shellScope = _shellContainerFactory.CreateContainer(topology);
}
return new ShellContext {
Settings = settings,
TopologyDescriptor = currentTopology,
TopologyDescriptor = currentDescriptor,
Topology = topology,
LifetimeScope = shellScope,
Shell = shellScope.Resolve<IOrchardShell>(),
};
}
private ShellContext CreateSetupContext() {
Logger.Warning("No shell settings available. Creating shell context for setup");
var settings = new ShellSettings { Name = "__Orchard__Setup__" };
var descriptor = new ShellTopologyDescriptor {
SerialNumber = -1,
EnabledFeatures = new[] { new TopologyFeature { Name = "Orchard.Setup" } },
};
var topology = _compositionStrategy.Compose(descriptor);
var shellScope = _shellContainerFactory.CreateContainer(topology);
return new ShellContext {
Settings = settings,
TopologyDescriptor = descriptor,
Topology = topology,
LifetimeScope = shellScope,
Shell = shellScope.Resolve<IOrchardShell>(),

View File

@@ -1,7 +1,13 @@
using System.Collections.Generic;
using System.Linq;
namespace Orchard.Environment.Topology.Models {
public class ShellTopologyDescriptor {
public ShellTopologyDescriptor() {
EnabledFeatures = Enumerable.Empty<TopologyFeature>();
Parameters = Enumerable.Empty<TopologyParameter>();
}
public int SerialNumber { get; set; }
public IEnumerable<TopologyFeature> EnabledFeatures { get; set; }
public IEnumerable<TopologyParameter> Parameters { get; set; }

View File

@@ -126,7 +126,6 @@
<Compile Include="Environment\DefaultCompositionStrategy.cs" />
<Compile Include="Environment\DefaultOrchardHost.cs" />
<Compile Include="Mvc\OrchardControllerFactory.cs" />
<Compile Include="Environment\HostContext.cs" />
<Compile Include="Environment\IOrchardHost.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Validation\Argument.cs" />