Browse Source

Add anonymous background job support

Introduce anonymous/background-job-by-name support: add AnonymousJobArgs, IAnonymousJobHandlerRegistry and AnonymousJobHandlerRegistry, and an AnonymousJobExecutorAsyncBackgroundJob to execute JSON-based anonymous handlers. AbpBackgroundJobOptions now stores anonymous handlers and exposes registration/query helpers. Updated background job managers (Default, Hangfire, Quartz, RabbitMQ, TickerQ) to wrap registered anonymous jobs into AnonymousJobArgs when enqueuing and to depend on the handler registry and JSON serializer where needed. Removed the old dynamic handler types/APIs (DynamicBackgroundJobContext, IDynamicBackgroundJobHandlerProvider, DynamicBackgroundJobHandlerProvider) and related dynamic handling code; BackgroundJobConfiguration simplified (JobType non-nullable) and JobExecutionContext no longer carries JobName. Tests and demo code updated to use anonymous handlers and tracking. This change centralizes runtime-registered handlers keyed by job name and standardizes anonymous job payloads as JSON.
pull/25059/head
SALİH ÖZKARA 3 weeks ago
parent
commit
67904ae2e4
  1. 9
      .cursor/hooks/state/continual-learning-index.json
  2. 8
      .cursor/hooks/state/continual-learning.json
  3. 30
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs
  4. 17
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobArgs.cs
  5. 56
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobHandlerRegistry.cs
  6. 19
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobConfiguration.cs
  7. 65
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs
  8. 25
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobContext.cs
  9. 36
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerProvider.cs
  10. 18
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAnonymousJobHandlerRegistry.cs
  11. 15
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerProvider.cs
  12. 6
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs
  13. 17
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
  14. 4
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
  15. 17
      framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzBackgroundJobManager.cs
  16. 2
      framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzJobExecutionAdapter.cs
  17. 5
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  18. 19
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs
  19. 2
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs
  20. 21
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs
  21. 35
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs
  22. 5
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs
  23. 15
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/DefaultBackgroundJobManager.cs
  24. 8
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs
  25. 8
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AnonymousJobExecutionTracker.cs
  26. 40
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs
  27. 10
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobManager_Tests.cs
  28. 8
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/DynamicJobExecutionTracker.cs
  29. 9
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs
  30. 27
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs
  31. 25
      modules/background-jobs/app/docker-compose.yml

9
.cursor/hooks/state/continual-learning-index.json

@ -0,0 +1,9 @@
{
"version": 1,
"transcripts": {
"C:\\Users\\salih\\.cursor\\projects\\d-GitHub2-abp\\agent-transcripts\\ded34ec4-ff99-404a-8b16-10baf3c5d9f5\\ded34ec4-ff99-404a-8b16-10baf3c5d9f5.jsonl": {
"mtimeMs": 1773090599259,
"lastProcessedAt": "2026-03-10T00:18:00.000Z"
}
}
}

8
.cursor/hooks/state/continual-learning.json

@ -0,0 +1,8 @@
{
"version": 1,
"lastRunAtMs": 1773090515725,
"turnsSinceLastRun": 5,
"lastTranscriptMtimeMs": 1773090515368,
"lastProcessedGenerationId": "b1b803a9-44bb-4047-a116-4c108a6dadd1",
"trialStartedAtMs": null
}

30
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Volo.Abp.BackgroundJobs; namespace Volo.Abp.BackgroundJobs;
@ -10,6 +11,7 @@ public class AbpBackgroundJobOptions
{ {
private readonly Dictionary<Type, BackgroundJobConfiguration> _jobConfigurationsByArgsType; private readonly Dictionary<Type, BackgroundJobConfiguration> _jobConfigurationsByArgsType;
private readonly ConcurrentDictionary<string, BackgroundJobConfiguration> _jobConfigurationsByName; private readonly ConcurrentDictionary<string, BackgroundJobConfiguration> _jobConfigurationsByName;
private readonly ConcurrentDictionary<string, Func<string, IServiceProvider, CancellationToken, Task>> _anonymousHandlers = new();
/// <summary> /// <summary>
/// Default: true. /// Default: true.
@ -60,7 +62,7 @@ public class AbpBackgroundJobOptions
public BackgroundJobConfiguration? GetJobOrNull(string name) public BackgroundJobConfiguration? GetJobOrNull(string name)
{ {
return _jobConfigurationsByName.GetValueOrDefault(name); return _jobConfigurationsByName.TryGetValue(name, out var config) ? config : null;
} }
public IReadOnlyList<BackgroundJobConfiguration> GetJobs() public IReadOnlyList<BackgroundJobConfiguration> GetJobs()
@ -84,28 +86,30 @@ public class AbpBackgroundJobOptions
_jobConfigurationsByName[jobConfiguration.JobName] = jobConfiguration; _jobConfigurationsByName[jobConfiguration.JobName] = jobConfiguration;
} }
public void AddDynamicJob(string jobName, Func<DynamicBackgroundJobContext, Task> handler) public void AddAnonymousJobHandler(string jobName, Func<string, IServiceProvider, CancellationToken, Task> handler)
{ {
var config = new BackgroundJobConfiguration(jobName, handler); Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
_jobConfigurationsByName[jobName] = config; Check.NotNull(handler, nameof(handler));
_anonymousHandlers[jobName] = handler;
} }
public void AddDynamicJob(string jobName, Action<DynamicBackgroundJobContext> handler) public void AddAnonymousJobHandler(string jobName, Action<string, IServiceProvider, CancellationToken> handler)
{ {
AddDynamicJob(jobName, context => AddAnonymousJobHandler(jobName, (jsonData, sp, ct) =>
{ {
handler(context); handler(jsonData, sp, ct);
return Task.CompletedTask; return Task.CompletedTask;
}); });
} }
public bool RemoveDynamicJob(string name) internal bool TryGetAnonymousHandler(string jobName, out Func<string, IServiceProvider, CancellationToken, Task>? handler)
{ {
if (_jobConfigurationsByName.TryGetValue(name, out var config) && config.IsDynamic) return _anonymousHandlers.TryGetValue(jobName, out handler);
{ }
return _jobConfigurationsByName.TryRemove(name, out _);
}
return false; internal bool IsAnonymousJobRegistered(string jobName)
{
return _anonymousHandlers.ContainsKey(jobName);
} }
} }

17
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobArgs.cs

@ -0,0 +1,17 @@
namespace Volo.Abp.BackgroundJobs;
[BackgroundJobName("AnonymousJob")]
public class AnonymousJobArgs
{
public const string JobNameConstant = "AnonymousJob";
public string JobName { get; }
public string JsonData { get; }
public AnonymousJobArgs(string jobName, string jsonData)
{
JobName = Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
JsonData = Check.NotNull(jsonData, nameof(jsonData));
}
}

56
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobHandlerRegistry.cs

@ -0,0 +1,56 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BackgroundJobs;
public class AnonymousJobHandlerRegistry : IAnonymousJobHandlerRegistry, ISingletonDependency
{
private readonly ConcurrentDictionary<string, Func<string, IServiceProvider, CancellationToken, Task>> _handlers = new();
private readonly AbpBackgroundJobOptions _options;
public AnonymousJobHandlerRegistry(IOptions<AbpBackgroundJobOptions> options)
{
_options = options.Value;
}
public virtual void Register(string jobName, Func<string, IServiceProvider, CancellationToken, Task> handler)
{
Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
Check.NotNull(handler, nameof(handler));
_handlers[jobName] = handler;
}
public virtual void Register(string jobName, Action<string, IServiceProvider, CancellationToken> handler)
{
Register(jobName, (jsonData, sp, ct) =>
{
handler(jsonData, sp, ct);
return Task.CompletedTask;
});
}
public virtual bool Unregister(string jobName)
{
return _handlers.TryRemove(jobName, out _);
}
public virtual bool IsRegistered(string jobName)
{
return _handlers.ContainsKey(jobName) || _options.IsAnonymousJobRegistered(jobName);
}
public virtual Func<string, IServiceProvider, CancellationToken, Task>? Get(string jobName)
{
if (_handlers.TryGetValue(jobName, out var handler))
{
return handler;
}
return _options.TryGetAnonymousHandler(jobName, out handler) ? handler : null;
}
}

19
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobConfiguration.cs

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Volo.Abp.BackgroundJobs; namespace Volo.Abp.BackgroundJobs;
@ -8,29 +6,14 @@ public class BackgroundJobConfiguration
{ {
public Type ArgsType { get; } public Type ArgsType { get; }
public Type? JobType { get; } public Type JobType { get; }
public string JobName { get; } public string JobName { get; }
public bool IsDynamic { get; }
public Func<DynamicBackgroundJobContext, Task>? DynamicHandler { get; }
public BackgroundJobConfiguration(Type jobType, string jobName) public BackgroundJobConfiguration(Type jobType, string jobName)
{ {
JobType = jobType; JobType = jobType;
ArgsType = BackgroundJobArgsHelper.GetJobArgsType(jobType); ArgsType = BackgroundJobArgsHelper.GetJobArgsType(jobType);
JobName = jobName; 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>);
}
} }

65
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs

@ -2,8 +2,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -31,54 +29,9 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc
public virtual async Task ExecuteAsync(JobExecutionContext context) 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); 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) protected virtual async Task ExecuteTypedHandlerAsync(JobExecutionContext context)
{ {
var job = context.ServiceProvider.GetService(context.JobType); var job = context.ServiceProvider.GetService(context.JobType);
@ -131,24 +84,6 @@ 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) protected virtual Guid? GetJobArgsTenantId(object jobArgs)
{ {
return jobArgs switch return jobArgs switch

25
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobContext.cs

@ -1,25 +0,0 @@
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

@ -1,36 +0,0 @@
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;
}
}

18
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAnonymousJobHandlerRegistry.cs

@ -0,0 +1,18 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.BackgroundJobs;
public interface IAnonymousJobHandlerRegistry
{
void Register(string jobName, Func<string, IServiceProvider, System.Threading.CancellationToken, Task> handler);
void Register(string jobName, Action<string, IServiceProvider, System.Threading.CancellationToken> handler);
bool Unregister(string jobName);
bool IsRegistered(string jobName);
Func<string, IServiceProvider, System.Threading.CancellationToken, Task>? Get(string jobName);
}

15
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerProvider.cs

@ -1,15 +0,0 @@
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);
}

6
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs

@ -14,19 +14,15 @@ public class JobExecutionContext : IServiceProviderAccessor
public CancellationToken CancellationToken { get; } public CancellationToken CancellationToken { get; }
public string? JobName { get; }
public JobExecutionContext( public JobExecutionContext(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
Type jobType, Type jobType,
object jobArgs, object jobArgs,
CancellationToken cancellationToken = default, CancellationToken cancellationToken = default)
string? jobName = null)
{ {
ServiceProvider = serviceProvider; ServiceProvider = serviceProvider;
JobType = jobType; JobType = jobType;
JobArgs = jobArgs; JobArgs = jobArgs;
CancellationToken = cancellationToken; CancellationToken = cancellationToken;
JobName = jobName;
} }
} }

17
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs

@ -16,15 +16,18 @@ public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDep
protected IOptions<AbpBackgroundJobOptions> BackgroundJobOptions { get; } protected IOptions<AbpBackgroundJobOptions> BackgroundJobOptions { get; }
protected IOptions<AbpHangfireOptions> HangfireOptions { get; } protected IOptions<AbpHangfireOptions> HangfireOptions { get; }
protected IJsonSerializer JsonSerializer { get; } protected IJsonSerializer JsonSerializer { get; }
protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; }
public HangfireBackgroundJobManager( public HangfireBackgroundJobManager(
IOptions<AbpBackgroundJobOptions> backgroundJobOptions, IOptions<AbpBackgroundJobOptions> backgroundJobOptions,
IOptions<AbpHangfireOptions> hangfireOptions, IOptions<AbpHangfireOptions> hangfireOptions,
IJsonSerializer jsonSerializer) IJsonSerializer jsonSerializer,
IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry)
{ {
BackgroundJobOptions = backgroundJobOptions; BackgroundJobOptions = backgroundJobOptions;
HangfireOptions = hangfireOptions; HangfireOptions = hangfireOptions;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry;
} }
public virtual Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, public virtual Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
@ -43,6 +46,13 @@ public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDep
public virtual Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, public virtual Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null) TimeSpan? delay = null)
{ {
if (ShouldWrapAsAnonymousJob(jobName))
{
var jsonData = JsonSerializer.Serialize(args);
var anonymousArgs = new AnonymousJobArgs(jobName, jsonData);
return EnqueueAsync(AnonymousJobArgs.JobNameConstant, anonymousArgs, priority, delay);
}
var serializedArgs = JsonSerializer.Serialize(args); var serializedArgs = JsonSerializer.Serialize(args);
var queueName = GetQueueName(jobName); var queueName = GetQueueName(jobName);
@ -56,6 +66,11 @@ public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDep
)); ));
} }
protected virtual bool ShouldWrapAsAnonymousJob(string jobName)
{
return jobName != AnonymousJobArgs.JobNameConstant && AnonymousJobHandlerRegistry.IsRegistered(jobName);
}
protected virtual string GetQueueName(Type argsType) protected virtual string GetQueueName(Type argsType)
{ {
return GetQueueName(BackgroundJobOptions.Value.GetJob(argsType)); return GetQueueName(BackgroundJobOptions.Value.GetJob(argsType));

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

@ -40,7 +40,7 @@ public class HangfireJobExecutionAdapter<TArgs>
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
var jobConfiguration = Options.GetJob(typeof(TArgs)); var jobConfiguration = Options.GetJob(typeof(TArgs));
var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType!, args!, cancellationToken: cancellationToken, jobName: jobConfiguration.JobName); var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType!, args!, cancellationToken: cancellationToken);
await JobExecuter.ExecuteAsync(context); await JobExecuter.ExecuteAsync(context);
} }
} }
@ -83,7 +83,7 @@ public class HangfireJobExecutionAdapter
{ {
var jobConfiguration = Options.GetJob(jobName); var jobConfiguration = Options.GetJob(jobName);
var args = JsonSerializer.Deserialize(jobConfiguration.ArgsType, serializedArgs); var args = JsonSerializer.Deserialize(jobConfiguration.ArgsType, serializedArgs);
var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType ?? typeof(object), args, cancellationToken: cancellationToken, jobName: jobName); var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, args, cancellationToken: cancellationToken);
await JobExecuter.ExecuteAsync(context); await JobExecuter.ExecuteAsync(context);
} }
} }

17
framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzBackgroundJobManager.cs

@ -19,11 +19,14 @@ public class QuartzBackgroundJobManager : IBackgroundJobManager, ITransientDepen
protected IJsonSerializer JsonSerializer { get; } protected IJsonSerializer JsonSerializer { get; }
public QuartzBackgroundJobManager(IScheduler scheduler, IOptions<AbpBackgroundJobQuartzOptions> options, IJsonSerializer jsonSerializer) protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; }
public QuartzBackgroundJobManager(IScheduler scheduler, IOptions<AbpBackgroundJobQuartzOptions> options, IJsonSerializer jsonSerializer, IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry)
{ {
Scheduler = scheduler; Scheduler = scheduler;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
Options = options.Value; Options = options.Value;
AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry;
} }
public virtual async Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, public virtual async Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
@ -35,9 +38,21 @@ public class QuartzBackgroundJobManager : IBackgroundJobManager, ITransientDepen
public virtual async Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, public virtual async Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null) TimeSpan? delay = null)
{ {
if (ShouldWrapAsAnonymousJob(jobName))
{
var jsonData = JsonSerializer.Serialize(args);
var anonymousArgs = new AnonymousJobArgs(jobName, jsonData);
return await EnqueueAsync(AnonymousJobArgs.JobNameConstant, anonymousArgs, priority, delay);
}
return await ReEnqueueAsync(jobName, args, Options.RetryCount, Options.RetryIntervalMillisecond, priority, delay); return await ReEnqueueAsync(jobName, args, Options.RetryCount, Options.RetryIntervalMillisecond, priority, delay);
} }
protected virtual bool ShouldWrapAsAnonymousJob(string jobName)
{
return jobName != AnonymousJobArgs.JobNameConstant && AnonymousJobHandlerRegistry.IsRegistered(jobName);
}
public virtual async Task<string> ReEnqueueAsync<TArgs>(TArgs args, int retryCount, int retryIntervalMillisecond, public virtual async Task<string> ReEnqueueAsync<TArgs>(TArgs args, int retryCount, int retryIntervalMillisecond,
BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null)
{ {

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

@ -97,7 +97,7 @@ public class QuartzJobExecutionAdapter : IJob
var serializedArgs = context.JobDetail.JobDataMap.GetString(JobArgsKey)!; var serializedArgs = context.JobDetail.JobDataMap.GetString(JobArgsKey)!;
var jobConfiguration = Options.GetJob(jobName); var jobConfiguration = Options.GetJob(jobName);
var args = JsonSerializer.Deserialize(jobConfiguration.ArgsType, serializedArgs); var args = JsonSerializer.Deserialize(jobConfiguration.ArgsType, serializedArgs);
var jobContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType ?? typeof(object), args, cancellationToken: context.CancellationToken, jobName: jobName); var jobContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, args, cancellationToken: context.CancellationToken);
try try
{ {
await JobExecuter.ExecuteAsync(jobContext); await JobExecuter.ExecuteAsync(jobContext);

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

@ -214,9 +214,8 @@ public class JobQueue<TArgs> : IJobQueue<TArgs>
{ {
var context = new JobExecutionContext( var context = new JobExecutionContext(
scope.ServiceProvider, scope.ServiceProvider,
JobConfiguration.JobType ?? typeof(object), JobConfiguration.JobType,
Serializer.Deserialize(ea.Body.ToArray(), typeof(TArgs)), Serializer.Deserialize(ea.Body.ToArray(), typeof(TArgs))
jobName: JobConfiguration.JobName
); );
try try

19
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
namespace Volo.Abp.BackgroundJobs.RabbitMQ; namespace Volo.Abp.BackgroundJobs.RabbitMQ;
@ -8,10 +9,14 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ;
public class RabbitMqBackgroundJobManager : IBackgroundJobManager, ITransientDependency public class RabbitMqBackgroundJobManager : IBackgroundJobManager, ITransientDependency
{ {
protected IJobQueueManager JobQueueManager { get; } protected IJobQueueManager JobQueueManager { get; }
protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; }
protected IJsonSerializer JsonSerializer { get; }
public RabbitMqBackgroundJobManager(IJobQueueManager jobQueueManager) public RabbitMqBackgroundJobManager(IJobQueueManager jobQueueManager, IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry, IJsonSerializer jsonSerializer)
{ {
JobQueueManager = jobQueueManager; JobQueueManager = jobQueueManager;
AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry;
JsonSerializer = jsonSerializer;
} }
public virtual async Task<string> EnqueueAsync<TArgs>( public virtual async Task<string> EnqueueAsync<TArgs>(
@ -29,7 +34,19 @@ public class RabbitMqBackgroundJobManager : IBackgroundJobManager, ITransientDep
BackgroundJobPriority priority = BackgroundJobPriority.Normal, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null) TimeSpan? delay = null)
{ {
if (ShouldWrapAsAnonymousJob(jobName))
{
var jsonData = JsonSerializer.Serialize(args);
var anonymousArgs = new AnonymousJobArgs(jobName, jsonData);
return await EnqueueAsync(AnonymousJobArgs.JobNameConstant, anonymousArgs, priority, delay);
}
var jobQueue = await JobQueueManager.GetAsync(jobName); var jobQueue = await JobQueueManager.GetAsync(jobName);
return (await jobQueue.EnqueueAsync(args, priority, delay))!; return (await jobQueue.EnqueueAsync(args, priority, delay))!;
} }
protected virtual bool ShouldWrapAsAnonymousJob(string jobName)
{
return jobName != AnonymousJobArgs.JobNameConstant && AnonymousJobHandlerRegistry.IsRegistered(jobName);
}
} }

2
framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs

@ -67,7 +67,7 @@ public class AbpBackgroundJobsTickerQModule : AbpModule
var jobExecuter = serviceProvider.GetRequiredService<IBackgroundJobExecuter>(); var jobExecuter = serviceProvider.GetRequiredService<IBackgroundJobExecuter>();
var args = await TickerRequestProvider.GetRequestAsync<TArgs>(context, cancellationToken); var args = await TickerRequestProvider.GetRequestAsync<TArgs>(context, cancellationToken);
var jobConfiguration = options.GetJob(typeof(TArgs)); var jobConfiguration = options.GetJob(typeof(TArgs));
var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType ?? typeof(object), args!, cancellationToken: cancellationToken, jobName: jobConfiguration.JobName); var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, args!, cancellationToken: cancellationToken);
await jobExecuter.ExecuteAsync(jobExecutionContext); await jobExecuter.ExecuteAsync(jobExecutionContext);
} }
}; };

21
framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs

@ -6,6 +6,7 @@ using TickerQ.Utilities;
using TickerQ.Utilities.Entities; using TickerQ.Utilities.Entities;
using TickerQ.Utilities.Interfaces.Managers; using TickerQ.Utilities.Interfaces.Managers;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
namespace Volo.Abp.BackgroundJobs.TickerQ; namespace Volo.Abp.BackgroundJobs.TickerQ;
@ -17,15 +18,21 @@ public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientD
protected ITimeTickerManager<TimeTickerEntity> TimeTickerManager { get; } protected ITimeTickerManager<TimeTickerEntity> TimeTickerManager { get; }
protected AbpBackgroundJobOptions Options { get; } protected AbpBackgroundJobOptions Options { get; }
protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; } protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; }
protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; }
protected IJsonSerializer JsonSerializer { get; }
public AbpTickerQBackgroundJobManager( public AbpTickerQBackgroundJobManager(
ITimeTickerManager<TimeTickerEntity> timeTickerManager, ITimeTickerManager<TimeTickerEntity> timeTickerManager,
IOptions<AbpBackgroundJobOptions> options, IOptions<AbpBackgroundJobOptions> options,
IOptions<AbpBackgroundJobsTickerQOptions> tickerQOptions) IOptions<AbpBackgroundJobsTickerQOptions> tickerQOptions,
IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry,
IJsonSerializer jsonSerializer)
{ {
TimeTickerManager = timeTickerManager; TimeTickerManager = timeTickerManager;
Options = options.Value; Options = options.Value;
TickerQOptions = tickerQOptions.Value; TickerQOptions = tickerQOptions.Value;
AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry;
JsonSerializer = jsonSerializer;
} }
public virtual async Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) public virtual async Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null)
@ -36,10 +43,22 @@ public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientD
public virtual async Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) public virtual async Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null)
{ {
if (ShouldWrapAsAnonymousJob(jobName))
{
var jsonData = JsonSerializer.Serialize(args);
var anonymousArgs = new AnonymousJobArgs(jobName, jsonData);
return await EnqueueAsync(AnonymousJobArgs.JobNameConstant, anonymousArgs, priority, delay);
}
var job = Options.GetJob(jobName); var job = Options.GetJob(jobName);
return await EnqueueAsync(job, args, priority, delay); return await EnqueueAsync(job, args, priority, delay);
} }
protected virtual bool ShouldWrapAsAnonymousJob(string jobName)
{
return jobName != AnonymousJobArgs.JobNameConstant && AnonymousJobHandlerRegistry.IsRegistered(jobName);
}
protected virtual async Task<string> EnqueueAsync(BackgroundJobConfiguration job, object args, BackgroundJobPriority priority, TimeSpan? delay) protected virtual async Task<string> EnqueueAsync(BackgroundJobConfiguration job, object args, BackgroundJobPriority priority, TimeSpan? delay)
{ {
var timeTicker = new TimeTickerEntity var timeTicker = new TimeTickerEntity

35
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs

@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs;
public class AnonymousJobExecutorAsyncBackgroundJob : AsyncBackgroundJob<AnonymousJobArgs>, ITransientDependency
{
protected IAnonymousJobHandlerRegistry HandlerRegistry { get; }
protected IServiceProvider ServiceProvider { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public AnonymousJobExecutorAsyncBackgroundJob(
IAnonymousJobHandlerRegistry handlerRegistry,
IServiceProvider serviceProvider,
ICancellationTokenProvider cancellationTokenProvider)
{
HandlerRegistry = handlerRegistry;
ServiceProvider = serviceProvider;
CancellationTokenProvider = cancellationTokenProvider;
}
public override async Task ExecuteAsync(AnonymousJobArgs args)
{
var handler = HandlerRegistry.Get(args.JobName);
if (handler == null)
{
throw new AbpException("No anonymous job handler registered for: " + args.JobName);
}
await handler(args.JsonData, ServiceProvider, CancellationTokenProvider.Token);
}
}

5
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs

@ -65,10 +65,9 @@ public class BackgroundJobWorker : AsyncPeriodicBackgroundWorkerBase, IBackgroun
var jobArgs = serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType); var jobArgs = serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType);
var context = new JobExecutionContext( var context = new JobExecutionContext(
workerContext.ServiceProvider, workerContext.ServiceProvider,
jobConfiguration.JobType ?? typeof(object), jobConfiguration.JobType,
jobArgs, jobArgs,
workerContext.CancellationToken, workerContext.CancellationToken);
jobName: jobInfo.JobName);
try try
{ {

15
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/DefaultBackgroundJobManager.cs

@ -18,6 +18,7 @@ public class DefaultBackgroundJobManager : IBackgroundJobManager, ITransientDepe
protected IBackgroundJobSerializer Serializer { get; } protected IBackgroundJobSerializer Serializer { get; }
protected IGuidGenerator GuidGenerator { get; } protected IGuidGenerator GuidGenerator { get; }
protected IBackgroundJobStore Store { get; } protected IBackgroundJobStore Store { get; }
protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; }
protected IOptions<AbpBackgroundJobOptions> BackgroundJobOptions { get; } protected IOptions<AbpBackgroundJobOptions> BackgroundJobOptions { get; }
protected IOptions<AbpBackgroundJobWorkerOptions> BackgroundJobWorkerOptions { get; } protected IOptions<AbpBackgroundJobWorkerOptions> BackgroundJobWorkerOptions { get; }
@ -26,12 +27,14 @@ public class DefaultBackgroundJobManager : IBackgroundJobManager, ITransientDepe
IBackgroundJobSerializer serializer, IBackgroundJobSerializer serializer,
IBackgroundJobStore store, IBackgroundJobStore store,
IGuidGenerator guidGenerator, IGuidGenerator guidGenerator,
IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry,
IOptions<AbpBackgroundJobOptions> backgroundJobOptions, IOptions<AbpBackgroundJobOptions> backgroundJobOptions,
IOptions<AbpBackgroundJobWorkerOptions> backgroundJobWorkerOptions) IOptions<AbpBackgroundJobWorkerOptions> backgroundJobWorkerOptions)
{ {
Clock = clock; Clock = clock;
Serializer = serializer; Serializer = serializer;
GuidGenerator = guidGenerator; GuidGenerator = guidGenerator;
AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry;
BackgroundJobOptions = backgroundJobOptions; BackgroundJobOptions = backgroundJobOptions;
BackgroundJobWorkerOptions = backgroundJobWorkerOptions; BackgroundJobWorkerOptions = backgroundJobWorkerOptions;
Store = store; Store = store;
@ -45,6 +48,13 @@ public class DefaultBackgroundJobManager : IBackgroundJobManager, ITransientDepe
public virtual async Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) public virtual async Task<string> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null)
{ {
if (ShouldWrapAsAnonymousJob(jobName))
{
var jsonData = Serializer.Serialize(args);
var anonymousArgs = new AnonymousJobArgs(jobName, jsonData);
return await EnqueueAsync(AnonymousJobArgs.JobNameConstant, anonymousArgs, priority, delay);
}
var jobInfo = new BackgroundJobInfo var jobInfo = new BackgroundJobInfo
{ {
Id = GuidGenerator.Create(), Id = GuidGenerator.Create(),
@ -65,4 +75,9 @@ public class DefaultBackgroundJobManager : IBackgroundJobManager, ITransientDepe
return jobInfo.Id.ToString(); return jobInfo.Id.ToString();
} }
protected virtual bool ShouldWrapAsAnonymousJob(string jobName)
{
return jobName != AnonymousJobArgs.JobNameConstant && AnonymousJobHandlerRegistry.IsRegistered(jobName);
}
} }

8
framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs

@ -13,14 +13,14 @@ public class AbpBackgroundJobsTestModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.AddSingleton<DynamicJobExecutionTracker>(); context.Services.AddSingleton<AnonymousJobExecutionTracker>();
Configure<AbpBackgroundJobOptions>(options => Configure<AbpBackgroundJobOptions>(options =>
{ {
options.AddDynamicJob("TestDynamicJob", context => options.AddAnonymousJobHandler("TestAnonymousJob", (jsonData, sp, ct) =>
{ {
var tracker = context.ServiceProvider.GetRequiredService<DynamicJobExecutionTracker>(); var tracker = sp.GetRequiredService<AnonymousJobExecutionTracker>();
tracker.ExecutedArgs.Add(context.Args); tracker.ExecutedJsonData.Add(jsonData);
return System.Threading.Tasks.Task.CompletedTask; return System.Threading.Tasks.Task.CompletedTask;
}); });
}); });

8
framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AnonymousJobExecutionTracker.cs

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Volo.Abp.BackgroundJobs;
public class AnonymousJobExecutionTracker
{
public List<string> ExecutedJsonData { get; } = new();
}

40
framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs

@ -139,54 +139,52 @@ public class BackgroundJobExecuter_Tests : BackgroundJobsTestBase
} }
[Fact] [Fact]
public async Task Should_Execute_Dynamic_Handler() public async Task Should_Execute_Anonymous_Job_Handler()
{ {
var tracker = GetRequiredService<DynamicJobExecutionTracker>(); var tracker = GetRequiredService<AnonymousJobExecutionTracker>();
tracker.ExecutedArgs.ShouldBeEmpty(); tracker.ExecutedJsonData.ShouldBeEmpty();
var args = new Dictionary<string, object> { ["Value"] = "dynamic-42" }; var args = new AnonymousJobArgs("TestAnonymousJob", "{\"OrderId\":\"ORD-001\"}");
await _backgroundJobExecuter.ExecuteAsync( await _backgroundJobExecuter.ExecuteAsync(
new JobExecutionContext( new JobExecutionContext(
ServiceProvider, ServiceProvider,
typeof(object), typeof(AnonymousJobExecutorAsyncBackgroundJob),
args, args
jobName: "TestDynamicJob"
) )
); );
tracker.ExecutedArgs.Count.ShouldBe(1); tracker.ExecutedJsonData.Count.ShouldBe(1);
tracker.ExecutedArgs[0]["Value"].ShouldBe("dynamic-42"); tracker.ExecutedJsonData[0].ShouldContain("ORD-001");
} }
[Fact] [Fact]
public async Task Should_Execute_Dynamic_Handler_Registered_At_Runtime() public async Task Should_Execute_Anonymous_Job_Handler_Registered_At_Runtime()
{ {
var handlerProvider = GetRequiredService<IDynamicBackgroundJobHandlerProvider>(); var handlerRegistry = GetRequiredService<IAnonymousJobHandlerRegistry>();
var executedValues = new List<string>(); var executedValues = new List<string>();
handlerProvider.Register("RuntimeDynamicJob", context => handlerRegistry.Register("RuntimeAnonymousJob", (jsonData, sp, ct) =>
{ {
executedValues.Add(context.Args["Message"]?.ToString()!); executedValues.Add(jsonData);
return Task.CompletedTask; return Task.CompletedTask;
}); });
var args = new Dictionary<string, object> { ["Message"] = "hello-runtime" }; var args = new AnonymousJobArgs("RuntimeAnonymousJob", "{\"Message\":\"hello-runtime\"}");
await _backgroundJobExecuter.ExecuteAsync( await _backgroundJobExecuter.ExecuteAsync(
new JobExecutionContext( new JobExecutionContext(
ServiceProvider, ServiceProvider,
typeof(object), typeof(AnonymousJobExecutorAsyncBackgroundJob),
args, args
jobName: "RuntimeDynamicJob"
) )
); );
executedValues.Count.ShouldBe(1); executedValues.Count.ShouldBe(1);
executedValues[0].ShouldBe("hello-runtime"); executedValues[0].ShouldContain("hello-runtime");
handlerProvider.IsRegistered("RuntimeDynamicJob").ShouldBeTrue(); handlerRegistry.IsRegistered("RuntimeAnonymousJob").ShouldBeTrue();
handlerProvider.Unregister("RuntimeDynamicJob").ShouldBeTrue(); handlerRegistry.Unregister("RuntimeAnonymousJob").ShouldBeTrue();
handlerProvider.IsRegistered("RuntimeDynamicJob").ShouldBeFalse(); handlerRegistry.IsRegistered("RuntimeAnonymousJob").ShouldBeFalse();
} }
} }

10
framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobManager_Tests.cs

@ -64,17 +64,15 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase
} }
[Fact] [Fact]
public async Task Should_Store_Dynamic_Jobs() public async Task Should_Store_Anonymous_Jobs()
{ {
var jobIdAsString = await _backgroundJobManager.EnqueueAsync("TestDynamicJob", (object)new Dictionary<string, object> var jobIdAsString = await _backgroundJobManager.EnqueueAsync("TestAnonymousJob", new { OrderId = "ORD-001" });
{
["OrderId"] = "ORD-001"
});
jobIdAsString.ShouldNotBe(default); jobIdAsString.ShouldNotBe(default);
var jobInfo = await _backgroundJobStore.FindAsync(Guid.Parse(jobIdAsString)); var jobInfo = await _backgroundJobStore.FindAsync(Guid.Parse(jobIdAsString));
jobInfo.ShouldNotBeNull(); jobInfo.ShouldNotBeNull();
jobInfo.JobName.ShouldBe("TestDynamicJob"); jobInfo.JobName.ShouldBe(AnonymousJobArgs.JobNameConstant);
jobInfo.JobArgs.ShouldContain("TestAnonymousJob");
jobInfo.JobArgs.ShouldContain("ORD-001"); jobInfo.JobArgs.ShouldContain("ORD-001");
} }
} }

8
framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/DynamicJobExecutionTracker.cs

@ -1,8 +0,0 @@
using System.Collections.Generic;
namespace Volo.Abp.BackgroundJobs;
public class DynamicJobExecutionTracker
{
public List<Dictionary<string, object>> ExecutedArgs { get; } = new();
}

9
modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs; using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs;
@ -14,11 +15,11 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared
{ {
Configure<AbpBackgroundJobOptions>(options => Configure<AbpBackgroundJobOptions>(options =>
{ {
options.AddDynamicJob("CompileTimeDynamicJob", dynamicContext => options.AddAnonymousJobHandler("CompileTimeAnonymousJob", (jsonData, sp, ct) =>
{ {
dynamicContext.Args.TryGetValue("Value", out var valueObj); var doc = JsonDocument.Parse(jsonData);
var value = valueObj?.ToString(); var value = doc.RootElement.TryGetProperty("Value", out var prop) ? prop.GetString() : null;
Console.WriteLine($"[DYNAMIC-COMPILE] {value}"); Console.WriteLine($"[ANONYMOUS-COMPILE] {value}");
return Task.CompletedTask; return Task.CompletedTask;
}); });
}); });

27
modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs

@ -1,5 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -9,14 +9,14 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs
public class SampleJobCreator : ITransientDependency public class SampleJobCreator : ITransientDependency
{ {
private readonly IBackgroundJobManager _backgroundJobManager; private readonly IBackgroundJobManager _backgroundJobManager;
private readonly IDynamicBackgroundJobHandlerProvider _dynamicBackgroundJobHandlerProvider; private readonly IAnonymousJobHandlerRegistry _anonymousJobHandlerRegistry;
public SampleJobCreator( public SampleJobCreator(
IBackgroundJobManager backgroundJobManager, IBackgroundJobManager backgroundJobManager,
IDynamicBackgroundJobHandlerProvider dynamicBackgroundJobHandlerProvider) IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry)
{ {
_backgroundJobManager = backgroundJobManager; _backgroundJobManager = backgroundJobManager;
_dynamicBackgroundJobHandlerProvider = dynamicBackgroundJobHandlerProvider; _anonymousJobHandlerRegistry = anonymousJobHandlerRegistry;
} }
public void CreateJobs() public void CreateJobs()
@ -26,10 +26,11 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs
public async Task CreateJobsAsync() public async Task CreateJobsAsync()
{ {
_dynamicBackgroundJobHandlerProvider.Register("RuntimeDynamicJob", context => _anonymousJobHandlerRegistry.Register("RuntimeAnonymousJob", (jsonData, sp, ct) =>
{ {
context.Args.TryGetValue("Value", out var valueObj); var doc = JsonDocument.Parse(jsonData);
Console.WriteLine($"[DYNAMIC-RUNTIME] {valueObj}"); var value = doc.RootElement.TryGetProperty("Value", out var prop) ? prop.GetString() : null;
Console.WriteLine($"[ANONYMOUS-RUNTIME] {value}");
return Task.CompletedTask; return Task.CompletedTask;
}); });
@ -57,16 +58,16 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs
(object)new { Value = "test 3 (yellow) - by name, anonymous", Time = DateTime.Now } (object)new { Value = "test 3 (yellow) - by name, anonymous", Time = DateTime.Now }
); );
// Dynamic enqueue (compile-time and runtime handlers) // Anonymous job enqueue (compile-time and runtime handlers)
if (!_backgroundJobManager.GetType().Name.Contains("RabbitMq", StringComparison.OrdinalIgnoreCase)) if (!_backgroundJobManager.GetType().Name.ToUpperInvariant().Contains("RABBITMQ"))
{ {
await _backgroundJobManager.EnqueueAsync( await _backgroundJobManager.EnqueueAsync(
"CompileTimeDynamicJob", "CompileTimeAnonymousJob",
(object)new Dictionary<string, object> { ["Value"] = "test 4 (dynamic) - compile-time" } new { Value = "test 4 (anonymous) - compile-time" }
); );
await _backgroundJobManager.EnqueueAsync( await _backgroundJobManager.EnqueueAsync(
"RuntimeDynamicJob", "RuntimeAnonymousJob",
(object)new Dictionary<string, object> { ["Value"] = "test 5 (dynamic) - runtime" } new { Value = "test 5 (anonymous) - runtime" }
); );
} }
} }

25
modules/background-jobs/app/docker-compose.yml

@ -0,0 +1,25 @@
services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "YourStrong!Passw0rd"
ports:
- "1433:1433"
volumes:
- sqlserver-data:/var/opt/mssql
rabbitmq:
image: rabbitmq:4-management
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: "guest"
RABBITMQ_DEFAULT_PASS: "guest"
volumes:
- rabbitmq-data:/var/lib/rabbitmq
volumes:
sqlserver-data:
rabbitmq-data:
Loading…
Cancel
Save