diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs index 1a6cb6a9e9..10cacce13e 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs @@ -1,13 +1,15 @@ -using System; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading.Tasks; namespace Volo.Abp.BackgroundJobs; public class AbpBackgroundJobOptions { private readonly Dictionary _jobConfigurationsByArgsType; - private readonly Dictionary _jobConfigurationsByName; + private readonly ConcurrentDictionary _jobConfigurationsByName; /// /// Default: true. @@ -23,7 +25,7 @@ public class AbpBackgroundJobOptions public AbpBackgroundJobOptions() { _jobConfigurationsByArgsType = new Dictionary(); - _jobConfigurationsByName = new Dictionary(); + _jobConfigurationsByName = new ConcurrentDictionary(); GetBackgroundJobName = BackgroundJobNameAttribute.GetName; } @@ -46,7 +48,7 @@ public class AbpBackgroundJobOptions public BackgroundJobConfiguration GetJob(string name) { - var jobConfiguration = _jobConfigurationsByName.GetOrDefault(name); + var jobConfiguration = GetJobOrNull(name); if (jobConfiguration == null) { @@ -56,6 +58,11 @@ public class AbpBackgroundJobOptions return jobConfiguration; } + public BackgroundJobConfiguration? GetJobOrNull(string name) + { + return _jobConfigurationsByName.GetValueOrDefault(name); + } + public IReadOnlyList GetJobs() { return _jobConfigurationsByArgsType.Values.ToImmutableList(); @@ -76,4 +83,29 @@ public class AbpBackgroundJobOptions _jobConfigurationsByArgsType[jobConfiguration.ArgsType] = jobConfiguration; _jobConfigurationsByName[jobConfiguration.JobName] = jobConfiguration; } + + public void AddDynamicJob(string jobName, Func handler) + { + var config = new BackgroundJobConfiguration(jobName, handler); + _jobConfigurationsByName[jobName] = config; + } + + public void AddDynamicJob(string jobName, Action handler) + { + AddDynamicJob(jobName, context => + { + handler(context); + return Task.CompletedTask; + }); + } + + public bool RemoveDynamicJob(string name) + { + if (_jobConfigurationsByName.TryGetValue(name, out var config) && config.IsDynamic) + { + return _jobConfigurationsByName.TryRemove(name, out _); + } + + return false; + } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobConfiguration.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobConfiguration.cs index c17ccdc443..cd0e844a99 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobConfiguration.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobConfiguration.cs @@ -1,4 +1,6 @@ -using System; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Volo.Abp.BackgroundJobs; @@ -6,14 +8,29 @@ public class BackgroundJobConfiguration { public Type ArgsType { get; } - public Type JobType { get; } + public Type? JobType { get; } public string JobName { get; } + public bool IsDynamic { get; } + + public Func? DynamicHandler { get; } + public BackgroundJobConfiguration(Type jobType, string jobName) { JobType = jobType; ArgsType = BackgroundJobArgsHelper.GetJobArgsType(jobType); JobName = jobName; } + + public BackgroundJobConfiguration(string jobName, Func handler) + { + Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); + Check.NotNull(handler, nameof(handler)); + + JobName = jobName; + DynamicHandler = handler; + IsDynamic = true; + ArgsType = typeof(Dictionary); + } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs index 6b6d822c50..d84845ee0f 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System; +using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; @@ -16,7 +18,7 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc public ILogger Logger { protected get; set; } protected AbpBackgroundJobOptions Options { get; } - + protected ICurrentTenant CurrentTenant { get; } public BackgroundJobExecuter(IOptions options, ICurrentTenant currentTenant) @@ -28,6 +30,56 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc } public virtual async Task ExecuteAsync(JobExecutionContext context) + { + if (context.JobName != null) + { + var jobConfig = Options.GetJobOrNull(context.JobName); + if (jobConfig?.DynamicHandler != null) + { + await ExecuteDynamicHandlerAsync(context, jobConfig); + return; + } + } + + await ExecuteTypedHandlerAsync(context); + } + + protected virtual async Task ExecuteDynamicHandlerAsync(JobExecutionContext context, BackgroundJobConfiguration jobConfig) + { + try + { + var cancellationTokenProvider = + context.ServiceProvider.GetRequiredService(); + + using (cancellationTokenProvider.Use(context.CancellationToken)) + { + var dictArgs = EnsureDictionaryArgs(context.JobArgs); + var dynamicContext = new DynamicBackgroundJobContext( + context.ServiceProvider, + dictArgs, + context.CancellationToken + ); + + await jobConfig.DynamicHandler!(dynamicContext); + } + } + catch (Exception ex) + { + Logger.LogException(ex); + + await context.ServiceProvider + .GetRequiredService() + .NotifyAsync(new ExceptionNotificationContext(ex)); + + throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex) + { + JobType = context.JobName!, + JobArgs = context.JobArgs + }; + } + } + + protected virtual async Task ExecuteTypedHandlerAsync(JobExecutionContext context) { var job = context.ServiceProvider.GetService(context.JobType); if (job == null) @@ -45,7 +97,7 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc try { - using(CurrentTenant.Change(GetJobArgsTenantId(context.JobArgs))) + using (CurrentTenant.Change(GetJobArgsTenantId(context.JobArgs))) { var cancellationTokenProvider = context.ServiceProvider.GetRequiredService(); @@ -54,15 +106,14 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc { if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob.ExecuteAsync)) { - await ((Task)jobExecuteMethod.Invoke(job, new[] { context.JobArgs })!); + await ((Task)jobExecuteMethod.Invoke(job, [context.JobArgs])!); } else { - jobExecuteMethod.Invoke(job, new[] { context.JobArgs }); + jobExecuteMethod.Invoke(job, [context.JobArgs]); } } } - } catch (Exception ex) { @@ -79,7 +130,25 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc }; } } - + + protected virtual Dictionary EnsureDictionaryArgs(object jobArgs) + { + if (jobArgs is Dictionary dict) + { + return dict; + } + + if (jobArgs is JsonElement jsonElement) + { + return JsonSerializer.Deserialize>(jsonElement.GetRawText()) + ?? new Dictionary(); + } + + var json = JsonSerializer.Serialize(jobArgs); + return JsonSerializer.Deserialize>(json) + ?? new Dictionary(); + } + protected virtual Guid? GetJobArgsTenantId(object jobArgs) { return jobArgs switch diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobContext.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobContext.cs new file mode 100644 index 0000000000..49ffe89aca --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobContext.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BackgroundJobs; + +public class DynamicBackgroundJobContext : IServiceProviderAccessor +{ + public IServiceProvider ServiceProvider { get; } + + public Dictionary Args { get; } + + public CancellationToken CancellationToken { get; } + + public DynamicBackgroundJobContext( + IServiceProvider serviceProvider, + Dictionary args, + CancellationToken cancellationToken = default) + { + ServiceProvider = serviceProvider; + Args = args; + CancellationToken = cancellationToken; + } +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerProvider.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerProvider.cs new file mode 100644 index 0000000000..2665719c2a --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BackgroundJobs; + +public class DynamicBackgroundJobHandlerProvider : IDynamicBackgroundJobHandlerProvider, ISingletonDependency +{ + protected AbpBackgroundJobOptions Options { get; } + + public DynamicBackgroundJobHandlerProvider(IOptions options) + { + Options = options.Value; + } + + public virtual void Register(string jobName, Func handler) + { + Options.AddDynamicJob(jobName, handler); + } + + public virtual void Register(string jobName, Action handler) + { + Options.AddDynamicJob(jobName, handler); + } + + public virtual bool Unregister(string jobName) + { + return Options.RemoveDynamicJob(jobName); + } + + public virtual bool IsRegistered(string jobName) + { + return Options.GetJobOrNull(jobName)?.IsDynamic == true; + } +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerProvider.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerProvider.cs new file mode 100644 index 0000000000..0f1bb8449a --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +namespace Volo.Abp.BackgroundJobs; + +public interface IDynamicBackgroundJobHandlerProvider +{ + void Register(string jobName, Func handler); + + void Register(string jobName, Action handler); + + bool Unregister(string jobName); + + bool IsRegistered(string jobName); +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs index ae7e098070..a68736bb7d 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using Volo.Abp.DependencyInjection; @@ -14,15 +14,19 @@ public class JobExecutionContext : IServiceProviderAccessor public CancellationToken CancellationToken { get; } + public string? JobName { get; } + public JobExecutionContext( IServiceProvider serviceProvider, Type jobType, object jobArgs, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, + string? jobName = null) { ServiceProvider = serviceProvider; JobType = jobType; JobArgs = jobArgs; CancellationToken = cancellationToken; + JobName = jobName; } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs index e9e2a3aa08..b85154a5c3 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs @@ -39,8 +39,8 @@ public class HangfireJobExecutionAdapter using (var scope = ServiceScopeFactory.CreateScope()) { - var jobType = Options.GetJob(typeof(TArgs)).JobType; - var context = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken); + var jobConfiguration = Options.GetJob(typeof(TArgs)); + var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType!, args!, cancellationToken: cancellationToken, jobName: jobConfiguration.JobName); await JobExecuter.ExecuteAsync(context); } } @@ -83,7 +83,7 @@ public class HangfireJobExecutionAdapter { var jobConfiguration = Options.GetJob(jobName); var args = JsonSerializer.Deserialize(jobConfiguration.ArgsType, serializedArgs); - var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, args, cancellationToken: cancellationToken); + var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType ?? typeof(object), args, cancellationToken: cancellationToken, jobName: jobName); await JobExecuter.ExecuteAsync(context); } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs index bdc12c2420..defdb87031 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs @@ -39,8 +39,8 @@ public class QuartzJobExecutionAdapter : IJob using (var scope = ServiceScopeFactory.CreateScope()) { var args = JsonSerializer.Deserialize(context.JobDetail.JobDataMap.GetString(nameof(TArgs))!); - var jobType = Options.GetJob(typeof(TArgs)).JobType; - var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: context.CancellationToken); + var jobConfiguration = Options.GetJob(typeof(TArgs)); + var jobContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType!, args!, cancellationToken: context.CancellationToken, jobName: jobConfiguration.JobName); try { await JobExecuter.ExecuteAsync(jobContext); @@ -97,7 +97,7 @@ public class QuartzJobExecutionAdapter : IJob var serializedArgs = context.JobDetail.JobDataMap.GetString(JobArgsKey)!; var jobConfiguration = Options.GetJob(jobName); var args = JsonSerializer.Deserialize(jobConfiguration.ArgsType, serializedArgs); - var jobContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, args, cancellationToken: context.CancellationToken); + var jobContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType ?? typeof(object), args, cancellationToken: context.CancellationToken, jobName: jobName); try { await JobExecuter.ExecuteAsync(jobContext); diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs index 33c41704f2..be7614060d 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs @@ -214,8 +214,9 @@ public class JobQueue : IJobQueue { var context = new JobExecutionContext( scope.ServiceProvider, - JobConfiguration.JobType, - Serializer.Deserialize(ea.Body.ToArray(), typeof(TArgs)) + JobConfiguration.JobType ?? typeof(object), + Serializer.Deserialize(ea.Body.ToArray(), typeof(TArgs)), + jobName: JobConfiguration.JobName ); try diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs index 3d93fc68a1..915e061057 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Reflection; using Microsoft.Extensions.DependencyInjection; @@ -66,8 +66,8 @@ public class AbpBackgroundJobsTickerQModule : AbpModule { var jobExecuter = serviceProvider.GetRequiredService(); var args = await TickerRequestProvider.GetRequestAsync(context, cancellationToken); - var jobType = options.GetJob(typeof(TArgs)).JobType; - var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken); + var jobConfiguration = options.GetJob(typeof(TArgs)); + var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType ?? typeof(object), args!, cancellationToken: cancellationToken, jobName: jobConfiguration.JobName); await jobExecuter.ExecuteAsync(jobExecutionContext); } }; diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs index a015e32d66..930beecd5c 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -65,9 +65,10 @@ public class BackgroundJobWorker : AsyncPeriodicBackgroundWorkerBase, IBackgroun var jobArgs = serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType); var context = new JobExecutionContext( workerContext.ServiceProvider, - jobConfiguration.JobType, + jobConfiguration.JobType ?? typeof(object), jobArgs, - workerContext.CancellationToken); + workerContext.CancellationToken, + jobName: jobInfo.JobName); try { diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs index f2464013fd..14e2c558bd 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Autofac; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; using Volo.Abp.Modularity; namespace Volo.Abp.BackgroundJobs; @@ -10,5 +11,18 @@ namespace Volo.Abp.BackgroundJobs; )] public class AbpBackgroundJobsTestModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddSingleton(); + Configure(options => + { + options.AddDynamicJob("TestDynamicJob", context => + { + var tracker = context.ServiceProvider.GetRequiredService(); + tracker.ExecutedArgs.Add(context.Args); + return System.Threading.Tasks.Task.CompletedTask; + }); + }); + } } diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs index db7b5f5cc8..87f7ff316b 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Shouldly; @@ -136,4 +137,56 @@ public class BackgroundJobExecuter_Tests : BackgroundJobsTestBase //Assert asyncJobObject.Canceled.ShouldBeTrue(); } + + [Fact] + public async Task Should_Execute_Dynamic_Handler() + { + var tracker = GetRequiredService(); + tracker.ExecutedArgs.ShouldBeEmpty(); + + var args = new Dictionary { ["Value"] = "dynamic-42" }; + + await _backgroundJobExecuter.ExecuteAsync( + new JobExecutionContext( + ServiceProvider, + typeof(object), + args, + jobName: "TestDynamicJob" + ) + ); + + tracker.ExecutedArgs.Count.ShouldBe(1); + tracker.ExecutedArgs[0]["Value"].ShouldBe("dynamic-42"); + } + + [Fact] + public async Task Should_Execute_Dynamic_Handler_Registered_At_Runtime() + { + var handlerProvider = GetRequiredService(); + var executedValues = new List(); + + handlerProvider.Register("RuntimeDynamicJob", context => + { + executedValues.Add(context.Args["Message"]?.ToString()!); + return Task.CompletedTask; + }); + + var args = new Dictionary { ["Message"] = "hello-runtime" }; + + await _backgroundJobExecuter.ExecuteAsync( + new JobExecutionContext( + ServiceProvider, + typeof(object), + args, + jobName: "RuntimeDynamicJob" + ) + ); + + executedValues.Count.ShouldBe(1); + executedValues[0].ShouldBe("hello-runtime"); + + handlerProvider.IsRegistered("RuntimeDynamicJob").ShouldBeTrue(); + handlerProvider.Unregister("RuntimeDynamicJob").ShouldBeTrue(); + handlerProvider.IsRegistered("RuntimeDynamicJob").ShouldBeFalse(); + } } diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobManager_Tests.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobManager_Tests.cs index 2f13e66822..75d1218f94 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobManager_Tests.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobManager_Tests.cs @@ -37,7 +37,7 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase public async Task Should_Store_Jobs_With_JobName() { var jobName = BackgroundJobNameAttribute.GetName(); - var jobIdAsString = await _backgroundJobManager.EnqueueAsync(jobName, new + var jobIdAsString = await _backgroundJobManager.EnqueueAsync(jobName, (object)new { Value = "42" }); @@ -52,7 +52,7 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase public async Task Should_Store_Async_Jobs_With_JobName() { var jobName = BackgroundJobNameAttribute.GetName(); - var jobIdAsString = await _backgroundJobManager.EnqueueAsync(jobName, new Dictionary() + var jobIdAsString = await _backgroundJobManager.EnqueueAsync(jobName, (object)new Dictionary() { ["Value"] = "42" }); @@ -62,4 +62,19 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase jobInfo.ShouldNotBeNull(); jobInfo.JobName.ShouldBe(jobName); } + + [Fact] + public async Task Should_Store_Dynamic_Jobs() + { + var jobIdAsString = await _backgroundJobManager.EnqueueAsync("TestDynamicJob", (object)new Dictionary + { + ["OrderId"] = "ORD-001" + }); + jobIdAsString.ShouldNotBe(default); + + var jobInfo = await _backgroundJobStore.FindAsync(Guid.Parse(jobIdAsString)); + jobInfo.ShouldNotBeNull(); + jobInfo.JobName.ShouldBe("TestDynamicJob"); + jobInfo.JobArgs.ShouldContain("ORD-001"); + } } diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/DynamicJobExecutionTracker.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/DynamicJobExecutionTracker.cs new file mode 100644 index 0000000000..dec387ab97 --- /dev/null +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/DynamicJobExecutionTracker.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.BackgroundJobs; + +public class DynamicJobExecutionTracker +{ + public List> ExecutedArgs { get; } = new(); +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs index 713355636f..47da99d683 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; @@ -8,6 +10,20 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared [DependsOn(typeof(AbpMultiTenancyModule))] public class DemoAppSharedModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AddDynamicJob("CompileTimeDynamicJob", dynamicContext => + { + dynamicContext.Args.TryGetValue("Value", out var valueObj); + var value = valueObj?.ToString(); + Console.WriteLine($"[DYNAMIC-COMPILE] {value}"); + return Task.CompletedTask; + }); + }); + } + public override void OnPostApplicationInitialization(ApplicationInitializationContext context) { context.ServiceProvider diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs index b131c5943d..a9ceca9bdd 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Threading; @@ -7,10 +9,14 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs public class SampleJobCreator : ITransientDependency { private readonly IBackgroundJobManager _backgroundJobManager; + private readonly IDynamicBackgroundJobHandlerProvider _dynamicBackgroundJobHandlerProvider; - public SampleJobCreator(IBackgroundJobManager backgroundJobManager) + public SampleJobCreator( + IBackgroundJobManager backgroundJobManager, + IDynamicBackgroundJobHandlerProvider dynamicBackgroundJobHandlerProvider) { _backgroundJobManager = backgroundJobManager; + _dynamicBackgroundJobHandlerProvider = dynamicBackgroundJobHandlerProvider; } public void CreateJobs() @@ -20,10 +26,49 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs public async Task CreateJobsAsync() { - await _backgroundJobManager.EnqueueAsync(new WriteToConsoleGreenJobArgs { Value = "test 1 (green)" }); - await _backgroundJobManager.EnqueueAsync(new WriteToConsoleGreenJobArgs { Value = "test 2 (green)" }); - await _backgroundJobManager.EnqueueAsync(new WriteToConsoleYellowJobArgs { Value = "test 1 (yellow)" }); - await _backgroundJobManager.EnqueueAsync(new WriteToConsoleYellowJobArgs { Value = "test 2 (yellow)" }); + _dynamicBackgroundJobHandlerProvider.Register("RuntimeDynamicJob", context => + { + context.Args.TryGetValue("Value", out var valueObj); + Console.WriteLine($"[DYNAMIC-RUNTIME] {valueObj}"); + return Task.CompletedTask; + }); + + // Type-safe enqueue (existing) + await _backgroundJobManager.EnqueueAsync(new WriteToConsoleGreenJobArgs { Value = "test 1 (green) - typed" }); + await _backgroundJobManager.EnqueueAsync(new WriteToConsoleYellowJobArgs { Value = "test 1 (yellow) - typed" }); + + // String-based enqueue with strongly-typed args + await _backgroundJobManager.EnqueueAsync( + "GreenJob", + (object)new WriteToConsoleGreenJobArgs { Value = "test 2 (green) - by name, typed args" } + ); + await _backgroundJobManager.EnqueueAsync( + "YellowJob", + (object)new WriteToConsoleYellowJobArgs { Value = "test 2 (yellow) - by name, typed args" } + ); + + // String-based enqueue with anonymous object + await _backgroundJobManager.EnqueueAsync( + "GreenJob", + (object)new { Value = "test 3 (green) - by name, anonymous", Time = DateTime.Now } + ); + await _backgroundJobManager.EnqueueAsync( + "YellowJob", + (object)new { Value = "test 3 (yellow) - by name, anonymous", Time = DateTime.Now } + ); + + // Dynamic enqueue (compile-time and runtime handlers) + if (!_backgroundJobManager.GetType().Name.Contains("RabbitMq", StringComparison.OrdinalIgnoreCase)) + { + await _backgroundJobManager.EnqueueAsync( + "CompileTimeDynamicJob", + (object)new Dictionary { ["Value"] = "test 4 (dynamic) - compile-time" } + ); + await _backgroundJobManager.EnqueueAsync( + "RuntimeDynamicJob", + (object)new Dictionary { ["Value"] = "test 5 (dynamic) - runtime" } + ); + } } } }