Nearly working integration of multi tenancy components

Tenant service provides refresh signal to host (temporary need)
Setup module changed to run in any named shell that is uninitialized
Table prefix added to setup form (for when sql server option is selected)
CreateSetupContext takes shellsettings to support uninitialized tenants
Removed name on default route provider - adding several routes by same name not allowed in single appdomain

--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-04-23 17:05:28 -07:00
parent 5fb845dcc9
commit 0c6ae8b8d7
19 changed files with 254 additions and 24 deletions

View File

@@ -53,5 +53,16 @@ namespace Orchard.Specs.Bindings {
});
}
[When(@"I cycle the app domain")]
public void WhenICycleTheAppDomain() {
var webApp = Binding<WebAppHosting>();
webApp.Host.Execute(() => {
Trace.WriteLine("This call to Host.Reinitialize should not be needed, eventually");
MvcApplication.Host.Reinitialize_Obsolete();
});
}
}
}

View File

@@ -119,6 +119,14 @@ namespace Orchard.Specs.Bindings {
}
[When(@"I go to ""(.*)"" on host (.*)")]
public void WhenIGoToPathOnHost(string urlPath, string host) {
Host.HostName = host;
_details = Host.SendRequest(urlPath);
_doc = new HtmlDocument();
_doc.Load(new StringReader(_details.ResponseText));
}
[When(@"I go to ""(.*)""")]
public void WhenIGoTo(string urlPath) {
_details = Host.SendRequest(urlPath);

View File

@@ -12,6 +12,7 @@ namespace Orchard.Specs.Hosting {
ResponseHeaders = new Dictionary<string, string>();
}
public string HostName { get; set; }
public string UrlPath { get; set; }
public string Page { get; set; }
public string Query { get; set; }

View File

@@ -16,6 +16,7 @@ namespace Orchard.Specs.Hosting {
var physicalPath = Bleroy.FluentPath.Path.Get(webHost.PhysicalDirectory);
var details = new RequestDetails {
HostName = webHost.HostName,
UrlPath = urlPath,
Page = physicalPath
.Combine(urlPath.TrimStart('/', '\\'))
@@ -89,6 +90,9 @@ namespace Orchard.Specs.Hosting {
if (_details.RequestHeaders.TryGetValue("Cookie", out value))
return value;
}
else if (index==HeaderHost) {
return _details.HostName;
}
return base.GetKnownRequestHeader(index);
}

View File

@@ -11,6 +11,7 @@ namespace Orchard.Specs.Hosting {
private Path _tempSite;
private Path _orchardWebPath;
public void Initialize(string templateName, string virtualDirectory) {
var baseDir = Path.Get(AppDomain.CurrentDomain.BaseDirectory);
@@ -29,6 +30,7 @@ namespace Orchard.Specs.Hosting {
.ShallowCopy("*.dll", _tempSite.Combine("bin"))
.ShallowCopy("*.pdb", _tempSite.Combine("bin"));
HostName = "localhost";
PhysicalDirectory = _tempSite;
VirtualDirectory = virtualDirectory;
@@ -44,6 +46,7 @@ namespace Orchard.Specs.Hosting {
sourceModule.Combine("Views").DeepCopy(targetModule.Combine("Views"));
}
public string HostName { get; set; }
public string PhysicalDirectory { get; private set; }
public string VirtualDirectory { get; private set; }

View File

@@ -41,3 +41,36 @@ Scenario: A new tenant is created with uninitialized state
And I am redirected
Then I should see "<td>Uninitialized</td>"
And the status should be 200 OK
Scenario: A new tenant goes to the setup screen
Given I have installed Orchard
And I have installed "Orchard.MultiTenancy"
When I go to "Admin/MultiTenancy/Add"
And I fill in
| name | value |
| Name | Scott |
| RequestUrlHost | scott.example.org |
And I hit "Save"
And I go to "/Setup" on host scott.example.org
Then I should see "Welcome to Orchard"
And I should see "Finish Setup"
And the status should be 200 OK
Scenario: A new tenant runs the setup
Given I have installed Orchard
And I have installed "Orchard.MultiTenancy"
When I go to "Admin/MultiTenancy/Add"
And I fill in
| name | value |
| Name | Scott |
| RequestUrlHost | scott.example.org |
And I hit "Save"
And I go to "/Setup" on host scott.example.org
And I fill in
| name | value |
| SiteName | Scott Site |
| AdminPassword | 6655321 |
And I hit "Finish Setup"
And I go to "/Default.aspx"
Then I should see "<h1>Scott Site</h1>"
And I should see "Welcome, <strong>admin</strong>!"

View File

@@ -2,7 +2,7 @@
// <auto-generated>
// This code was generated by SpecFlow (http://www.specflow.org/).
// SpecFlow Version:1.2.0.0
// Runtime Version:2.0.50727.3603
// Runtime Version:2.0.50727.4927
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -158,6 +158,98 @@ this.ScenarioSetup(scenarioInfo);
testRunner.Then("I should see \"<td>Uninitialized</td>\"");
#line 43
testRunner.And("the status should be 200 OK");
#line hidden
testRunner.CollectScenarioErrors();
}
[NUnit.Framework.TestAttribute()]
[NUnit.Framework.DescriptionAttribute("A new tenant goes to the setup screen")]
public virtual void ANewTenantGoesToTheSetupScreen()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("A new tenant goes to the setup screen", ((string[])(null)));
#line 45
this.ScenarioSetup(scenarioInfo);
#line 46
testRunner.Given("I have installed Orchard");
#line 47
testRunner.And("I have installed \"Orchard.MultiTenancy\"");
#line 48
testRunner.When("I go to \"Admin/MultiTenancy/Add\"");
#line hidden
TechTalk.SpecFlow.Table table3 = new TechTalk.SpecFlow.Table(new string[] {
"name",
"value"});
table3.AddRow(new string[] {
"Name",
"Scott"});
table3.AddRow(new string[] {
"RequestUrlHost",
"scott.example.org"});
#line 49
testRunner.And("I fill in", ((string)(null)), table3);
#line 53
testRunner.And("I hit \"Save\"");
#line 54
testRunner.And("I go to \"/Setup\" on host scott.example.org");
#line 55
testRunner.Then("I should see \"Welcome to Orchard\"");
#line 56
testRunner.And("I should see \"Finish Setup\"");
#line 57
testRunner.And("the status should be 200 OK");
#line hidden
testRunner.CollectScenarioErrors();
}
[NUnit.Framework.TestAttribute()]
[NUnit.Framework.DescriptionAttribute("A new tenant runs the setup")]
public virtual void ANewTenantRunsTheSetup()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("A new tenant runs the setup", ((string[])(null)));
#line 59
this.ScenarioSetup(scenarioInfo);
#line 60
testRunner.Given("I have installed Orchard");
#line 61
testRunner.And("I have installed \"Orchard.MultiTenancy\"");
#line 62
testRunner.When("I go to \"Admin/MultiTenancy/Add\"");
#line hidden
TechTalk.SpecFlow.Table table4 = new TechTalk.SpecFlow.Table(new string[] {
"name",
"value"});
table4.AddRow(new string[] {
"Name",
"Scott"});
table4.AddRow(new string[] {
"RequestUrlHost",
"scott.example.org"});
#line 63
testRunner.And("I fill in", ((string)(null)), table4);
#line 67
testRunner.And("I hit \"Save\"");
#line 68
testRunner.And("I go to \"/Setup\" on host scott.example.org");
#line hidden
TechTalk.SpecFlow.Table table5 = new TechTalk.SpecFlow.Table(new string[] {
"name",
"value"});
table5.AddRow(new string[] {
"SiteName",
"Scott Site"});
table5.AddRow(new string[] {
"AdminPassword",
"6655321"});
#line 69
testRunner.And("I fill in", ((string)(null)), table5);
#line 73
testRunner.And("I hit \"Finish Setup\"");
#line 74
testRunner.And("I go to \"/Default.aspx\"");
#line 75
testRunner.Then("I should see \"<h1>Scott Site</h1>\"");
#line 76
testRunner.And("I should see \"Welcome, <strong>admin</strong>!\"");
#line hidden
testRunner.CollectScenarioErrors();
}

View File

@@ -162,5 +162,51 @@ namespace Orchard.Tests.Environment {
Assert.That(table.Match(new StubHttpContext("~/bar/foo", "wiki.example.com")), Is.Null);
}
[Test]
public void HostNameMatchesRightmostIfRequestIsLonger() {
var table = (IRunningShellTable) new RunningShellTable();
var settings = new ShellSettings {Name = "Default"};
var settingsA = new ShellSettings {Name = "Alpha", RequestUrlHost = "example.com"};
table.Add(settings);
table.Add(settingsA);
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "www.example.com")), Is.SameAs(settingsA));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "wiki.example.com")), Is.SameAs(settingsA));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "example.com")), Is.SameAs(settingsA));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "localhost")), Is.SameAs(settings));
}
[Test]
public void LongestMatchingHostHasPriority() {
var table = (IRunningShellTable) new RunningShellTable();
var settings = new ShellSettings {Name = "Default"};
var settingsA = new ShellSettings {Name = "Alpha", RequestUrlHost = "www.example.com"};
var settingsB = new ShellSettings {Name = "Beta", RequestUrlHost = "example.com"};
var settingsG = new ShellSettings {Name = "Gamma", RequestUrlHost = "wiki.example.com"};
table.Add(settings);
table.Add(settingsA);
table.Add(settingsB);
table.Add(settingsG);
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "www.example.com")), Is.SameAs(settingsA));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "wiki.example.com")), Is.SameAs(settingsG));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "username.example.com")), Is.SameAs(settingsB));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "localhost")), Is.SameAs(settings));
}
[Test]
public void ShellNameUsedToDistinctThingsAsTheyAreAdded() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default" };
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "removed.example.com" };
var settingsB = new ShellSettings { Name = "Alpha", RequestUrlHost = "added.example.com" };
table.Add(settings);
table.Add(settingsA);
table.Add(settingsB);
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "removed.example.com")), Is.SameAs(settings));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "added.example.com")), Is.SameAs(settingsB));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "localhost")), Is.SameAs(settings));
}
}
}

View File

@@ -75,7 +75,7 @@ namespace Orchard.Tests.Environment.ShellBuilders {
.Returns(_container.BeginLifetimeScope("shell"));
var factory = _container.Resolve<IShellContextFactory>();
var context = factory.CreateSetupContext();
var context = factory.CreateSetupContext(new ShellSettings { Name = "Default" });
Assert.That(context.Descriptor.EnabledFeatures, Has.Some.With.Property("Name").EqualTo("Orchard.Setup"));
}

View File

@@ -1,12 +1,15 @@
using System.Collections.Generic;
using Orchard.Environment;
using Orchard.Environment.Configuration;
namespace Orchard.MultiTenancy.Services {
public class TenantService : ITenantService {
private readonly IShellSettingsManager _shellSettingsManager;
private readonly IOrchardHost _orchardHost;
public TenantService(IShellSettingsManager shellSettingsManager) {
public TenantService(IShellSettingsManager shellSettingsManager, IOrchardHost orchardHost) {
_shellSettingsManager = shellSettingsManager;
_orchardHost = orchardHost;
}
#region Implementation of ITenantService
@@ -17,6 +20,10 @@ namespace Orchard.MultiTenancy.Services {
public void CreateTenant(ShellSettings settings) {
_shellSettingsManager.SaveSettings(settings);
// MultiTenancy: This will not be needed when host listens to event bus
_orchardHost.Reinitialize_Obsolete();
}
#endregion

View File

@@ -23,6 +23,7 @@ using Orchard.UI.Notify;
namespace Orchard.Setup.Controllers {
[ValidateInput(false)]
public class SetupController : Controller {
private readonly ShellSettings _shellSettings;
private readonly INotifier _notifier;
private readonly IOrchardHost _orchardHost;
private readonly IShellSettingsManager _shellSettingsManager;
@@ -31,12 +32,14 @@ namespace Orchard.Setup.Controllers {
private readonly IAppDataFolder _appDataFolder;
public SetupController(
ShellSettings shellSettings,
INotifier notifier,
IOrchardHost orchardHost,
IShellSettingsManager shellSettingsManager,
IShellContainerFactory shellContainerFactory,
ICompositionStrategy compositionStrategy,
IAppDataFolder appDataFolder) {
_shellSettings = shellSettings;
_notifier = notifier;
_orchardHost = orchardHost;
_shellSettingsManager = shellSettingsManager;
@@ -75,10 +78,10 @@ namespace Orchard.Setup.Controllers {
}
try {
var shellSettings = new ShellSettings {
Name = "Default",
var shellSettings = new ShellSettings(_shellSettings) {
DataProvider = model.DatabaseOptions ? "SQLite" : "SqlServer",
DataConnectionString = model.DatabaseConnectionString
DataConnectionString = model.DatabaseConnectionString,
DataTablePrefix = model.DatabaseTablePrefix,
};
// The vanilla Orchard distibution has the following modules enabled.
@@ -99,10 +102,10 @@ namespace Orchard.Setup.Controllers {
var bootstrapLifetimeScope = _shellContainerFactory.CreateContainer(shellSettings, shellToplogy);
using (var environment = new StandaloneEnvironment(bootstrapLifetimeScope)) {
environment.Resolve<ISessionFactoryHolder>().CreateDatabase();
environment.Resolve<IShellDescriptorManager>().UpdateShellDescriptor(
0,
shellDescriptor.EnabledFeatures,
0,
shellDescriptor.EnabledFeatures,
shellDescriptor.Parameters);
}
@@ -110,6 +113,9 @@ namespace Orchard.Setup.Controllers {
// creating a standalone environment.
// in theory this environment can be used to resolve any normal components by interface, and those
// components will exist entirely in isolation - no crossover between the safemode container currently in effect
// must mark state as Running - otherwise standalone enviro is created "for setup"
shellSettings.State = new TenantState("Running");
using (var environment = _orchardHost.CreateStandaloneEnvironment(shellSettings)) {
try {
// create superuser
@@ -165,9 +171,9 @@ namespace Orchard.Setup.Controllers {
}
}
shellSettings.State = new TenantState("Running");
_shellSettingsManager.SaveSettings(shellSettings);
// MultiTenancy: This will not be needed when host listens to event bus
_orchardHost.Reinitialize_Obsolete();
// redirect to the welcome page.

View File

@@ -1,3 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations;
using Orchard.Setup.Annotations;
using Orchard.Mvc.ViewModels;
@@ -17,5 +18,7 @@ namespace Orchard.Setup.ViewModels {
public bool DatabaseOptions { get; set; }
[SqlDatabaseConnectionString]
public string DatabaseConnectionString { get; set; }
public string DatabaseTablePrefix { get; set; }
}
}

View File

@@ -35,6 +35,10 @@ using (Html.BeginFormAntiForgeryPost()) { %>
<%=Html.EditorFor(svm => svm.DatabaseConnectionString)%>
<span class="hint"><%=_Encoded("Example:") %><br /><%=_Encoded("Data Source=sqlServerName;Initial Catalog=dbName;Persist Security Info=True;User ID=userName;Password=password") %></span>
</span>
<span data-controllerid="sql">
<label for="DatabaseTablePrefix"><%=_Encoded("Database Table Prefix") %></label>
<%=Html.EditorFor(svm => svm.DatabaseTablePrefix)%>
</span>
</div>
</fieldset>
<fieldset>

View File

@@ -5,6 +5,20 @@
/// from the App_Data settings.txt files.
/// </summary>
public class ShellSettings {
public ShellSettings() {
State = new TenantState("Invalid");
}
public ShellSettings(ShellSettings settings) {
Name = settings.Name;
DataProvider = settings.DataProvider;
DataConnectionString = settings.DataConnectionString;
DataTablePrefix = settings.DataTablePrefix;
RequestUrlHost = settings.RequestUrlHost;
RequestUrlPrefix = settings.RequestUrlPrefix;
State = settings.State;
}
public string Name { get; set; }
public string DataProvider { get; set; }

View File

@@ -96,11 +96,16 @@ namespace Orchard.Environment {
}
ShellContext CreateSetupContext() {
Logger.Debug("Creating shell context for setup");
return _shellContextFactory.CreateSetupContext();
Logger.Debug("Creating shell context for root setup");
return _shellContextFactory.CreateSetupContext(new ShellSettings { Name = "Default" });
}
ShellContext CreateShellContext(ShellSettings settings) {
if (settings.State.CurrentState == TenantState.State.Uninitialized) {
Logger.Debug("Creating shell context for tenant {0} setup", settings.Name);
return _shellContextFactory.CreateSetupContext(settings);
}
Logger.Debug("Creating shell context for tenant {0}", settings.Name);
return _shellContextFactory.CreateShellContext(settings);
}

View File

@@ -8,7 +8,6 @@ using Orchard.Environment.Configuration;
namespace Orchard.Environment {
public interface IRunningShellTable {
void Add(ShellSettings settings);
IEnumerable<ShellSettings> List();
ShellSettings Match(HttpContextBase httpContext);
}
@@ -50,10 +49,6 @@ namespace Orchard.Environment {
}
}
public IEnumerable<ShellSettings> List() {
return _shells;
}
public ShellSettings Match(HttpContextBase httpContext) {
var host = httpContext.Request.ServerVariables.Get("HTTP_HOST") ?? "";

View File

@@ -20,7 +20,7 @@ namespace Orchard.Environment.ShellBuilders {
/// Builds a shell context for an uninitialized Orchard instance. Needed
/// to display setup user interface.
/// </summary>
ShellContext CreateSetupContext();
ShellContext CreateSetupContext(ShellSettings settings);
}
public class ShellContextFactory : IShellContextFactory {
@@ -87,11 +87,9 @@ namespace Orchard.Environment.ShellBuilders {
};
}
public ShellContext CreateSetupContext() {
public ShellContext CreateSetupContext(ShellSettings settings) {
Logger.Warning("No shell settings available. Creating shell context for setup");
var settings = new ShellSettings { Name = "Default" };
var descriptor = new ShellDescriptor {
SerialNumber = -1,
EnabledFeatures = new[] { new ShellFeature { Name = "Orchard.Setup" } },

View File

@@ -10,8 +10,9 @@ namespace Orchard.Mvc.ModelBinders {
}
public void Publish(IEnumerable<ModelBinderDescriptor> binders) {
// MultiTenancy: should hook default model binder instead and rely on shell-specific binders (instead adding to type dictionary)
foreach (var descriptor in binders) {
_binders.Add(descriptor.Type, descriptor.ModelBinder);
_binders[descriptor.Type] = descriptor.ModelBinder;
}
}
}

View File

@@ -8,8 +8,7 @@ namespace Orchard.Mvc.Routes {
public class DefaultRouteProvider : IRouteProvider {
public IEnumerable<RouteDescriptor> GetRoutes() {
return new[] {
new RouteDescriptor {
Name = "Default",
new RouteDescriptor {
Priority = -20,
Route = new Route(
"{controller}/{action}/{id}",