A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/umbraco/Umbraco-CMS/issues/12083 below:

Error mapping a Custom MVC Route to a 'Client Request' - yes I know

Which exact Umbraco version are you using? For example: 9.0.1 - don't just write v9

v9.3.1

Bug summary

I think this might be a bug but also I might be being stupid, so bear with me.

I'm using Custom MVC Routing to map a route to a custom Controller inheriting from UmbracoPageController and implementing IVirtualPageController.

Now I'm mapping this route to a file: sitemap.xml

And I'm fully aware that this .xml extensions means that Umbraco considers this to be a 'client-side' request and so doesn't make an UmbracoContext available via the IUmbracoContextAccessor.

There is a note to such an effect in the documentation on this subject:

So in my FindContent implementation, I'm using IUmbracoContextFactory to get hold of an UmbracoContext to find the IPublishedContent that I wish to associate with my sitemap routing....

... and it works! - huzzaah, I can associate the homepage IPublishedContent item with the sitemap.xml route.. I can see it assigned, on a breakpoint...

but... but... boom!

The white screen of death

And the rather interesting error message

"Wasn't able to get an UmbracoContext"

But we had one! I saw it, I got the Homepage with it... what is going wrong???

(if I map the route without the .xml, to just /sitemap - all is fine - just thought I'd add that....)

So looking at the stack trace - you can see this is thrown in the Core of Umbraco inside a method called 'SetUmbracoRouteValues'

and if we have a look in the codebase you can see that's called in the UmbracoVirtualPageFilterAttribute:

public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)

In OnActionExecutingAsync for this attribute

The attribute first tries to see if the route has been mapped via 'ForUmbracoRoute()' method

and if not checks to see if the controller implements IVirtualPageController

In both cases it then calls SetUmbracoRouteValues, passing in the ActionExecutingContext and the method used to find the IPublishedContent for the route...

All as expected...

But here's the snag inside the SetUmbracoRouteValues method:

private async Task SetUmbracoRouteValues(ActionExecutingContext context, IPublishedContent content)

Although we have obtained the IPublishedContent for the route - we want to create a PublishedRequestBuilder object in order to be able to 'set' the PublishedContent we have onto the request.... and THIS needs an UmbracoContext in order to to be able to supply a 'CleanedUmbracoUrl' value... a property of UmbracoContext which calls a method on the UriUtility class lazily to retrieve it.

The UmbracoContext isn't used for anything else.

The problem, I think, is the way the UmbracoContext is accessed within SetUmbracoRouteValues, the implementation uses
IUmbracoContextAccessor like so:

var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();

and if we look at the GetRequiredUmbracoContext() implementation:

public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor umbracoContextAccessor)

It tries to get the UmbracoContext from the accessor, and if it can't find one (which it won't it's a client request remember!) then it 'throws':

throw new InvalidOperationException("Wasn't able to get an UmbracoContext");
Which is our magical error message we are experiencing!

Am I doing something wrong?

I'll put how to reproduce below.

How to fix??

Naively, and remember I might be stupid - I wonder if it would be possible to just inject the UriUtility class into the 'UmbracoVirtualPageFilterAttribute' - to call UriToUmbraco on the HttpContext.Request.GetEncodedUrl() .... would that work? is that allowed? or does it need to be called via the UmbracoContext.... if that were possible it would mean you don't need to try to obtain an UmbracoContext during the SetUmbracoRoutesValue method...

... or if it needs to be called via an UmbracoContext - then I think SetUmbracoRoutesValue might need to face up to the fact that on a client side request, there is no UmbracoContext - and it would need to be prepared to use the IUmbracoContextFactory to retrieve one!

Specifics

Is the above not specific enough? :-)

Steps to reproduce

Create a new instance of Umbraco using the very latest version 9.3.1.

Install the legendary Umbraco Starter Kit to give you some content of some form.

In your IDE of preferred choice, update your Startup.cs file to 'map a custom route' to sitemap.xml:

   public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseUmbraco()
                .WithMiddleware(u =>
                {
                    u.UseBackOffice();
                    u.UseWebsite();
                })
                .WithEndpoints(u =>
                {
                    u.EndpointRouteBuilder.MapControllerRoute(
              "Sitemap Controller",
              "/sitemap.xml",
              new { Controller = "SiteMap", Action = "Index" });
                    u.UseInstallerEndpoints();
                    u.UseBackOfficeEndpoints();
                    u.UseWebsiteEndpoints();
                });
        }

Then create your SiteMap Controller like so:

public class SiteMapController : UmbracoPageController, IVirtualPageController
    {
        public SiteMapController(
            ILogger<UmbracoPageController> logger,
            ICompositeViewEngine compositeViewEngine)
            : base(logger, compositeViewEngine)
        { }

        [HttpGet]
        public IActionResult Index()
        {  
            return Content(CurrentPage.Name);
        }


        public IPublishedContent FindContent(ActionExecutingContext actionExecutingContext)
        {
            var _umbracoContextFactory = actionExecutingContext.HttpContext.RequestServices.GetRequiredService<IUmbracoContextFactory>();
            IPublishedContent content = default(IPublishedContent);
            using (UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext())
            {
                var umbracoContext = umbracoContextReference.UmbracoContext;
                content = umbracoContext.Content.GetAtRoot().FirstOrDefault();
            }
            return content;
        }
    }

and then run your site and visit /sitemap.xml

if you are debugging, you should hit the breakpoint inside your SiteMap controller, if you pop one inside FindContent, and you'll see the content variable gets content successfully, and then you'll experience the disappointment of the total white out...

Expected result / actual result

I'd expect it to write out the 'name' of the homepage (or sitemap if I'd implemented it)

but instead, we get the snowstorm of the .net core white error page:


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4