From 9ee427e8a89c4123eb86c90ee6bbd576870bc5f9 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 2 May 2026 14:15:52 +0800 Subject: [PATCH 1/2] Disable conventional registration of InMemoryDynamicBackgroundWorker InMemoryDynamicBackgroundWorker indirectly implements ISingletonDependency via IBackgroundWorker, so conventional registration tries to register it as a service. Its constructor takes a string workerName parameter that the DI container cannot resolve, which crashes any host that runs ServiceCollection validation (e.g. ASP.NET Core in Development, where WebApplicationBuilder.Build() enables ValidateOnBuild). The dynamic worker is created on demand by DefaultDynamicBackgroundWorkerManager and must not be auto-registered, so mark it with [DisableConventionalRegistration]. --- .../InMemoryDynamicBackgroundWorker.cs | 2 ++ ...amicBackgroundWorker_Registration_Tests.cs | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs index bab468a655..7bffb3895a 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs @@ -1,10 +1,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; using Volo.Abp.Threading; namespace Volo.Abp.BackgroundWorkers; +[DisableConventionalRegistration] public class InMemoryDynamicBackgroundWorker : AsyncPeriodicBackgroundWorkerBase { public string WorkerName { get; } diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs new file mode 100644 index 0000000000..de673886f5 --- /dev/null +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs @@ -0,0 +1,33 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.BackgroundWorkers; + +public class InMemoryDynamicBackgroundWorker_Registration_Tests +{ + [Fact] + public async Task Should_Not_Be_Auto_Registered_To_DI_Container() + { + // Regression guard: IBackgroundWorker derives from ISingletonDependency, so without + // [DisableConventionalRegistration] the conventional registration would register + // InMemoryDynamicBackgroundWorker as a Singleton. Its constructor takes a `string + // workerName` parameter that the DI container cannot resolve, which broke any host + // running ServiceCollection validation (e.g. ASP.NET Core in Development, where + // WebApplicationBuilder.Build() enables ValidateOnBuild). + using var application = await AbpApplicationFactory.CreateAsync(); + + application.Services + .Any(d => d.ServiceType == typeof(InMemoryDynamicBackgroundWorker)) + .ShouldBeFalse( + "InMemoryDynamicBackgroundWorker is created on demand by DefaultDynamicBackgroundWorkerManager " + + "and must not be auto-registered as a service."); + + // Building a fresh provider with ValidateOnBuild = true must not throw. + await using var validatingProvider = application.Services.BuildServiceProvider( + new ServiceProviderOptions { ValidateOnBuild = true }); + validatingProvider.ShouldNotBeNull(); + } +} From cd3825b84d176600b23bdc2217aaa1cd8a2e7010 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 2 May 2026 14:19:19 +0800 Subject: [PATCH 2/2] Split registration tests: reproduce ValidateOnBuild failure separately --- ...amicBackgroundWorker_Registration_Tests.cs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs index de673886f5..677efbbc5e 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs @@ -8,26 +8,30 @@ namespace Volo.Abp.BackgroundWorkers; public class InMemoryDynamicBackgroundWorker_Registration_Tests { + // Reproduces the original failure: ASP.NET Core in Development enables ValidateOnBuild, + // and InMemoryDynamicBackgroundWorker has a `string workerName` constructor parameter + // that DI cannot resolve. Without [DisableConventionalRegistration] this throws: + // Unable to resolve service for type 'System.String' while attempting to activate + // 'Volo.Abp.BackgroundWorkers.InMemoryDynamicBackgroundWorker'. [Fact] - public async Task Should_Not_Be_Auto_Registered_To_DI_Container() + public async Task BuildServiceProvider_With_ValidateOnBuild_Should_Not_Throw() { - // Regression guard: IBackgroundWorker derives from ISingletonDependency, so without - // [DisableConventionalRegistration] the conventional registration would register - // InMemoryDynamicBackgroundWorker as a Singleton. Its constructor takes a `string - // workerName` parameter that the DI container cannot resolve, which broke any host - // running ServiceCollection validation (e.g. ASP.NET Core in Development, where - // WebApplicationBuilder.Build() enables ValidateOnBuild). using var application = await AbpApplicationFactory.CreateAsync(); - application.Services - .Any(d => d.ServiceType == typeof(InMemoryDynamicBackgroundWorker)) - .ShouldBeFalse( - "InMemoryDynamicBackgroundWorker is created on demand by DefaultDynamicBackgroundWorkerManager " + - "and must not be auto-registered as a service."); - - // Building a fresh provider with ValidateOnBuild = true must not throw. - await using var validatingProvider = application.Services.BuildServiceProvider( + var act = () => application.Services.BuildServiceProvider( new ServiceProviderOptions { ValidateOnBuild = true }); - validatingProvider.ShouldNotBeNull(); + + act.ShouldNotThrow(); + } + + // Verifies the fix: InMemoryDynamicBackgroundWorker is created on demand by + // DefaultDynamicBackgroundWorkerManager (`new InMemoryDynamicBackgroundWorker(...)`) + // and must stay out of the conventional registration loop. + [Fact] + public async Task InMemoryDynamicBackgroundWorker_Should_Not_Be_Registered_As_Service() + { + using var application = await AbpApplicationFactory.CreateAsync(); + + application.Services.ShouldNotContain(d => d.ServiceType == typeof(InMemoryDynamicBackgroundWorker)); } }