Browse Source

Add dynamic background job support

Introduce dynamic background job support so jobs can be registered and executed without a compile-time job type. Key changes:

- Add DynamicBackgroundJobContext, IDynamicBackgroundJobHandlerProvider and DynamicBackgroundJobHandlerProvider to allow registering/unregistering dynamic handlers at runtime.
- Extend BackgroundJobConfiguration with DynamicHandler and IsDynamic, and make JobType nullable for dynamic scenarios.
- Update AbpBackgroundJobOptions to use a ConcurrentDictionary for name lookup, and add methods to Add/Remove dynamic jobs and GetJobOrNull.
- Extend JobExecutionContext with JobName and propagate it through Hangfire/Quartz/RabbitMQ/TickerQ adapters and worker code.
- Update BackgroundJobExecuter to detect and execute dynamic handlers, deserialize/ensure dictionary args, and retain existing typed execution path.
- Add tests (DynamicJobExecutionTracker, runtime/compile-time dynamic handler tests) and register a sample dynamic job in test module.
- Update demo SampleJobCreator and DemoAppSharedModule to demonstrate compile-time and runtime dynamic job registration and enqueueing.

These changes enable flexible, dictionary-based job arguments and runtime registration of background job handlers while preserving existing typed job execution.
pull/25059/head
SALİH ÖZKARA 3 weeks ago
parent
commit
c43458ac0e
  1. 40
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs
  2. 21
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobConfiguration.cs
  3. 83
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs
  4. 25
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobContext.cs
  5. 36
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerProvider.cs
  6. 15
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerProvider.cs
  7. 8
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs
  8. 6
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
  9. 6
      framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs
  10. 5
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  11. 6
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs
  12. 7
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs
  13. 16
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs
  14. 53
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs
  15. 19
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobManager_Tests.cs
  16. 8
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/DynamicJobExecutionTracker.cs
  17. 18
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs
  18. 57
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs

40
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<Type, BackgroundJobConfiguration> _jobConfigurationsByArgsType;
private readonly Dictionary<string, BackgroundJobConfiguration> _jobConfigurationsByName;
private readonly ConcurrentDictionary<string, BackgroundJobConfiguration> _jobConfigurationsByName;
/// <summary>
/// Default: true.
@ -23,7 +25,7 @@ public class AbpBackgroundJobOptions
public AbpBackgroundJobOptions()
{
_jobConfigurationsByArgsType = new Dictionary<Type, BackgroundJobConfiguration>();
_jobConfigurationsByName = new Dictionary<string, BackgroundJobConfiguration>();
_jobConfigurationsByName = new ConcurrentDictionary<string, BackgroundJobConfiguration>();
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<BackgroundJobConfiguration> 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<DynamicBackgroundJobContext, Task> handler)
{
var config = new BackgroundJobConfiguration(jobName, handler);
_jobConfigurationsByName[jobName] = config;
}
public void AddDynamicJob(string jobName, Action<DynamicBackgroundJobContext> 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;
}
}

21
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<DynamicBackgroundJobContext, Task>? DynamicHandler { get; }
public BackgroundJobConfiguration(Type jobType, string jobName)
{
JobType = jobType;
ArgsType = BackgroundJobArgsHelper.GetJobArgsType(jobType);
JobName = jobName;
}
public BackgroundJobConfiguration(string jobName, Func<DynamicBackgroundJobContext, Task> handler)
{
Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
Check.NotNull(handler, nameof(handler));
JobName = jobName;
DynamicHandler = handler;
IsDynamic = true;
ArgsType = typeof(Dictionary<string, object>);
}
}

83
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<BackgroundJobExecuter> Logger { protected get; set; }
protected AbpBackgroundJobOptions Options { get; }
protected ICurrentTenant CurrentTenant { get; }
public BackgroundJobExecuter(IOptions<AbpBackgroundJobOptions> 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<ICancellationTokenProvider>();
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<IExceptionNotifier>()
.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<ICancellationTokenProvider>();
@ -54,15 +106,14 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc
{
if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob<object>.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<string, object> EnsureDictionaryArgs(object jobArgs)
{
if (jobArgs is Dictionary<string, object> dict)
{
return dict;
}
if (jobArgs is JsonElement jsonElement)
{
return JsonSerializer.Deserialize<Dictionary<string, object>>(jsonElement.GetRawText())
?? new Dictionary<string, object>();
}
var json = JsonSerializer.Serialize(jobArgs);
return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
?? new Dictionary<string, object>();
}
protected virtual Guid? GetJobArgsTenantId(object jobArgs)
{
return jobArgs switch

25
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<string, object> Args { get; }
public CancellationToken CancellationToken { get; }
public DynamicBackgroundJobContext(
IServiceProvider serviceProvider,
Dictionary<string, object> args,
CancellationToken cancellationToken = default)
{
ServiceProvider = serviceProvider;
Args = args;
CancellationToken = cancellationToken;
}
}

36
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<AbpBackgroundJobOptions> options)
{
Options = options.Value;
}
public virtual void Register(string jobName, Func<DynamicBackgroundJobContext, Task> handler)
{
Options.AddDynamicJob(jobName, handler);
}
public virtual void Register(string jobName, Action<DynamicBackgroundJobContext> 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;
}
}

15
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<DynamicBackgroundJobContext, Task> handler);
void Register(string jobName, Action<DynamicBackgroundJobContext> handler);
bool Unregister(string jobName);
bool IsRegistered(string jobName);
}

8
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;
}
}

6
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs

@ -39,8 +39,8 @@ public class HangfireJobExecutionAdapter<TArgs>
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);
}
}

6
framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs

@ -39,8 +39,8 @@ public class QuartzJobExecutionAdapter<TArgs> : IJob
using (var scope = ServiceScopeFactory.CreateScope())
{
var args = JsonSerializer.Deserialize<TArgs>(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);

5
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs

@ -214,8 +214,9 @@ public class JobQueue<TArgs> : IJobQueue<TArgs>
{
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

6
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<IBackgroundJobExecuter>();
var args = await TickerRequestProvider.GetRequestAsync<TArgs>(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);
}
};

7
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
{

16
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<DynamicJobExecutionTracker>();
Configure<AbpBackgroundJobOptions>(options =>
{
options.AddDynamicJob("TestDynamicJob", context =>
{
var tracker = context.ServiceProvider.GetRequiredService<DynamicJobExecutionTracker>();
tracker.ExecutedArgs.Add(context.Args);
return System.Threading.Tasks.Task.CompletedTask;
});
});
}
}

53
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<DynamicJobExecutionTracker>();
tracker.ExecutedArgs.ShouldBeEmpty();
var args = new Dictionary<string, object> { ["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<IDynamicBackgroundJobHandlerProvider>();
var executedValues = new List<string>();
handlerProvider.Register("RuntimeDynamicJob", context =>
{
executedValues.Add(context.Args["Message"]?.ToString()!);
return Task.CompletedTask;
});
var args = new Dictionary<string, object> { ["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();
}
}

19
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<MyJobArgs>();
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<MyAsyncJobArgs>();
var jobIdAsString = await _backgroundJobManager.EnqueueAsync(jobName, new Dictionary<string, object>()
var jobIdAsString = await _backgroundJobManager.EnqueueAsync(jobName, (object)new Dictionary<string, object>()
{
["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<string, object>
{
["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");
}
}

8
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<Dictionary<string, object>> ExecutedArgs { get; } = new();
}

18
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<AbpBackgroundJobOptions>(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

57
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<string, object> { ["Value"] = "test 4 (dynamic) - compile-time" }
);
await _backgroundJobManager.EnqueueAsync(
"RuntimeDynamicJob",
(object)new Dictionary<string, object> { ["Value"] = "test 5 (dynamic) - runtime" }
);
}
}
}
}

Loading…
Cancel
Save