Injection UmbracoHelper breaks API routes containing dot character, UmbracoContext related?

I am running Umbraco 13.7.2 for Umbraco Cloud.

I have a custom controller:

TestController.cs:

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Web.Common;

namespace My.Controllers
{
  public class TestController : Controller
  {
    public TestController()
    {
    }

    [HttpGet("testcontroller/hello")]
    public IActionResult Test(string filename)
    {
      return Ok("hello");
    }

    [HttpGet("testcontroller/hello.pdf")]
    public IActionResult TestEnding(string filename)
    {
      return Ok("hello pdf");
    }
}

This works great!

Request to /testcontroller/hello:

hello

Request to /testcontroller/hello.pdf:

hello

However, if I simply inject UmbracoHelper. Constructor changed to:

public TestController(UmbracoHelper _umbracoHelper)

then the results turn into:

Request to /testcontroller/hello:

hello

Request to /testcontroller/hello.pdf:
Results in error 500 with the following trace:

[14:14:39 ERR] An unhandled exception has occurred while executing the request.
System.InvalidOperationException: Wasn't able to get an UmbracoContext
   at Umbraco.Extensions.UmbracoContextAccessorExtensions.GetRequiredUmbracoContext(IUmbracoContextAccessor umbracoContextAccessor)
   at Umbraco.Cms.Infrastructure.DependencyInjection.UmbracoBuilderExtensions.<>c.<AddCoreInitialServices>b__0_10(IServiceProvider factory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at lambda_method40(Closure, IServiceProvider, Object[])
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
2
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Umbraco.Cms.Web.Common.Middleware.BasicAuthenticationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Cms.Web.BackOffice.Middleware.BackOfficeExternalLoginProviderErrorMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Cloud.Cms.PublicAccess.Middleware.CloudPublicAccessMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Deploy.Infrastructure.Middleware.BearerTokenAuthenticationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
2
   at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at StackExchange.Profiling.MiniProfilerMiddleware.Invoke(HttpContext context) in C:\projects\dotnet\src\MiniProfiler.AspNetCore\MiniProfilerMiddleware.cs:line 112
   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Cms.Web.Common.Middleware.PreviewAuthenticationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestLoggingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext httpContext, Boolean retry)
   at Umbraco.Forms.Web.HttpModules.ProtectFormUploadRequestsMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Any idea what I am doing wrong? Can I fix this somehow?
I have an important set of url’s that I need to build custom API handling that I cannot change, and these include paths that have file endings that I have to act upon.

Could doing something with umbraco contexts help?

Please help me Obi-Wans, you are my only hope.

You’re not in any kind of Umbraco Context, Umbraco doesn’t know this route so it doesn’t know how to help you get a context. You’ll need to construct a context.

Have a look at how I do it in a Hangfire context (same principle):

From what I can understand from your article, it shows the context factory ensuring an umbraco context, and I tried this before, in this manner with the same result:

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Web.Common;

namespace My.Controllers
{
  public class TestController : Controller
  {
    private readonly IUmbracoContextFactory _umbracoContextFactory;
       
    public TestController(IUmbracoContextFactory umbracoContextFactory, UmbracoHelper umbracoHelper)
    {
      using var _ = umbracoContextFactory.EnsureUmbracoContext();
      _umbracoContextFactory = umbracoContextFactory;
    }

    [HttpGet("testcontroller/hello")]
    public IActionResult Test(string filename)
    {
      using var _ = _umbracoContextFactory.EnsureUmbracoContext();
      return Ok("hello");
    }

    [HttpGet("testcontroller/hello.pdf")]
    public IActionResult TestEnding(string filename)
    {
      using var _ = _umbracoContextFactory.EnsureUmbracoContext();
      return Ok("hello pdf");
    }
  }
}

I tried ensuring context in the constructor and in the actions respectively, also individually and not “everywhere” which I illustrate here.

Did I understand your example correctly?

I think I’m just missing some basic C# gotcha maybe?
It’s just so weird to me that it ONLY happens when requesting something that ends with .something.

Should I perhaps extract the injection of UmbracoHelper to a service, and then only use the service after ensuring Umbraco context?
Though in that case, I wouldn’t be able to inject THAT service in turn, since it happens before any action code (with context-ensuring).

Never mind, I was just dense in reading the example, the key being require the service via the ServiceProvider after you’ve ensured a context.

Thank you Sebastiaan!

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.