Copied to clipboard

Flag this post as spam?

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


  • Roy Berris 89 posts 577 karma points c-trib
    Jun 24, 2022 @ 13:24
    Roy Berris
    2

    Exception page served from Umbraco in V9/V10

    Hi, this is not a question. But I wanted to share a solution for a exception page that is served from Umbraco. See the solution below. Note that the exception page itself should probably be stripped of any header / footer or components because that is most likely where the exception triggered in the first place.

  • Roy Berris 89 posts 577 karma points c-trib
    Jun 24, 2022 @ 13:33
    Roy Berris
    103

    Because Umbraco doesn't really support a way to serve domain specific and language specific error pages out of the box. This middleware will catch any exceptions outside of the Umbraco domain (read: URL's that do not start with /umbraco/). And redirect the user to a error page that is in the CMS.

    You'll still need to set-up the doctype and views yourself for this page. This is just the routing logic.

    Create a new class in a folder called Middleware.

    public class ExceptionMiddleware
    {
        private readonly IUmbracoContextFactory _umbracoContextFactory;
        private readonly IVariationContextAccessor _variationContextAccessor;
        private readonly IDomainService _domainService;
        private readonly RequestDelegate _next;
    
        public ExceptionMiddleware(
            IUmbracoContextFactory umbracoContextFactory,
            IVariationContextAccessor variationContextAccessor,
            IDomainService domainService,
            RequestDelegate next)
        {
            _umbracoContextFactory = umbracoContextFactory;
            _variationContextAccessor = variationContextAccessor;
            _domainService = domainService;
            _next = next;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception)
            {
                HandleException(context);
            }
        }
    
        private void HandleException(HttpContext httpContext)
        {
            var domain = _domainService.GetByUri(httpContext.Request.Host.Value);
    
            if (domain is null)
            {
                return;
            }
    
            if (domain.RootContentId is not int siteId)
            {
                return;
            }
    
            _variationContextAccessor.VariationContext ??= new VariationContext(domain.LanguageIsoCode);
    
            var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext();
    
            if (umbracoContextReference?.UmbracoContext is not UmbracoContext umbracoContext
                || umbracoContext.Content is null)
            {
                return;
            }
    
            var siteRoot = umbracoContext.Content.GetById(false, siteId);
            var errorNode = siteRoot?.Children?.FirstOrDefault(i => i.ContentType.Alias == ErrorPage.ModelTypeAlias);
    
            if (errorNode is null)
            {
                return;
            }
    
            httpContext.Response.Redirect(errorNode.Url(domain.LanguageIsoCode));
        }
    }
    

    This middleware will capture any exceptions that occur in the request pipeline. And redirects the user to the error page.

    This will get the root content for the current request. This is needed when you have multiple root nodes, when serving multiple websites from the same Umbraco instance.

    Because middleware is outside of the scope of the Umbraco Context. We'll need to ensure a context with the context factory. One downside to this is that within the scope it does not know the variant to set for this context (for multi-lingual sites). Because of this we'll have to set it when it is null, to the domains culture.

    ErrorPage.ModelTypeAlias is referring to the modelsbuilder created ErrorPage class. You can replace this with a string, but to keep things safe I prefer not to use magic strings. When changing the alias in the future, you'll get notified that the ErrorPage class does not exist anymore. Make sure you replace this with the right modelsbuilder type for your error page.

    The IDomainService.GetByUri is an extension method I created, because we use this on multiple places. Feel free to copy it, or to replace the code in the middleware with the logic below:

    public static IDomain? GetByUri(this IDomainService service, string authority)
    {
        var domains = service.GetAll(true);
    
        return domains?.FirstOrDefault(f => f.DomainName == authority
            || f.DomainName == $"https://{authority}"
            || f.DomainName == $"http://{authority}");
    }
    

    Now we'll need to add the middleware to the request pipeline. For development I want to serve the ASP.NET developer exception page.

    In Startup.Configure (or when using minimal program, the IApplicationBuilder). Place the following code:

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseWhen(i => !i.Request.IsUmbracoDomain(), pipeline =>
        {
            pipeline.UseMiddleware<ExceptionMiddleware>();
        });
    }
    

    Just to keep things clean the IsUmbracoDomain on the HttpRequest is also an extension method:

    public static bool IsUmbracoDomain(this HttpRequest request)
    {
        if (!request.Path.HasValue)
        {
            return false;
        }
    
        return request.Path.Value.ToLower().StartsWith("/umbraco/");
    }
    

    Hope this helps someone!

  • B. Gunnarsson (Bryns) 25 posts 180 karma points c-trib
    Jun 24, 2022 @ 16:06
    B. Gunnarsson (Bryns)
    1

    This is fantastic! Thanks for sharing <3

  • Johan Runsten 38 posts 277 karma points c-trib
    Jun 27, 2022 @ 06:57
    Johan Runsten
    1

    Hi!

    Thanks for sharing! Here's how I ususally do it, call me old fashioned but I always made the error page a static page outside any backend pipelines just to be sure it's not caused by any errors in the CMS which would cause the error page to not show as well (and cause other problems).

    app.UseExceptionHandler("/error.html");

    Just an alternative way :)

  • Roy Berris 89 posts 577 karma points c-trib
    Jun 27, 2022 @ 07:02
    Roy Berris
    0

    Probably better (and easier) for most people. In my set-up, the error page URL changes based on language. So I had to get a bit fancy :D

Please Sign in or register to post replies

Write your reply to:

Draft