I'm running a single instance of Orchard CMS on my web server with two custom modules, ModuleFirst
and ModuleSecond
. For reasons, I want these two to act as separate websites with their own domain and homepage. I can not set up additional websites or use Orchard's built-in Tenants feature.
The way I went about achieving this is as follows:
first-domain.com
and second-domain.com
Implemented a ThemeSelector (which I think acts like an ActionFilter) that switches the theme based on the host of the Url in the incoming Request
if (host.Contains("second-domain.com"))
{
useSecondTheme = true;
}
This is working reasonably well for the most part. I can navigate to first-domain.com/foo
and second-domain.com/bar
and it looks like I'm on different websites.
For the two "homepages" I can't make a unique route because I don't want to add any suffixes. Both projects define a blank route that should lead to their respective Home/Index but I can't figure out how to make this work.
new RouteDescriptor {
Priority = 90,
Route = new Route(
"",
new RouteValueDictionary { // Defaults
{"area", "ModuleFirst"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary(), // Constraints
new RouteValueDictionary { // Datatokens
{"area", "ModuleFirst"}
},
new MvcRouteHandler())
}
new RouteDescriptor {
Priority = 100,
Route = new Route(
"",
new RouteValueDictionary { // Defaults
{"area", "ModuleSecond"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary(), // Constraints
new RouteValueDictionary { // Datatokens
{"area", "ModuleSecond"}
},
new MvcRouteHandler())
}
I tried to implement an ActionFilter that Redirects to ModuleFirst/Home/Index
when a request with host url first-domain.com
reaches ModuleSecond/Home/Index
but this obviously doesn't work since it just keeps hitting the highest priority route over and over and breaks the website.
I have also tried to implement a custom RouteConstraint on the route with the highest priority to block all incoming request that don't come from its intended domain, assuming that those would then fall back on the lower priority route.
public class SecondConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.Url.DnsSafeHost.Contains("second-domain.com");
}
}
used as follows:
new RouteDescriptor {
Priority = 100,
Route = new Route(
"",
new RouteValueDictionary { // Defaults
{"area", "ModuleSecond"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary { // Constraints
{"isSecond", new SecondConstraint()}
},
new RouteValueDictionary { // Datatokens
{"area", "ModuleSecond"}
},
new MvcRouteHandler())
}
I can now navigate to second-domain.com
just fine and get the correct page but navigating to first-domain.com
times out. I haven't managed to find any examples of RouteConstraints in Orchard though, so maybe I'm doing it wrong.
Though I would recommend anyone who is about to do something like this from scratch to use tenants and OAuth instead if possible, as Bertrand suggests in his comment, I thought of a fairly clean way to accomplish what I wanted.
Instead of using two separate modules, I just use one.
In my one module, I implement IRouteProvider
to override the default route, which catches both first-domain.com/
and second-domain.com/
public class Routes : IRouteProvider
{
public void GetRoutes(ICollection<RouteDescriptor> routes)
{
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);
}
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[] {
new RouteDescriptor {
Priority = 100,
Route = new Route(
"",
new RouteValueDictionary {
{"area", "MyModule"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "MyModule"}
},
new MvcRouteHandler())
}
};
}
}
Then I created a custom Part with just a single property bool IsHomepage
and attached it to the Page Type
public class SecondHomepagePart : ContentPart<SecondHomepagePartRecord>
{
public bool IsHomepage
{
get { return Retrieve(r => r.IsHomepage); }
set { Store(r => r.IsHomepage, value); }
}
}
In the driver I make sure that there is only ever one "second homepage"
protected override DriverResult Editor(SecondHomepagePart part, IUpdateModel updater, dynamic shapeHelper)
{
if (updater.TryUpdateModel(part, Prefix, null, null))
{
if (part.IsHomepage)
{
var otherHomepages = _orchardServices.ContentManager.Query<SecondHomepagePart>().Where<SecondHomepagePartRecord>(r => r.IsHomepage && r.Id != part.Id).List();
foreach (var homepagePart in otherHomepages)
{
homepagePart.IsHomepage = false;
}
}
}
return Editor(part, shapeHelper);
}
Then in my custom HomeController
I just check the current domain, fetch the ContentItem with the blank AutoroutePart
or the SecondHomepagePart
where IsHomepage
is set to true depending on the domain, build the Detail Shape of that item and then display that Shape in my custom Index.cshtml
view.
Controller:
public ActionResult Index()
{
var host = HttpContext.Request.Url.DnsSafeHost;
var model = new IndexViewModel();
IContent homepageItem;
if (host.Contains("first-domain.com"))
{
homepageItem = _homeAliasService.GetHomePage();
}
else
{
homepageItem = _orchardServices.ContentManager.Query<SecondHomepagePart>()
.Where<SecondHomepagePartRecord>(r => r.IsHomepage)
.List()
.FirstOrDefault();
}
model.Homepage = _orchardServices.ContentManager.BuildDisplay(homepageItem, "Detail");
return View(model);
}
View:
@model MyModule.ViewModels.Home.IndexViewModel
@if (Model.Homepage != null)
{
@Display(Model.Homepage)
}
I use the same check in an IConditionProvider
and IThemeSelector
to have a layer rule for each domain and to set the appropriate theme automatically. Now I essentially have two completely different websites to the outside world but with shared content, widgets, parts, custom settings etc. This is a quick and easy solution for a client who sells the same products from the same stock but under different brandings and under different conditions, along with some exclusive content that's different for both.