Copied to clipboard

Flag this post as spam?

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


  • Saied 349 posts 674 karma points
    Jul 22, 2016 @ 23:48
    Saied
    0

    Different 404 page per website?

    Hi,

    I have 3 websites under one umbraco 7.4.3 instance. I can set the id of a page in umbracosettings.config, but all sites go to this one page, which is only defined on one site. Is there a way, I can have a different 404 per website?

    I can't use a package because I have a redirect service that redirects old urls to new urls and all the packages I have tried so far such as Url Tracker and Page Not Found Manager seem to supersede this and it breaks the redirect service.

    Thanks, Saied

  • Marc Goodson 2141 posts 14324 karma points MVP 8x c-trib
    Jul 23, 2016 @ 00:15
    Marc Goodson
    0

    Hi Saied

    When a request is made via Umbraco, it tries to match the request to a particular content node.

    Usually it maps by the name of the content item, matching the url, but it can also map via the name of UmbracoUrlAlias, or or via template alias or via the node id.

    and Umbraco is flexible enough for you to define your own rules to make these mappings.

    you do this by implementing an IContentFinder, and adding it to the list of the current ContentFinderResolvers (that do the usual out of the box mappings on name etc)

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

    There is also an option to register a 'last chance finder' that will return a proper 404 code, and here you could write your logic (depending on the domain of the request) to return the site appropriate 404 page.

    So a lot depends on how your redirect service is configured to work. Ideally it too should be a Custom IContentFinder that is the 'last' ContentFinderResolver in the list (but not the Last Chance Resolver!) then if a request comes into Umbraco, and can't be matched to an existing content item, this 'redirect IContentFinder' would check if it's an old url, and look up the appropriate redirect, and issue the 301, if the request didn't match an old url, it would fall through to your custom 'last chance finder' that this would then return the appropriate 404.

    You can see an example of a LastChance IContentFinder in the source for Page Not Found package:

    https://github.com/TimGeyssens/UmbracoPageNotFoundManager/blob/master/PageNotFoundManager/PageNotFoundContentFinder.cs

    and an example of a 301 IContentFinder can be found in the source of Umbraco for the new 301 tracking functionality in 7.5:

    https://github.com/umbraco/Umbraco-CMS/blob/82954b6b6f303d6ae2d704e4d606840ada841a57/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs

    if that helps!

    regards

    Marc

  • Saied 349 posts 674 karma points
    Jul 23, 2016 @ 01:40
    Saied
    0

    Hi Marc,

    The way the redirect service works is that when a page is requested, before the page is returned, the url is looked up in the database. This might be something like www.example.com\abc. If it finds this and there is a new url to redirect to, it will redirect to that url and not display the umbraco 404.

    If I understand you correctly, are you saying that I should let the redirect service code run, but if it does not find, then I should let umbraco redirect to a proper 404 page using the TryFindContent.

    I am still a bit confused on how TryFindContent works. Once I implement it, how would I get it to redirect to a 404 page?

    Thanks for the help, Saied

  • Saied 349 posts 674 karma points
    Jul 23, 2016 @ 02:06
    Saied
    0

    Marc,

    Interestingly, I was just looking at the code that handles the redirects from old urls to new urls and it does use the TryContentFinder. The ApplicationStarting event has this line:

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

    How can I modify NotFoundContentFinder to redirect to a custom 404 page( a node that I would create under the root of each site for example) if it does not find an entry in the database. Here is the code:

        public class NotFoundContentFinder : IContentFinder
    {
        private static Guid SystemUserId = new Guid("a23d1aec-b91a-4fa8-8e9e-f65966c68389");
    
        public bool TryFindContent(PublishedContentRequest contentRequest)
        {
            if (!contentRequest.Is404)
            {
                return false;
            }
    
            var domainName = HttpContext.Current.Request.Url.Authority;
            var slug = HttpContext.Current.Request.Path;
    
            if (slug.StartsWith("/error.aspx", StringComparison.OrdinalIgnoreCase))
            {
                slug = HttpUtility.UrlDecode(HttpContext.Current.Request.QueryString.ToString()).Replace("404;", "");
                slug = slug.Substring(slug.IndexOf(domainName) + domainName.Length);
            }
            if (slug.StartsWith(":80", StringComparison.OrdinalIgnoreCase))
            {
                slug = slug.Substring(3);
            }
            if (slug.StartsWith(":443", StringComparison.OrdinalIgnoreCase))
            {
                slug = slug.Substring(4);
            }
    
            var redirectSet = RedirectFromLookup(domainName, slug) || RedirectSctFiles(domainName, slug) || RedirectBullydogFiles(domainName, slug);
            if (redirectSet)
            {
                HttpContext.Current.Server.ClearError();
            }
            return redirectSet;
        }
    
        private static bool RedirectFromLookup(string domainName, string slug)
        {
            Redirect redirect;
            try
            {
                var svc = new RedirectService();
                redirect = svc.Add(new Redirect
                {
                    Slug = slug,
                    SiteName = domainName,
                    IsActive = true,
                    RedirectType = RedirectType.Permanent
                }, SystemUserId);
            }
            catch (Exception )
            {
                //TODO: log ex
                return false;
            }
    
            if (redirect == null || redirect.RedirectType == null || string.IsNullOrWhiteSpace(redirect.TargetUrl))
            {
                return false;
            }
    
            switch (redirect.RedirectType.Value)
            {
                case RedirectType.Permanent:
                    HttpContext.Current.Response.RedirectPermanent(redirect.TargetUrl);
                    HttpContext.Current.Response.End();
                    break;
                case RedirectType.Temporary:
                    HttpContext.Current.Response.Redirect(redirect.TargetUrl);
                    HttpContext.Current.Response.End();
                    break;
            }
            return true;
        }
    
        private static bool RedirectSctFiles(string domainName, string slug)
        {
            if (domainName.Contains("sctflash.com") && slug.StartsWith("/sctfiles/", StringComparison.OrdinalIgnoreCase))
            {
                slug = "/sctfiles/" + slug.Substring(10);
                HttpContext.Current.Response.RedirectPermanent("http://cdn.derivesystems.com" + SlugWithRandomQueryString(slug));
                HttpContext.Current.Response.End();
                return true;
            }
            return false;
        }
    
        private static bool RedirectBullydogFiles(string domainName, string slug)
        {
            if (domainName.Contains("bullydog.com") && slug.StartsWith("/updateagent/", StringComparison.OrdinalIgnoreCase))
            {
                HttpContext.Current.Response.RedirectPermanent("http://dv-prod-wamp01.cloudapp.net" + SlugWithRandomQueryString(slug));
                HttpContext.Current.Response.End();
                return true;
            }
            return false;
        }
    
        private static string SlugWithRandomQueryString(string slug)
        {
            if (string.IsNullOrWhiteSpace(slug))
            {
                return slug;
            }
            var parts = slug.Split("?".ToCharArray(), StringSplitOptions.None);
            if (parts.Length == 0)
            {
                return slug;
            }
            return string.Format("{0}?{1}", parts[0], Guid.NewGuid());
        }
    }
    

    UPDATE:

    Marc,

    After looking at it some more, I thought if the redirectSet returns false, meaning it did not find an entry in the database or lookup, I could redirect to a not-found page that I create in my content structure on my root. What do you think?

      if (redirectSet)
                {
                    HttpContext.Current.Server.ClearError();
                }
                else
                {
                    HttpContext.Current.Response.RedirectPermanent("/not-found", true);
                }
    

    My only issue is what if the page changes name, like from not-found to uh-oh. I guess I could store the 404 page in a appsetting or is there another option?

  • Marc Goodson 2141 posts 14324 karma points MVP 8x c-trib
    Jul 23, 2016 @ 08:33
    Marc Goodson
    0

    Hi Saied

    Yes so your existing redirect ContentFinder is registerred as the 'Last Chance' Finder Resolver - and this is why the Umbraco Page Not Found package breaks it, because it's 404 handler becomes the 'Last Chance' handler and removes the existing redirect one!!!

    I think the neatest way of getting this working is to register your existing ContentFinder that does the redirect logic as the the last resolver in the pipeline, but not the 'Last Chance' one.

    And then install the Page Not Found package, which will register the Page Not Found handler as the 'Last Chance' handler- this gives you the nice interface inside Umbraco to set the 404 page for the domain.

    Then when a request comes in, Umbraco will try to match it to a node, executing each IContentFinder in order, the last one will be your custom redirect one, if a custom redirect doesn't exist it will fall through to the 'last chance' Content Finder from the package and serve the correct 404.

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

    and

    from the package:

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

    but yes from what you are saying you've got the gist of it, but you'll just end up writing the Page Not Found package :-) if you carry on making the existing single content finder do both jobs!!!

  • Saied 349 posts 674 karma points
    Jul 23, 2016 @ 16:49
    Saied
    0

    Hi Marc,

    I did the following steps and was wondering if it was correct. I put the following in the ApplicationStarting event:

    ContentFinderResolver.Current.InsertTypeBefore<ContentFinderByNotFoundHandlers, NotFoundContentFinder>();
    
    ContentLastChanceFinderResolver.Current.SetFinder(new PageNotFoundContentFinder());
    

    If I understand the above correctly, does the InsertTypeBefore method insert the NotFoundContentFinder before the PageNotFoundContentFinder so that runs first. If the NotFoundContentFinder does not return true, it then goes to the PageNotFoundContentFinder. Is this correct?

    Another question I had was is there a default content finder? What I mean is, it looks like what is happening is:

    1. Run the custom redirect first. If redirect found, return.
    2. If page exists in website (not 404), go to it - where does this come in (is there a default for this?)
    3. if no redirect found and 404 is thrown, run the Last Chance finder.

    No. 2 is what is confusing. I know that no matter what page I go to, t will always run the code for number one first. So if it does not find a redirect and the page exists, I was just wondering if there was a default content finder that runs next that takes the user to the page if it exists.

    Have you had any luck running the Page Not Found Manager along the 301 Url Tracker. Now that you have helped me get this working, it would be nice to use the 301 Url Tracker to be able to setup redirects through Umbraco, but I am not sure if that would break the redirect one. Also, I wouldn't want the 301 Url Tracker to handle 404s because I have the Page Not Found Manager for that.

    Thanks for the great help, Saied

  • Marc Goodson 2141 posts 14324 karma points MVP 8x c-trib
    Jul 25, 2016 @ 23:09
    Marc Goodson
    0

    Hi Saied

    Yes there are a number of default IContentFinders that ship with Umbraco, these form the 'Umbraco pipeline' because each one is executed one after another until a piece of content is found.

    You can see the code here:

    https://github.com/umbraco/Umbraco-CMS/blob/82954b6b6f303d6ae2d704e4d606840ada841a57/src/Umbraco.Web/WebBootManager.cs

    ContentFinderResolver.Current = new ContentFinderResolver(
                    ServiceProvider, LoggerResolver.Current.Logger,
                    // all built-in finders in the correct order, devs can then modify this list
                    // on application startup via an application event handler.
                    typeof(ContentFinderByPageIdQuery),
                    typeof(ContentFinderByNiceUrl),
                    typeof(ContentFinderByIdPath),
    
                    // these will be handled by ContentFinderByNotFoundHandlers so they can be enabled/disabled
                    // via the config file... soon as we get rid of INotFoundHandler support, we must enable
                    // them here.
                    //typeof (ContentFinderByNiceUrlAndTemplate),
                    //typeof (ContentFinderByProfile),
                    //typeof (ContentFinderByUrlAlias),
    
                    // note: that one should run *after* NiceUrlAndTemplate, UrlAlias... but at the moment
                    // it cannot be done - just make sure to do it properly in v8!
                    typeof(ContentFinderByRedirectUrl),
    
                    // implement INotFoundHandler support... remove once we get rid of it
                    typeof(ContentFinderByNotFoundHandlers)
                );
    

    You can see the last one in the default list is 'ContentFinderByNotFoundHandlers' and we inserted your custom redirect one before this.

    But before your Redirect are other finders, eg ContentFinderByNiceUrl, that will find content for existing Umbraco Content, before it gets a chance to fall through to your redirect, if that makes sense ?

Please Sign in or register to post replies

Write your reply to:

Draft