Copied to clipboard

Flag this post as spam?

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


  • Ross Ekberg 124 posts 364 karma points
    May 08, 2020 @ 16:58
    Ross Ekberg
    0

    My goal is to create a multi site 404 handler. I see several tutorials on how to do this. The problem is that I come from a Web Forms background and am not super familiar with MVC. All the examples say to "create this" and then show code. Where do I save this code? What do I name the file? I feel like I am missing a very simple fundamental step.

    Here is the example I am looking at:

    https://our.umbraco.com/documentation/reference/routing/request-pipeline/icontentfinder

  • Amir Khan 1282 posts 2739 karma points
    May 08, 2020 @ 18:28
    Amir Khan
    0

    I don't have an answer for you, but as a primarily front end dev I often get lost there also.

  • Steve Morgan 1346 posts 4453 karma points c-trib
    May 11, 2020 @ 15:06
    Steve Morgan
    0

    Hi,

    The short(ish), not entirely technically correct but hopefully understandable, answer:

    There are two basic elements to an MVC site - compiled code and non-compiled "code". Non compiled includes, Views and your front end JS etc - you've found you can happily change this stuff.

    As a pure front ender you might be finding code examples which are on the compiled side. This means you can't just drop a .cs file (c#) into the website and it expect it to be picked up (something you might be used to with PHP or similar). It will need to be compiled and forms part of a DLL - this code is picked up when the website starts up. Usually you need to compile, build and release this code.

    Here's the good news - you can have code that should really be compiled picked up when the website starts (NOTE: use of this technique should be limited as it can get unwieldy quickly and has MANY downsides!!).

    Create a folder in the website directory called App_Code - now if you drop a .cs file in there the website will compile it at startup. This means also if you have some error in your syntax you will also break your website so be careful and don't do this development on a live site!

    In fact - if you're getting to this point it's time to start using Visual Studio and building releases, trust me the time in learning this stuff will pay off. This route means that you will be able know on build that you've not broken your site and you'll get lots of hints and tips from Intellisense (think of it as Autocomplete / Autocorrect).

    But for now..

    • Create a folder in your website called App_Code
    • Create a text file titled "CustomFourOhFour.cs"
    • Paste the below in.. (I've combined the two files you need from the Umbraco documentation into one for ease).
    • Check the site still starts up!
    • Create a doctype with the alias fourOhFourPageAlias for each site
  • Steve Morgan 1346 posts 4453 karma points c-trib
    May 11, 2020 @ 15:09
    Steve Morgan
    1
    using Umbraco.Core;
    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Core.Services;
    using Umbraco.Core.Composing;
    using Umbraco.Web;
    using Umbraco.Web.Routing;
    using System.Linq;
    
    namespace My.Website
    {
        [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
        public class SetLastChanceContentFindersComposer : IUserComposer
        {
            public void Compose(Composition composition)
            {
                //set the last chance content finder
                composition.SetContentLastChanceFinder<My404ContentFinder>();
            }
        }
    
        public class My404ContentFinder : IContentLastChanceFinder
        {
            private readonly IDomainService _domainService;
            public My404ContentFinder(IDomainService domainService)
            {
                _domainService = domainService;
            }
            public bool TryFindContent(PublishedRequest contentRequest)
            {
                //find the root node with a matching domain to the incoming request
                var url = contentRequest.Uri.ToString();
                var allDomains = _domainService.GetAll(true);
                var domain = allDomains?.Where(f => f.DomainName == contentRequest.Uri.Authority || f.DomainName == "https://" + contentRequest.Uri.Authority).FirstOrDefault();
                var siteId = domain != null ? domain.RootContentId : (allDomains.Any() ? allDomains.FirstOrDefault().RootContentId : null);
                var siteRoot = contentRequest.UmbracoContext.Content.GetById(false, siteId ?? -1);
                if (siteRoot == null) { siteRoot = contentRequest.UmbracoContext.Content.GetAtRoot().FirstOrDefault(); }
                if (siteRoot == null)
                {
                    return false;
                }
                //assuming the 404 page is in the root of the language site with alias fourOhFourPageAlias
                IPublishedContent notFoundNode = siteRoot.Children.FirstOrDefault(f => f.ContentType.Alias == "fourOhFourPageAlias");
    
    
                if (notFoundNode != null)
                {
                    contentRequest.PublishedContent = notFoundNode;
                }
                // return true or false depending on whether our custom 404 page was found
                return contentRequest.PublishedContent != null;
            }
        }
    }
    
  • Ross Ekberg 124 posts 364 karma points
    May 11, 2020 @ 16:58
    Ross Ekberg
    0

    Wow, thank you for this. I will give it a go here shortly.

  • Osman Coskun 164 posts 398 karma points
    Oct 26, 2023 @ 00:46
    Osman Coskun
    0

    Thank you Steve.

  • Ross Ekberg 124 posts 364 karma points
    May 11, 2020 @ 17:59
    Ross Ekberg
    0

    This seems to require the Umbraco.Core.Composing class, which doesn't seem to be available in v7.

  • Ross Ekberg 124 posts 364 karma points
    May 11, 2020 @ 21:06
    Ross Ekberg
    0

    It seems the example you provided is meant for v8. I am running v7. I did find this example:

    https://24days.in/umbraco-cms/2014/the-double-album/multi-site-404/

    but I don't know how to combine them like you did. I get that you have to create a icontenthandler and then have to register it.

  • Steve Morgan 1346 posts 4453 karma points c-trib
    May 13, 2020 @ 10:12
    Steve Morgan
    0

    Comment deleted - found a bug!

  • Steve Morgan 1346 posts 4453 karma points c-trib
    May 13, 2020 @ 10:20
    Steve Morgan
    101

    Hi,

    I did a quick bit of hacking around in a v7 version. This will work matching on the domain in the URL - if you're doing the same domain with different cultures you'll need to amend it based on the example in the documentation.

    It assumes your home nodes are at the root.

    But I like this solution as I hope it's more readable and easy to understand.

    Be sure to change the fourOhFour alias to match your doctype (ensure you get the case correct too).

    using System;
    using System.Linq;
    using System.Globalization;
    using Umbraco.Core;
    using Umbraco.Core.Models;
    using Umbraco.Web;
    using Umbraco.Web.Routing;
    using System.Web;
    
    namespace FourOhFour
    {
        public class MyApplication : ApplicationEventHandler
        {
            protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
            {
                // Add our custom 404 content finder - by using a last chance finder it will return 404 response code correctly
                ContentLastChanceFinderResolver.Current.SetFinder(new FourOhFourContentFinder());
            }
        }
    
        public class FourOhFourContentFinder : IContentFinder
        {
            public bool TryFindContent(PublishedContentRequest contentRequest)
            {
                var allRoots = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetAtRoot();
                var domain = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
                var rootNode = allRoots.Where(f => f.UrlWithDomain().StartsWith(domain)).FirstOrDefault();
    
                if (rootNode != null)
                {
                    // logic to find your 404 page and set it to contentRequest.PublishedContent
                    // replace 'fourOhFour' with the alias of your 404 page
                    IPublishedContent notFoundNode = rootNode.Descendants().Where(x => x.DocumentTypeAlias == "fourOhFour").FirstOrDefault();
    
                    if (notFoundNode != null)
                    {
                        contentRequest.PublishedContent = notFoundNode;
                    }
                }
    
                return contentRequest.PublishedContent != null;
            }
        }
    }
    
  • Ross Ekberg 124 posts 364 karma points
    May 20, 2020 @ 20:52
    Ross Ekberg
    1

    Thank you so much! I have been looking for this solution for a while. I had to tweak the code a bit to get it to work on my installation, but not much. I marked your post as the answer. Below is the final result.

    using System;
    using System.Linq;
    using System.Globalization;
    using Umbraco.Core;
    using Umbraco.Core.Models;
    using Umbraco.Web;
    using Umbraco.Web.Routing;
    using System.Web;
    
    namespace FourOhFour
    {
        public class MyApplication : ApplicationEventHandler
        {
            protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
            {
                // Add custom 404 content finder. Use last chance finder so it will return 404 response code correctly...
                ContentLastChanceFinderResolver.Current.SetFinder(new FourOhFourContentFinder());
            }
        }
    
        public class FourOhFourContentFinder : IContentFinder
        {
            public bool TryFindContent(PublishedContentRequest contentRequest)
            {
            //Get all root sites...
                var allRoots = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetAtRoot();
    
            //Get current site domain...
                var domain = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
    
            //Get the root node of current site...
                var rootNode = allRoots.Where(f => f.UrlWithDomain().StartsWith(domain)).FirstOrDefault();
    
                if (rootNode != null)
                {
                    //Get the site's specific 404 page.  Must be called '404 Error Page'...
            IPublishedContent notFoundNode = rootNode.Descendants().Where(x => x.Name == "404 Error Page").FirstOrDefault();
    
                    if (notFoundNode != null)
                    {
                        contentRequest.PublishedContent = notFoundNode;
                    } else {
                //If nothing is found, put in the main site's 404 page...
                contentRequest.PublishedContent = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetById(6008);
            }
                }
    
                return contentRequest.PublishedContent != null;
            }
        }
    }
    
  • Ross Ekberg 124 posts 364 karma points
    May 27, 2020 @ 19:31
    Ross Ekberg
    0

    Well, it's not working now. It works in my test installation, but not on production. I don't know what is different between the two installs.

    What is happening is basically it's acting as if I have added no code. It does nothing. No errors, just the ugly Umbraco 404 page, which is what you get when you don't set a default 404 page in the config.

    Does anyone have any ideas? I tried replacing this:

    ContentLastChanceFinderResolver.Current.SetFinder(new FourOhFourContentFinder()); 
    

    With this:

    ContentFinderResolver.Current.InsertTypeBefore<ContentFinderByNotFoundHandlers, FourOhFourContentFinder>();
    

    And nothing changed.

  • Ross Ekberg 124 posts 364 karma points
    May 28, 2020 @ 18:46
    Ross Ekberg
    0

    Well, it now seems to be working. I think it was a caching issue. Thanks again to Steve for helping me solve this one!

  • Steve Morgan 1346 posts 4453 karma points c-trib
    May 29, 2020 @ 08:14
    Steve Morgan
    0

    I suspect the site needed a restart to pick up the new code.

    Touching the web.config or restarting the server would have done this. This happens automatically every few hours too so that might explain why it suddenly started working.

    HTH

    Steve

Please Sign in or register to post replies

Write your reply to:

Draft