diff --git a/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs b/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs
new file mode 100644
index 0000000000..3b5bf7aebb
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Umbraco.Cms.Core.DependencyInjection
+{
+ ///
+ /// Provides access to a request scoped service provider when available for cases where
+ /// IHttpContextAccessor is not available. e.g. No reference to AspNetCore.Http in core.
+ ///
+ public interface IScopedServiceProvider
+ {
+ ///
+ /// Gets a request scoped service provider when available.
+ ///
+ ///
+ /// Can be null.
+ ///
+ IServiceProvider ServiceProvider { get; }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 1ad30c6e8e..6385e3431d 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -166,7 +166,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddScoped();
builder.Services.AddSingleton();
- builder.Services.AddSingleton();
+ builder.Services.AddSingleton(sp =>
+ new PublishedContentQueryAccessor(sp.GetRequiredService())
+ );
builder.Services.AddScoped(factory =>
{
var umbCtx = factory.GetRequiredService();
diff --git a/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs b/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs
index 68a15580e4..00adc5018b 100644
--- a/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs
+++ b/src/Umbraco.Infrastructure/IPublishedContentQueryAccessor.cs
@@ -1,7 +1,18 @@
-using Umbraco.Cms.Infrastructure;
-
namespace Umbraco.Cms.Core
{
+ ///
+ /// Not intended for use in background threads where you should make use of
+ /// and instead resolve IPublishedContentQuery from a
+ /// e.g. using
+ ///
+ ///
+ /// // Background thread example
+ /// using UmbracoContextReference _ = _umbracoContextFactory.EnsureUmbracoContext();
+ /// using IServiceScope serviceScope = _serviceProvider.CreateScope();
+ /// IPublishedContentQuery query = serviceScope.ServiceProvider.GetRequiredService<IPublishedContentQuery>();
+ ///
+ ///
+ ///
public interface IPublishedContentQueryAccessor
{
bool TryGetValue(out IPublishedContentQuery publishedContentQuery);
diff --git a/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs b/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs
index 9f716365a0..d61900df95 100644
--- a/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs
+++ b/src/Umbraco.Infrastructure/PublishedContentQueryAccessor.cs
@@ -1,17 +1,21 @@
using System;
using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DependencyInjection;
namespace Umbraco.Cms.Core
{
public class PublishedContentQueryAccessor : IPublishedContentQueryAccessor
{
- private readonly IServiceProvider _serviceProvider;
+ private readonly IScopedServiceProvider _scopedServiceProvider;
- public PublishedContentQueryAccessor(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
+ [Obsolete("Please use alternative constructor")]
+ public PublishedContentQueryAccessor(IServiceProvider serviceProvider) => _scopedServiceProvider = serviceProvider.GetRequiredService();
+
+ public PublishedContentQueryAccessor(IScopedServiceProvider scopedServiceProvider) => _scopedServiceProvider = scopedServiceProvider;
public bool TryGetValue(out IPublishedContentQuery publishedContentQuery)
{
- publishedContentQuery = _serviceProvider.GetRequiredService();
+ publishedContentQuery = _scopedServiceProvider.ServiceProvider?.GetService();
return publishedContentQuery is not null;
}
diff --git a/src/Umbraco.Web.Common/DependencyInjection/ScopedServiceProvider.cs b/src/Umbraco.Web.Common/DependencyInjection/ScopedServiceProvider.cs
new file mode 100644
index 0000000000..de660184f1
--- /dev/null
+++ b/src/Umbraco.Web.Common/DependencyInjection/ScopedServiceProvider.cs
@@ -0,0 +1,17 @@
+using System;
+using Microsoft.AspNetCore.Http;
+using Umbraco.Cms.Core.DependencyInjection;
+
+namespace Umbraco.Cms.Web.Common.DependencyInjection
+{
+ ///
+ internal class ScopedServiceProvider : IScopedServiceProvider
+ {
+ private readonly IHttpContextAccessor _accessor;
+
+ public ScopedServiceProvider(IHttpContextAccessor accessor) => _accessor = accessor;
+
+ ///
+ public IServiceProvider ServiceProvider => _accessor.HttpContext?.RequestServices;
+ }
+}
diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
index e3e3462e6a..fda4d7ca32 100644
--- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -349,6 +349,7 @@ namespace Umbraco.Extensions
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.AddScoped();
builder.Services.AddScoped();
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/PublishedContentQueryAccessorTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/PublishedContentQueryAccessorTests.cs
new file mode 100644
index 0000000000..ec62bb55fa
--- /dev/null
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/PublishedContentQueryAccessorTests.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using NUnit.Framework;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Tests.Integration.TestServerTest;
+
+namespace Umbraco.Cms.Tests.Integration.Umbraco.Core
+{
+ [TestFixture]
+ public class PublishedContentQueryAccessorTests : UmbracoTestServerTestBase
+ {
+ [Test]
+ public async Task PublishedContentQueryAccessor_WithRequestScope_WillProvideQuery()
+ {
+ HttpResponseMessage result = await Client.GetAsync("/demo-published-content-query-accessor");
+ Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
+ }
+ }
+
+ public class PublishedContentQueryAccessorTestController : Controller
+ {
+ private readonly IPublishedContentQueryAccessor _accessor;
+
+ public PublishedContentQueryAccessorTestController(IPublishedContentQueryAccessor accessor)
+ {
+ _accessor = accessor;
+ }
+
+ [HttpGet("demo-published-content-query-accessor")]
+ public IActionResult Test()
+ {
+ var success = _accessor.TryGetValue(out IPublishedContentQuery query);
+
+ if (!success || query == null)
+ {
+ throw new ApplicationException("It doesn't work");
+ }
+
+ return Ok();
+ }
+ }
+}