Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Ben Dickman 11 posts 83 karma points
    Apr 10, 2017 @ 13:37
    Ben Dickman
    0

    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:

    1. Navigate a url i.e. Site1.com/buy
    2. Check if the page exists in the current site
    3. 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.

    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

  • Jamie Pollock 172 posts 836 karma points c-trib
    Apr 10, 2017 @ 14:20
    Jamie Pollock
    0

    Hey Ben,
    Out of interest. What version of Umbraco are you running on?

    Thanks,
    Jamie

  • Ben Dickman 11 posts 83 karma points
    Apr 10, 2017 @ 14:25
    Ben Dickman
    0

    Hey Jamie,

    Sorry I should have included the version, 7.5.11.

    Thanks,

  • Jamie Pollock 172 posts 836 karma points c-trib
    Apr 10, 2017 @ 14:28
    Jamie Pollock
    0

    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.

  • Ben Dickman 11 posts 83 karma points
    Apr 10, 2017 @ 14:43
    Ben Dickman
    0

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

    This looks promising IContentFinder tutorial

    Thanks,

  • Jamie Pollock 172 posts 836 karma points c-trib
    Apr 10, 2017 @ 15:06
    Jamie Pollock
    0

    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.

    Thanks,
    Jamie

  • Ben Dickman 11 posts 83 karma points
    Apr 10, 2017 @ 15:18
    Ben Dickman
    1

    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.

  • Jamie Pollock 172 posts 836 karma points c-trib
    Apr 10, 2017 @ 15:21
    Jamie Pollock
    0

    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

  • Ben Dickman 11 posts 83 karma points
    Apr 10, 2017 @ 15:26
    Ben Dickman
    1

    Hey Jamie,

    I hope it works as well :-)

    I'll give you a shout if not.

    Thanks again,

  • Ben Dickman 11 posts 83 karma points
    Apr 11, 2017 @ 08:04
    Ben Dickman
    0

    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

  • Jamie Pollock 172 posts 836 karma points c-trib
    Apr 11, 2017 @ 11:36
    Jamie Pollock
    0

    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,
    Jamie

  • Ben Dickman 11 posts 83 karma points
    Apr 11, 2017 @ 13:27
    Ben Dickman
    0

    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

  • Jamie Pollock 172 posts 836 karma points c-trib
    Apr 11, 2017 @ 13:35
    Jamie Pollock
    0

    Hey Ben,
    That's odd. Have you tried republishing the XML cache? Not sure that would help but worth a try!

    Thanks,
    Jamie

  • Ben Dickman 11 posts 83 karma points
    Apr 11, 2017 @ 14:01
    Ben Dickman
    0

    Hey,

    No luck. Very strange?!?

  • Jamie Pollock 172 posts 836 karma points c-trib
    Apr 11, 2017 @ 17:16
    Jamie Pollock
    0

    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 the DomainService,

    var hostName = this.Request.Url.Host;
    var rootContent = GetRootContent(model, hostName);
    

    And change GetRootContent to...

    private IPublishedContent GetRootContent(RenderModel model, string hostName)
    {
        var domain = Services.DomainService.GetByName(hostName);
    
        if (domain != null)
        {
            if (model.Content.Site().Id != domain.RootContentId)
            {
                return Umbraco.TypedContent(domain.RootContentId);
            }
        }
    
        return model.Content.Site();
    }
    

    Thanks,
    Jamie

  • Ben Dickman 11 posts 83 karma points
    Apr 12, 2017 @ 09:50
    Ben Dickman
    0

    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

Please Sign in or register to post replies

Write your reply to:

Draft