Running multiple sites (single installation) with a fall back default site
Hi All,
I am trying to run multiple sites from a single installation and have the ability for each page to fall back to a default page/content. I have a client that contains multiple brands (each requiring a website) and for some of the pages the content is very similar, for these pages I plan on using one page across all brands and swap out the brand logo, this page would sit under the 'default' site. The remaining pages would be custom for each of the sites and override the default page of the same name. The following structure represents my requirements.
Default pages/content (common pages across all brands):
home
buy
sell
other pages
Site 1 (no buy page, fallback to the default site buy page):
home
sell
other pages
Site 2 (no sell page, fallback to the default site sell page):
home
buy
other pages
I currently have multiple sites setup in IIS and the domains in Umbraco, this is allowing me to run multiple sites from one installation.
The next step would be to have the following logic:
Navigate a url i.e. Site1.com/buy
Check if the page exists in the current site
If the page exists, render this as normal and if not fallback to the 'Default' site and render this page/content.
I have spent some considerable time researching this and cannot find the answer. I did come across the following video which is pretty much exactly what I am trying to achieve, however, I cannot figure out how to code this in Umbraco.
I am fairly new to Umbraco and have stumbled my way to this point. I would be very grateful if someone could give me some ideas, whether this is the correct approach and maybe some suggestions on how to achieve this?
I appreciate your time reading this post and look forward to hearing from the Umbraco experts :-)
That's okay! I think the direction you want to go is using an IContentFinder.
My biggest concern is that if you using this it will return the IPublishedContent for the page you find in the default site and therefore you might lose context of things like menus or footer content which is relevant to the site which is missing the content.
So looking at IContentFinder it looks like I can check for a page by URL and if it is not found fallback to the default site? This would return me the IPublishedContent for the default page, which I can render. I don't suppose you have an example of this?
I have also been playing around with custom templating, so for all of these brands I use the same master page. For each of the brand sites I have a page level property that determines the site theme and ultimately where to look for the master page etc. I think this overcomes the issue you were concerned about? The following code from the Default controller highlights how I an doing this:
public class DefaultController : RenderMvcController
{
/// <summary>
/// The default controller method
/// </summary>
/// <param name="model">
/// The model.
/// </param>
/// <returns>
/// The <see cref="ActionResult"/> to render the basic view model
/// </returns>
public override ActionResult Index(RenderModel model)
{
var currentTemplateName = model.Content.GetTemplateAlias();
//replace the live below with the ConfigurationData.json approach or similar
var siteTheme = model.Content.AncestorOrSelf(1).GetPropertyValue<string>("siteName");
var templatePath = ThemeHelper.GetFinalThemePath(siteTheme, ThemeHelper.PathType.View, currentTemplateName);
return View(templatePath, model);
}
}
Hey Ben,
I can provide a simple example of IContentFinder. It's actually fairly straightforward looking at it.
using Umbraco.Core;
using Umbraco.Web.Routing;
namespace My.Website
{
public class MultiSiteFallbackContentFinder : IContentFinder
{
public bool TryFindContent(PublishedContentRequest contentRequest)
{
// Get the current path regardless of domain
var path = contentRequest.Uri.GetAbsolutePathDecoded();
var contentCache = contentRequest.RoutingContext.UmbracoContext.ContentCache;
// Find the first content node in the content cache that matches this path
var foundContent = contentCache.GetByRoute(path);
contentRequest.PublishedContent = foundContent;
//If we found something return true
return foundContent == null;
}
}
}
Due to the nature of how Umbraco routing the first content node that matches /buy/ or /sell/ will be the one it finds.
So if you had:
site1.com/buy/
site2.com/buy/
but no:
site3.com/buy/
This code would find site1.com's IPublishedContent for /buy/ not site2.com.
To wire this in, simply add an ApplicationEventHandler with the following code.
using Umbraco.Core;
using Umbraco.Web.Routing;
namespace My.Website
{
public class BootManager : ApplicationEventHandler
{
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentFinderResolver.Current.AddType<MultiSiteFallbackContentFinder>();
}
}
}
This will ensure on Application Starting the routing engine will have a content finder. It's important to add it rather than insert as this will ensure it tries to call this content finder after the built-in content finder.
So I managed to get it working, if the page does not exist in the current site it picks up the fallback page.
I do have an issue, which I think you mentioned could come up. In my master page I pick up the site logo from a property defined in the page (media picker). When I load a fallback page it picks up that logo and not the logo of the site I am running from.
Hey Ben,
Yea, I feared this might happen. This part will be best handled in bespoke controller logic. Meaning, if you're currently doing your logic in views I'm not sure what to do for you.
Anyways, on to the solution!
using System.Web.Mvc;
using Umbraco.Core.Models;
using Umbraco.Web;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
namespace My.Website.Controllers
{
public class DefaultController : RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
var rootContent = GetRootContent(model);
// I've added your logic from above replacing model.Content.AncestorOrSelf with rootContent
var currentTemplateName = model.Content.GetTemplateAlias();
//replace the live below with the ConfigurationData.json approach or similar
var siteTheme = rootContent.GetPropertyValue<string>("siteName");
var templatePath = ThemeHelper.GetFinalThemePath(siteTheme, ThemeHelper.PathType.View, currentTemplateName);
return View(templatePath, model);
}
private IPublishedContent GetRootContent(RenderModel model)
{
if (this.PublishedContentRequest.HasDomain)
{
var domain = this.PublishedContentRequest.UmbracoDomain;
//if the domain isn't our current site domain then return the content for this domain's root
if (model.Content.Site().Id != domain.RootContentId)
{
return Umbraco.TypedContent(domain.RootContentId);
}
}
// return the current content root as a default case if there isn't fallback content being passed in by the contentfinder, in most cases if content exists in your other sites the code will hit this path.
return model.Content.Site();
}
}
}
Fortunately the Umbraco RenderMvcController has the PublishedContentRequest we were messing with earlier and we only modified the content not the domain property. So it still knows our original domain.
What I'd suggest is trying to find the right domain. If the PublishedContentRequest has a domain and that domain isn't the same as the current content's "Site" (basically a shorthand for finding the root) then return the root which is associated to that domain.
This will mean if you have site3.com/buy/ but are returning site1.com/buy/ content it will still know you're requesting that from a URL which has the UmbracoDomain from site3.com. So get the root content for site3.com!
I'd consider moving the GetRootContent(RenderModel model) to a more convenient place if you're planning to reuse this in multiple controllers.
Thanks for the info, much appreciated. I am running into an issue though. The domain is always null, even though I have set this in the 'Culture & Hostnames'. I am doing something wrong?
I will give it a try. I did re-save the domain which did not have any affect. It is odd that the domain is not being picked up, I can use a method to get all domains and they appear in the returned list as expected!
Running multiple sites (single installation) with a fall back default site
Hi All,
I am trying to run multiple sites from a single installation and have the ability for each page to fall back to a default page/content. I have a client that contains multiple brands (each requiring a website) and for some of the pages the content is very similar, for these pages I plan on using one page across all brands and swap out the brand logo, this page would sit under the 'default' site. The remaining pages would be custom for each of the sites and override the default page of the same name. The following structure represents my requirements.
Default pages/content (common pages across all brands):
Site 1 (no buy page, fallback to the default site buy page):
Site 2 (no sell page, fallback to the default site sell page):
I currently have multiple sites setup in IIS and the domains in Umbraco, this is allowing me to run multiple sites from one installation.
The next step would be to have the following logic:
I have spent some considerable time researching this and cannot find the answer. I did come across the following video which is pretty much exactly what I am trying to achieve, however, I cannot figure out how to code this in Umbraco.
40 different sites from a single Umbraco installation
I am fairly new to Umbraco and have stumbled my way to this point. I would be very grateful if someone could give me some ideas, whether this is the correct approach and maybe some suggestions on how to achieve this?
I appreciate your time reading this post and look forward to hearing from the Umbraco experts :-)
Ben
Hey Ben,
Out of interest. What version of Umbraco are you running on?
Thanks,
Jamie
Hey Jamie,
Sorry I should have included the version, 7.5.11.
Thanks,
That's okay! I think the direction you want to go is using an
IContentFinder
.My biggest concern is that if you using this it will return the
IPublishedContent
for the page you find in the default site and therefore you might lose context of things like menus or footer content which is relevant to the site which is missing the content.So looking at IContentFinder it looks like I can check for a page by URL and if it is not found fallback to the default site? This would return me the IPublishedContent for the default page, which I can render. I don't suppose you have an example of this?
I have also been playing around with custom templating, so for all of these brands I use the same master page. For each of the brand sites I have a page level property that determines the site theme and ultimately where to look for the master page etc. I think this overcomes the issue you were concerned about? The following code from the Default controller highlights how I an doing this:
This looks promising IContentFinder tutorial
Thanks,
Hey Ben,
I can provide a simple example of
IContentFinder
. It's actually fairly straightforward looking at it.Due to the nature of how Umbraco routing the first content node that matches /buy/ or /sell/ will be the one it finds.
So if you had:
but no:
site3.com/buy/
This code would find site1.com's IPublishedContent for /buy/ not site2.com.
To wire this in, simply add an ApplicationEventHandler with the following code.
This will ensure on Application Starting the routing engine will have a content finder. It's important to add it rather than insert as this will ensure it tries to call this content finder after the built-in content finder.
Thanks,
Jamie
This looks excellent, I will give this a shot but looks promising.
Very much appreciated, thanks.
Will mark as the solution if I get it working.
Hey Ben,
I hope it works for you. Otherwise let me know and we'll see what we can do, eh?
My tests were based on a fresh install which had all site roots at the top level of Content.
Thanks,
Jamie
Hey Jamie,
I hope it works as well :-)
I'll give you a shout if not.
Thanks again,
Hey Jamie,
So I managed to get it working, if the page does not exist in the current site it picks up the fallback page.
I do have an issue, which I think you mentioned could come up. In my master page I pick up the site logo from a property defined in the page (media picker). When I load a fallback page it picks up that logo and not the logo of the site I am running from.
Any ideas?
Thanks,
Ben
Hey Ben,
Yea, I feared this might happen. This part will be best handled in bespoke controller logic. Meaning, if you're currently doing your logic in views I'm not sure what to do for you.
Anyways, on to the solution!
Fortunately the Umbraco
RenderMvcController
has thePublishedContentRequest
we were messing with earlier and we only modified the content not the domain property. So it still knows our original domain.What I'd suggest is trying to find the right domain. If the PublishedContentRequest has a domain and that domain isn't the same as the current content's "Site" (basically a shorthand for finding the root) then return the root which is associated to that domain.
This will mean if you have site3.com/buy/ but are returning site1.com/buy/ content it will still know you're requesting that from a URL which has the UmbracoDomain from site3.com. So get the root content for site3.com!
I'd consider moving the
GetRootContent(RenderModel model)
to a more convenient place if you're planning to reuse this in multiple controllers.Thanks,
Jamie
Hey Jamie,
Thanks for the info, much appreciated. I am running into an issue though. The domain is always null, even though I have set this in the 'Culture & Hostnames'. I am doing something wrong?
Thanks,
Ben
Hey Ben,
That's odd. Have you tried republishing the XML cache? Not sure that would help but worth a try!
Thanks,
Jamie
Hey,
No luck. Very strange?!?
Hey Ben,
Sorry, I've been out all morning. Daft question, have you tried resaving the domains?
I've got a v7.5.11 instance running locally and not had an issue However! I do have an alternative solution. :)
Try this. Firstly get the
Request.Url.HostName
and get the domain via theDomainService
,And change GetRootContent to...
Thanks,
Jamie
Hey Jamie,
Thanks for the alternative solution!
I will give it a try. I did re-save the domain which did not have any affect. It is odd that the domain is not being picked up, I can use a method to get all domains and they appear in the returned list as expected!
Thanks again,
Ben
is working on a reply...