From 70c333ad97a564f5c1a62f869f1b449a2cebf097 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 20 Mar 2026 18:06:33 +0800 Subject: [PATCH] Refactor background job execution and management --- .../background-jobs/hangfire.md | 2 - .../infrastructure/background-jobs/index.md | 147 +++++++++--------- .../BackgroundJobs/AbpBackgroundJobOptions.cs | 39 +---- .../AnonymousJobExecutorAsyncBackgroundJob.cs | 12 +- .../AnonymousJobHandlerRegistry.cs | 62 -------- .../BackgroundJobs/BackgroundJobExecuter.cs | 16 +- .../DefaultDynamicBackgroundJobManager.cs | 112 +++++++++++++ ...> DynamicBackgroundJobExecutionContext.cs} | 4 +- .../DynamicBackgroundJobHandler.cs | 9 ++ .../DynamicBackgroundJobHandlerRegistry.cs | 40 +++++ .../IAnonymousJobHandlerRegistry.cs | 17 -- .../BackgroundJobs/IBackgroundJobManager.cs | 15 -- .../IDynamicBackgroundJobHandlerRegistry.cs | 12 ++ .../IDynamicBackgroundJobManager.cs | 51 ++++++ .../NullBackgroundJobManager.cs | 6 - .../Hangfire/AbpDashboardOptionsProvider.cs | 53 ------- .../Hangfire/HangfireBackgroundJobManager.cs | 62 +------- .../Hangfire/HangfireJobExecutionAdapter.cs | 48 +----- .../Quartz/AbpBackgroundJobsQuartzModule.cs | 1 - .../Quartz/QuartzBackgroundJobManager.cs | 59 +------ .../Quartz/QuartzJobExecutionAdapter.cs | 61 +------- .../Abp/BackgroundJobs/RabbitMQ/IJobQueue.cs | 11 +- .../RabbitMQ/IJobQueueManager.cs | 2 - .../Abp/BackgroundJobs/RabbitMQ/JobQueue.cs | 14 -- .../RabbitMQ/JobQueueManager.cs | 27 ---- .../RabbitMQ/RabbitMqBackgroundJobManager.cs | 55 +------ .../TickerQ/AbpBackgroundJobsTickerQModule.cs | 4 +- .../TickerQ/AbpTickerQBackgroundJobManager.cs | 68 +------- .../DefaultBackgroundJobManager.cs | 33 +--- .../AbpBackgroundJobsTestModule.cs | 16 +- .../BackgroundJobExecuter_Tests.cs | 51 ------ .../BackgroundJobManager_Tests.cs | 38 +++-- .../DemoAppSharedModule.cs | 29 ++-- .../Jobs/SampleJobCreator.cs | 47 +++--- 34 files changed, 419 insertions(+), 804 deletions(-) delete mode 100644 framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobHandlerRegistry.cs create mode 100644 framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DefaultDynamicBackgroundJobManager.cs rename framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/{AnonymousJobExecutionContext.cs => DynamicBackgroundJobExecutionContext.cs} (83%) create mode 100644 framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandler.cs create mode 100644 framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerRegistry.cs delete mode 100644 framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAnonymousJobHandlerRegistry.cs create mode 100644 framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerRegistry.cs create mode 100644 framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobManager.cs diff --git a/docs/en/framework/infrastructure/background-jobs/hangfire.md b/docs/en/framework/infrastructure/background-jobs/hangfire.md index 3ea466d83b..05cd214016 100644 --- a/docs/en/framework/infrastructure/background-jobs/hangfire.md +++ b/docs/en/framework/infrastructure/background-jobs/hangfire.md @@ -149,8 +149,6 @@ namespace MyProject Hangfire Dashboard provides information about your background jobs, including method names and serialized arguments as well as gives you an opportunity to manage them by performing different actions – retry, delete, trigger, etc. So it is important to restrict access to the Dashboard. To make it secure by default, only local requests are allowed, however you can change this by following the [official documentation](http://docs.hangfire.io/en/latest/configuration/using-dashboard.html) of Hangfire. -When you enqueue jobs via anonymous handlers (`IBackgroundJobManager.EnqueueAsync(string jobName, object args)` + `IAnonymousJobHandlerRegistry`), ABP uses an internal transport job name (`Abp.AnonymousJob`), but the Hangfire dashboard display name tries to show the effective job name from payload (for example, `ProcessOrder`). - You can integrate the Hangfire dashboard to [ABP authorization system](../../fundamentals/authorization/index.md) using the **AbpHangfireAuthorizationFilter** class. This class is defined in the `Volo.Abp.Hangfire` package. The following example, checks if the current user is logged in to the application: diff --git a/docs/en/framework/infrastructure/background-jobs/index.md b/docs/en/framework/infrastructure/background-jobs/index.md index f581985df9..c4393c0d2d 100644 --- a/docs/en/framework/infrastructure/background-jobs/index.md +++ b/docs/en/framework/infrastructure/background-jobs/index.md @@ -197,76 +197,6 @@ Enqueue method gets some optional arguments to control the background job: * **priority** is used to control priority of the job item. It gets an `BackgroundJobPriority` enum which has `Low`, `BelowNormal`, `Normal` (default), `AboveNormal` and `Hight` fields. * **delay** is used to wait a while (`TimeSpan`) before first try. -### Enqueue by ABP Job Name - -You can also enqueue jobs by their ABP background job name at runtime: - -```csharp -var jobName = BackgroundJobNameAttribute.GetName(); // "emails" if configured via [BackgroundJobName] - -await _backgroundJobManager.EnqueueAsync( - jobName, - new - { - EmailAddress = "user@abp.io", - Subject = "Welcome", - Body = "..." - } -); -``` - -`jobName` here is the ABP background job name (configuration key), not a provider queue name (like a Hangfire queue or RabbitMQ queue). - -In this case, ABP resolves the target job configuration by `jobName` and serializes the `args` object. -If the `args` runtime type does not match the configured argument type, ABP normalizes the payload by serializing and deserializing it to the expected argument type. - -### Anonymous Job Handlers - -ABP supports registering runtime-resolved anonymous handlers keyed by a job name. - -You can register handlers at startup: - -```csharp -Configure(options => -{ - options.AddAnonymousJobHandler("ProcessOrder", (context, cancellationToken) => - { - // Parse or deserialize context.JsonData and run your logic. - return Task.CompletedTask; - }); -}); -``` - -You can also register/unregister handlers at runtime: - -```csharp -public class MyService : ITransientDependency -{ - private readonly IAnonymousJobHandlerRegistry _anonymousJobHandlerRegistry; - - public MyService(IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry) - { - _anonymousJobHandlerRegistry = anonymousJobHandlerRegistry; - } - - public void Register() - { - _anonymousJobHandlerRegistry.Register("ProcessOrder", (context, cancellationToken) => - { - return Task.CompletedTask; - }); - } -} -``` - -Then enqueue it by name: - -```csharp -await _backgroundJobManager.EnqueueAsync("ProcessOrder", new { OrderId = "42" }); -``` - -ABP keeps a stable internal transport job name (`Abp.AnonymousJob`) for provider compatibility, while preserving your effective job name (`ProcessOrder`) in the payload. - ### Disable Job Execution You may want to disable background job execution for your application. This is generally needed if you want to execute background jobs in another process and disable it for the current process. @@ -416,6 +346,83 @@ If you don't want to use a distributed lock provider, you may go with the follow * Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). * Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance. +## Dynamic Background Jobs + +ABP provides `IDynamicBackgroundJobManager` for scenarios where you need to enqueue jobs by name at runtime, without requiring a strongly-typed job args class at compile time. This is useful for plugin systems, dynamic workflows, or any case where job types are not known ahead of time. + +### Enqueue by Job Name (Typed Job) + +If a typed job is already registered (e.g., via `[BackgroundJobName("emails")]`), you can enqueue it by name: + +````csharp +public class MyService : ApplicationService +{ + private readonly IDynamicBackgroundJobManager _dynamicJobManager; + + public MyService(IDynamicBackgroundJobManager dynamicJobManager) + { + _dynamicJobManager = dynamicJobManager; + } + + public async Task DoSomethingAsync() + { + await _dynamicJobManager.EnqueueAsync("emails", new + { + EmailAddress = "user@abp.io", + Subject = "Hello", + Body = "World" + }); + } +} +```` + +The `IDynamicBackgroundJobManager` will look up the typed job configuration, deserialize the args to the expected type, and enqueue through the standard typed pipeline. + +### Dynamic Job Handlers + +You can also register dynamic handlers at runtime for jobs that don't have a pre-defined typed job class: + +````csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + var dynamicJobManager = context.ServiceProvider + .GetRequiredService(); + + dynamicJobManager.RegisterHandler("ProcessOrder", async (context, ct) => + { + var json = context.JsonData; + var serviceProvider = context.ServiceProvider; + // Process the order using JsonData and resolved services... + }); +} +```` + +Then enqueue jobs using the registered name: + +````csharp +await _dynamicJobManager.EnqueueAsync("ProcessOrder", new +{ + OrderId = "ORD-001", + Amount = 99.99 +}); +```` + +### Handler Management + +````csharp +// Check if a handler is registered +bool exists = _dynamicJobManager.IsHandlerRegistered("ProcessOrder"); + +// Unregister a handler +bool removed = _dynamicJobManager.UnregisterHandler("ProcessOrder"); +```` + +### How It Works + +- **Typed job path**: When the job name matches a registered typed job configuration, the args are serialized to JSON and deserialized to the expected args type, then enqueued through `IBackgroundJobManager.EnqueueAsync`. +- **Dynamic handler path**: When the job name matches a registered dynamic handler, the args are wrapped as `AnonymousJobArgs` (an internal transport type) and enqueued through `IBackgroundJobManager.EnqueueAsync`. When the job executes, the framework looks up the handler by name and invokes it. +- All dynamic jobs go through the **standard typed job pipeline**, which means they work with all providers (Default, Hangfire, Quartz, RabbitMQ, TickerQ) without any provider-specific changes. + ## Integrations Background job system is extensible and you can change the default background job manager with your own implementation or on of the pre-built integrations. 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 f98d2d89f4..8494f5df3e 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,17 +1,13 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; namespace Volo.Abp.BackgroundJobs; public class AbpBackgroundJobOptions { private readonly Dictionary _jobConfigurationsByArgsType; - private readonly ConcurrentDictionary _jobConfigurationsByName; - private readonly ConcurrentDictionary> _anonymousHandlers = new(); + private readonly Dictionary _jobConfigurationsByName; /// /// Default: true. @@ -27,7 +23,7 @@ public class AbpBackgroundJobOptions public AbpBackgroundJobOptions() { _jobConfigurationsByArgsType = new Dictionary(); - _jobConfigurationsByName = new ConcurrentDictionary(); + _jobConfigurationsByName = new Dictionary(); GetBackgroundJobName = BackgroundJobNameAttribute.GetName; } @@ -62,7 +58,7 @@ public class AbpBackgroundJobOptions public BackgroundJobConfiguration? GetJobOrNull(string name) { - return _jobConfigurationsByName.TryGetValue(name, out var config) ? config : null; + return _jobConfigurationsByName.GetOrDefault(name); } public IReadOnlyList GetJobs() @@ -85,33 +81,4 @@ public class AbpBackgroundJobOptions _jobConfigurationsByArgsType[jobConfiguration.ArgsType] = jobConfiguration; _jobConfigurationsByName[jobConfiguration.JobName] = jobConfiguration; } - - public void AddAnonymousJobHandler(string jobName, Func handler) - { - Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); - Check.NotNull(handler, nameof(handler)); - - _anonymousHandlers[jobName] = handler; - } - - public void AddAnonymousJobHandler(string jobName, Action handler) - { - Check.NotNull(handler, nameof(handler)); - - AddAnonymousJobHandler(jobName, (context, ct) => - { - handler(context, ct); - return Task.CompletedTask; - }); - } - - internal bool TryGetAnonymousHandler(string jobName, out Func? handler) - { - return _anonymousHandlers.TryGetValue(jobName, out handler); - } - - internal bool IsAnonymousJobRegistered(string jobName) - { - return _anonymousHandlers.ContainsKey(jobName); - } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs index e06b092fe9..e3c95996d0 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs @@ -9,11 +9,11 @@ namespace Volo.Abp.BackgroundJobs; public class AnonymousJobExecutorAsyncBackgroundJob : AsyncBackgroundJob, ITransientDependency { - protected IAnonymousJobHandlerRegistry HandlerRegistry { get; } + protected IDynamicBackgroundJobHandlerRegistry HandlerRegistry { get; } protected IServiceProvider ServiceProvider { get; } public AnonymousJobExecutorAsyncBackgroundJob( - IAnonymousJobHandlerRegistry handlerRegistry, + IDynamicBackgroundJobHandlerRegistry handlerRegistry, IServiceProvider serviceProvider) { HandlerRegistry = handlerRegistry; @@ -23,7 +23,7 @@ public class AnonymousJobExecutorAsyncBackgroundJob : AsyncBackgroundJob().Token; - var executionContext = new AnonymousJobExecutionContext(args.JobName, args.JsonData, ServiceProvider); + var executionContext = new DynamicBackgroundJobExecutionContext(args.JobName, args.JsonData, ServiceProvider); await handler(executionContext, cancellationToken); } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobHandlerRegistry.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobHandlerRegistry.cs deleted file mode 100644 index 24810d7de9..0000000000 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobHandlerRegistry.cs +++ /dev/null @@ -1,62 +0,0 @@ -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> _handlers = new(); - private readonly AbpBackgroundJobOptions _options; - - public AnonymousJobHandlerRegistry(IOptions options) - { - _options = options.Value; - } - - public virtual void Register(string jobName, Func handler) - { - Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); - Check.NotNull(handler, nameof(handler)); - - _handlers[jobName] = handler; - } - - public virtual void Register(string jobName, Action handler) - { - Check.NotNull(handler, nameof(handler)); - - Register(jobName, (context, ct) => - { - handler(context, ct); - return Task.CompletedTask; - }); - } - - public virtual bool Unregister(string jobName) - { - Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); - return _handlers.TryRemove(jobName, out _); - } - - public virtual bool IsRegistered(string jobName) - { - Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); - return _handlers.ContainsKey(jobName) || _options.IsAnonymousJobRegistered(jobName); - } - - public virtual Func? Get(string jobName) - { - Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); - - if (_handlers.TryGetValue(jobName, out var handler)) - { - return handler; - } - - return _options.TryGetAnonymousHandler(jobName, out handler) ? handler : null; - } -} 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 0170b881df..e8a840035f 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 @@ -16,7 +16,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,11 +28,6 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc } public virtual async Task ExecuteAsync(JobExecutionContext context) - { - await ExecuteTypedHandlerAsync(context); - } - - protected virtual async Task ExecuteTypedHandlerAsync(JobExecutionContext context) { var job = context.ServiceProvider.GetService(context.JobType); if (job == null) @@ -50,7 +45,7 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc try { - using (CurrentTenant.Change(GetJobArgsTenantId(context.JobArgs))) + using(CurrentTenant.Change(GetJobArgsTenantId(context.JobArgs))) { var cancellationTokenProvider = context.ServiceProvider.GetRequiredService(); @@ -59,14 +54,15 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc { if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob.ExecuteAsync)) { - await ((Task)jobExecuteMethod.Invoke(job, [context.JobArgs])!); + await ((Task)jobExecuteMethod.Invoke(job, new[] { context.JobArgs })!); } else { - jobExecuteMethod.Invoke(job, [context.JobArgs]); + jobExecuteMethod.Invoke(job, new[] { context.JobArgs }); } } } + } catch (Exception ex) { @@ -83,7 +79,7 @@ public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependenc }; } } - + protected virtual Guid? GetJobArgsTenantId(object jobArgs) { return jobArgs switch diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DefaultDynamicBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DefaultDynamicBackgroundJobManager.cs new file mode 100644 index 0000000000..4d462b8529 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DefaultDynamicBackgroundJobManager.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.BackgroundJobs; + +public class DefaultDynamicBackgroundJobManager : IDynamicBackgroundJobManager, ITransientDependency +{ + private static readonly ConcurrentDictionary EnqueueMethodCache = new(); + + protected IBackgroundJobManager BackgroundJobManager { get; } + protected IDynamicBackgroundJobHandlerRegistry HandlerRegistry { get; } + protected AbpBackgroundJobOptions BackgroundJobOptions { get; } + protected IJsonSerializer JsonSerializer { get; } + + public DefaultDynamicBackgroundJobManager( + IBackgroundJobManager backgroundJobManager, + IDynamicBackgroundJobHandlerRegistry handlerRegistry, + IOptions backgroundJobOptions, + IJsonSerializer jsonSerializer) + { + BackgroundJobManager = backgroundJobManager; + HandlerRegistry = handlerRegistry; + BackgroundJobOptions = backgroundJobOptions.Value; + JsonSerializer = jsonSerializer; + } + + public virtual async Task EnqueueAsync( + string jobName, + object args, + BackgroundJobPriority priority = BackgroundJobPriority.Normal, + TimeSpan? delay = null) + { + Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); + Check.NotNull(args, nameof(args)); + + var jobConfiguration = BackgroundJobOptions.GetJobOrNull(jobName); + if (jobConfiguration != null) + { + return await EnqueueTypedJobAsync(jobConfiguration, args, priority, delay); + } + + if (HandlerRegistry.IsRegistered(jobName)) + { + return await EnqueueAnonymousJobAsync(jobName, args, priority, delay); + } + + throw new AbpException( + $"No typed job configuration or dynamic handler registered for job name: {jobName}"); + } + + public virtual void RegisterHandler( + string jobName, + DynamicBackgroundJobHandler handler) + { + HandlerRegistry.Register(jobName, handler); + } + + public virtual bool UnregisterHandler(string jobName) + { + return HandlerRegistry.Unregister(jobName); + } + + public virtual bool IsHandlerRegistered(string jobName) + { + return HandlerRegistry.IsRegistered(jobName); + } + + protected virtual async Task EnqueueTypedJobAsync( + BackgroundJobConfiguration jobConfiguration, + object args, + BackgroundJobPriority priority, + TimeSpan? delay) + { + var argsType = jobConfiguration.ArgsType; + + // Normalize args to the expected type via JSON round-trip + var json = JsonSerializer.Serialize(args); + var typedArgs = JsonSerializer.Deserialize(argsType, json); + + var enqueueMethod = GetOrCreateEnqueueMethod(argsType); + var task = (Task)enqueueMethod.Invoke(BackgroundJobManager, [typedArgs, priority, delay])!; + return await task; + } + + protected virtual Task EnqueueAnonymousJobAsync( + string jobName, + object args, + BackgroundJobPriority priority, + TimeSpan? delay) + { + var jsonData = JsonSerializer.Serialize(args); + var anonymousArgs = new AnonymousJobArgs(jobName, jsonData); + return BackgroundJobManager.EnqueueAsync(anonymousArgs, priority, delay); + } + + private static MethodInfo GetOrCreateEnqueueMethod(Type argsType) + { + return EnqueueMethodCache.GetOrAdd(argsType, static type => + { + var method = typeof(IBackgroundJobManager).GetMethod( + nameof(IBackgroundJobManager.EnqueueAsync), + BindingFlags.Public | BindingFlags.Instance); + + return method!.MakeGenericMethod(type); + }); + } +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutionContext.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobExecutionContext.cs similarity index 83% rename from framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutionContext.cs rename to framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobExecutionContext.cs index 42fb470c6a..6b207cac97 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutionContext.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobExecutionContext.cs @@ -2,7 +2,7 @@ using System; namespace Volo.Abp.BackgroundJobs; -public class AnonymousJobExecutionContext +public class DynamicBackgroundJobExecutionContext { public string JobName { get; } @@ -10,7 +10,7 @@ public class AnonymousJobExecutionContext public IServiceProvider ServiceProvider { get; } - public AnonymousJobExecutionContext( + public DynamicBackgroundJobExecutionContext( string jobName, string jsonData, IServiceProvider serviceProvider) diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandler.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandler.cs new file mode 100644 index 0000000000..f1f9d17602 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandler.cs @@ -0,0 +1,9 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.BackgroundJobs; + +/// +/// Represents a handler delegate for dynamic background jobs. +/// +public delegate Task DynamicBackgroundJobHandler(DynamicBackgroundJobExecutionContext context, CancellationToken cancellationToken); diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerRegistry.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerRegistry.cs new file mode 100644 index 0000000000..ce5710dd14 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/DynamicBackgroundJobHandlerRegistry.cs @@ -0,0 +1,40 @@ +using System.Collections.Concurrent; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BackgroundJobs; + +public class DynamicBackgroundJobHandlerRegistry : IDynamicBackgroundJobHandlerRegistry, ISingletonDependency +{ + protected ConcurrentDictionary Handlers { get; } + + public DynamicBackgroundJobHandlerRegistry() + { + Handlers = new ConcurrentDictionary(); + } + + public virtual void Register(string jobName, DynamicBackgroundJobHandler handler) + { + Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); + Check.NotNull(handler, nameof(handler)); + + Handlers[jobName] = handler; + } + + public virtual bool Unregister(string jobName) + { + Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); + return Handlers.TryRemove(jobName, out _); + } + + public virtual bool IsRegistered(string jobName) + { + Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); + return Handlers.ContainsKey(jobName); + } + + public virtual DynamicBackgroundJobHandler? Get(string jobName) + { + Check.NotNullOrWhiteSpace(jobName, nameof(jobName)); + return Handlers.TryGetValue(jobName, out var handler) ? handler : null; + } +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAnonymousJobHandlerRegistry.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAnonymousJobHandlerRegistry.cs deleted file mode 100644 index 7c38631018..0000000000 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAnonymousJobHandlerRegistry.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Volo.Abp.BackgroundJobs; - -public interface IAnonymousJobHandlerRegistry -{ - void Register(string jobName, Func handler); - - void Register(string jobName, Action handler); - - bool Unregister(string jobName); - - bool IsRegistered(string jobName); - - Func? Get(string jobName); -} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJobManager.cs index dd32de559f..9b7ee34842 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJobManager.cs @@ -21,19 +21,4 @@ public interface IBackgroundJobManager BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null ); - - /// - /// Enqueues a job to be executed by its name. - /// - /// Name of the background job. - /// Job arguments. - /// Job priority. - /// Job delay (wait duration before first try). - /// Unique identifier of a background job. - Task EnqueueAsync( - string jobName, - object args, - BackgroundJobPriority priority = BackgroundJobPriority.Normal, - TimeSpan? delay = null - ); } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerRegistry.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerRegistry.cs new file mode 100644 index 0000000000..dff212d65b --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobHandlerRegistry.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.BackgroundJobs; + +public interface IDynamicBackgroundJobHandlerRegistry +{ + void Register(string jobName, DynamicBackgroundJobHandler handler); + + bool Unregister(string jobName); + + bool IsRegistered(string jobName); + + DynamicBackgroundJobHandler? Get(string jobName); +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobManager.cs new file mode 100644 index 0000000000..d053a7a976 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IDynamicBackgroundJobManager.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; + +namespace Volo.Abp.BackgroundJobs; + +/// +/// Manages dynamic background jobs that can be enqueued by name +/// without requiring a strongly-typed job args class at compile time. +/// Also supports registering dynamic job handlers at runtime. +/// +public interface IDynamicBackgroundJobManager +{ + /// + /// Enqueues a job by name with dynamic payload. + /// If a typed job configuration exists for this name, the args will be + /// deserialized to the configured args type and enqueued through the typed pipeline. + /// If a dynamic handler is registered, the args will be wrapped as + /// and enqueued through the standard typed pipeline. + /// + /// Name of the background job. + /// Job arguments (will be serialized to JSON). + /// Job priority. + /// Job delay (wait duration before first try). + /// Unique identifier of a background job. + Task EnqueueAsync( + string jobName, + object args, + BackgroundJobPriority priority = BackgroundJobPriority.Normal, + TimeSpan? delay = null); + + /// + /// Registers a dynamic job handler at runtime. + /// + /// Unique name for the dynamic job. + /// The handler delegate to execute when the job runs. + void RegisterHandler(string jobName, DynamicBackgroundJobHandler handler); + + /// + /// Unregisters a previously registered dynamic job handler. + /// + /// Name of the dynamic job to unregister. + /// True if the handler was found and removed; false otherwise. + bool UnregisterHandler(string jobName); + + /// + /// Checks whether a dynamic handler is registered for the given job name. + /// + /// Name of the dynamic job. + /// True if registered; false otherwise. + bool IsHandlerRegistered(string jobName); +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/NullBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/NullBackgroundJobManager.cs index 602cbef17b..e291ff7c18 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/NullBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/NullBackgroundJobManager.cs @@ -21,10 +21,4 @@ public class NullBackgroundJobManager : IBackgroundJobManager, ISingletonDepende { throw new AbpException("Background job system has not a real implementation. If it's mandatory, use an implementation (either the default provider or a 3rd party implementation). If it's optional, check IBackgroundJobManager.IsAvailable() extension method and act based on it."); } - - public virtual Task EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, - TimeSpan? delay = null) - { - throw new AbpException("Background job system has not a real implementation. If it's mandatory, use an implementation (either the default provider or a 3rd party implementation). If it's optional, check IBackgroundJobManager.IsAvailable() extension method and act based on it."); - } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs index 9a09fd9ce0..989cfb4357 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Text.Json; using System.Threading; using Hangfire; using Microsoft.Extensions.Options; @@ -23,65 +22,13 @@ public class AbpDashboardOptionsProvider : ITransientDependency DisplayNameFunc = (_, job) => { var jobName = job.ToString(); - if (job.Args.Count == 3 && job.Args.Last() is CancellationToken) { - if (job.Args[1] is AnonymousJobArgs anonymousJobArgs) - { - return anonymousJobArgs.JobName; - } - jobName = AbpBackgroundJobOptions.GetJob(job.Args[1].GetType()).JobName; } - if (job.Args.Count == 4 && job.Args.Last() is CancellationToken) - { - if (job.Args[1] is string transportJobName) - { - if (transportJobName == AnonymousJobArgs.JobNameConstant && - job.Args[2] is string serializedArgs && - TryGetEffectiveJobName(serializedArgs, out var effectiveJobName)) - { - return effectiveJobName; - } - - return transportJobName; - } - } - return jobName; } }; } - - protected virtual bool TryGetEffectiveJobName(string serializedArgs, out string effectiveJobName) - { - effectiveJobName = string.Empty; - - try - { - using var document = JsonDocument.Parse(serializedArgs); - if (TryGetJobNameElement(document.RootElement, out var jobNameElement)) - { - var jobName = jobNameElement.GetString(); - if (!string.IsNullOrWhiteSpace(jobName)) - { - effectiveJobName = jobName!; - return true; - } - } - } - catch - { - // Ignore parse errors and fallback to transport job name. - } - - return false; - } - - protected virtual bool TryGetJobNameElement(JsonElement rootElement, out JsonElement jobNameElement) - { - return rootElement.TryGetProperty(nameof(AnonymousJobArgs.JobName), out jobNameElement) - || rootElement.TryGetProperty("jobName", out jobNameElement); - } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs index 9d84f1f329..004a6856ce 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs @@ -3,12 +3,9 @@ using System.Reflection; using System.Threading.Tasks; using Hangfire; using Hangfire.States; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Hangfire; -using Volo.Abp.Json; namespace Volo.Abp.BackgroundJobs.Hangfire; @@ -17,21 +14,11 @@ public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDep { protected IOptions BackgroundJobOptions { get; } protected IOptions HangfireOptions { get; } - protected IJsonSerializer JsonSerializer { get; } - protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; } - public ILogger Logger { get; set; } - public HangfireBackgroundJobManager( - IOptions backgroundJobOptions, - IOptions hangfireOptions, - IJsonSerializer jsonSerializer, - IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry) + public HangfireBackgroundJobManager(IOptions backgroundJobOptions, IOptions hangfireOptions) { BackgroundJobOptions = backgroundJobOptions; HangfireOptions = hangfireOptions; - JsonSerializer = jsonSerializer; - AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry; - Logger = NullLogger.Instance; } public virtual Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, @@ -47,54 +34,9 @@ public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDep )); } - public virtual Task EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, - TimeSpan? delay = null) - { - if (ShouldWrapAsAnonymousJob(jobName)) - { - Logger.LogInformation( - "Wrapping job into anonymous transport. TransportJobName: {TransportJobName}, EffectiveJobName: {EffectiveJobName}", - AnonymousJobArgs.JobNameConstant, - 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 queueName = GetQueueName(jobName); - - return Task.FromResult(delay.HasValue - ? BackgroundJob.Schedule( - adapter => adapter.ExecuteAsync(queueName, jobName, serializedArgs, default), - delay.Value - ) - : BackgroundJob.Enqueue( - adapter => adapter.ExecuteAsync(queueName, jobName, serializedArgs, default) - )); - } - - protected virtual bool ShouldWrapAsAnonymousJob(string jobName) - { - return jobName != AnonymousJobArgs.JobNameConstant && - AnonymousJobHandlerRegistry.IsRegistered(jobName) && - BackgroundJobOptions.Value.GetJobOrNull(jobName) == null; - } - protected virtual string GetQueueName(Type argsType) { - return GetQueueName(BackgroundJobOptions.Value.GetJob(argsType)); - } - - protected virtual string GetQueueName(string jobName) - { - return GetQueueName(BackgroundJobOptions.Value.GetJob(jobName)); - } - - protected virtual string GetQueueName(BackgroundJobConfiguration jobConfiguration) - { - var queueAttribute = jobConfiguration.JobType.GetCustomAttribute(); + var queueAttribute = BackgroundJobOptions.Value.GetJob(argsType).JobType.GetCustomAttribute(); return queueAttribute != null ? HangfireOptions.Value.DefaultQueuePrefix + queueAttribute.Queue : HangfireOptions.Value.DefaultQueue; } } 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 38d6bbdf52..d2ac1ed9cc 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 @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Hangfire; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Volo.Abp.Json; namespace Volo.Abp.BackgroundJobs.Hangfire; @@ -39,51 +38,8 @@ public class HangfireJobExecutionAdapter using (var scope = ServiceScopeFactory.CreateScope()) { - var jobConfiguration = Options.GetJob(typeof(TArgs)); - var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType!, args!, cancellationToken: cancellationToken); - await JobExecuter.ExecuteAsync(context); - } - } -} - -public class HangfireJobExecutionAdapter -{ - protected AbpBackgroundJobOptions Options { get; } - protected IServiceScopeFactory ServiceScopeFactory { get; } - protected IBackgroundJobExecuter JobExecuter { get; } - protected IJsonSerializer JsonSerializer { get; } - - public HangfireJobExecutionAdapter( - IOptions options, - IBackgroundJobExecuter jobExecuter, - IServiceScopeFactory serviceScopeFactory, - IJsonSerializer jsonSerializer) - { - JobExecuter = jobExecuter; - ServiceScopeFactory = serviceScopeFactory; - JsonSerializer = jsonSerializer; - Options = options.Value; - } - - [Queue("{0}")] - public async Task ExecuteAsync(string queue, string jobName, string serializedArgs, CancellationToken cancellationToken = default) - { - if (!Options.IsJobExecutionEnabled) - { - throw new AbpException( - "Background job execution is disabled. " + - "This method should not be called! " + - "If you want to enable the background job execution, " + - $"set {nameof(AbpBackgroundJobOptions)}.{nameof(AbpBackgroundJobOptions.IsJobExecutionEnabled)} to true! " + - "If you've intentionally disabled job execution and this seems a bug, please report it." - ); - } - - using (var scope = ServiceScopeFactory.CreateScope()) - { - var jobConfiguration = Options.GetJob(jobName); - var args = JsonSerializer.Deserialize(jobConfiguration.ArgsType, serializedArgs); - var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, args, cancellationToken: cancellationToken); + var jobType = Options.GetJob(typeof(TArgs)).JobType; + var context = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken); await JobExecuter.ExecuteAsync(context); } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/AbpBackgroundJobsQuartzModule.cs b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/AbpBackgroundJobsQuartzModule.cs index 8e92b4544a..0589518c6f 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/AbpBackgroundJobsQuartzModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/AbpBackgroundJobsQuartzModule.cs @@ -15,7 +15,6 @@ public class AbpBackgroundJobsQuartzModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddTransient(typeof(QuartzJobExecutionAdapter<>)); - context.Services.AddTransient(); } public override void OnPreApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzBackgroundJobManager.cs index a30b8bc9dd..b116eae539 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Quartz/Volo/Abp/BackgroundJobs/Quartz/QuartzBackgroundJobManager.cs @@ -1,7 +1,5 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Quartz; using Volo.Abp.DependencyInjection; @@ -18,26 +16,14 @@ public class QuartzBackgroundJobManager : IBackgroundJobManager, ITransientDepen protected IScheduler Scheduler { get; } protected AbpBackgroundJobQuartzOptions Options { get; } - protected AbpBackgroundJobOptions BackgroundJobOptions { get; } protected IJsonSerializer JsonSerializer { get; } - protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; } - public ILogger Logger { get; set; } - - public QuartzBackgroundJobManager( - IScheduler scheduler, - IOptions options, - IOptions backgroundJobOptions, - IJsonSerializer jsonSerializer, - IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry) + public QuartzBackgroundJobManager(IScheduler scheduler, IOptions options, IJsonSerializer jsonSerializer) { Scheduler = scheduler; JsonSerializer = jsonSerializer; Options = options.Value; - BackgroundJobOptions = backgroundJobOptions.Value; - AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry; - Logger = NullLogger.Instance; } public virtual async Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, @@ -46,31 +32,6 @@ public class QuartzBackgroundJobManager : IBackgroundJobManager, ITransientDepen return await ReEnqueueAsync(args, Options.RetryCount, Options.RetryIntervalMillisecond, priority, delay); } - public virtual async Task EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, - TimeSpan? delay = null) - { - if (ShouldWrapAsAnonymousJob(jobName)) - { - Logger.LogInformation( - "Wrapping job into anonymous transport. TransportJobName: {TransportJobName}, EffectiveJobName: {EffectiveJobName}", - AnonymousJobArgs.JobNameConstant, - 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); - } - - protected virtual bool ShouldWrapAsAnonymousJob(string jobName) - { - return jobName != AnonymousJobArgs.JobNameConstant && - AnonymousJobHandlerRegistry.IsRegistered(jobName) && - BackgroundJobOptions.GetJobOrNull(jobName) == null; - } - public virtual async Task ReEnqueueAsync(TArgs args, int retryCount, int retryIntervalMillisecond, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) { @@ -87,22 +48,4 @@ public class QuartzBackgroundJobManager : IBackgroundJobManager, ITransientDepen await Scheduler.ScheduleJob(jobDetail, trigger); return jobDetail.Key.ToString(); } - - public virtual async Task ReEnqueueAsync(string jobName, object args, int retryCount, int retryIntervalMillisecond, - BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) - { - var jobDataMap = new JobDataMap - { - {QuartzJobExecutionAdapter.JobNameKey, jobName}, - {QuartzJobExecutionAdapter.JobArgsKey, JsonSerializer.Serialize(args)}, - {JobDataPrefix + nameof(Options.RetryCount), retryCount.ToString()}, - {JobDataPrefix + nameof(Options.RetryIntervalMillisecond), retryIntervalMillisecond.ToString()}, - {JobDataPrefix + RetryIndex, "0"} - }; - - var jobDetail = JobBuilder.Create().RequestRecovery().SetJobData(jobDataMap).Build(); - var trigger = !delay.HasValue ? TriggerBuilder.Create().StartNow().Build() : TriggerBuilder.Create().StartAt(new DateTimeOffset(DateTime.Now.Add(delay.Value))).Build(); - await Scheduler.ScheduleJob(jobDetail, trigger); - return jobDetail.Key.ToString(); - } } 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 bba43b4156..640a01d7a0 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,65 +39,8 @@ public class QuartzJobExecutionAdapter : IJob using (var scope = ServiceScopeFactory.CreateScope()) { var args = JsonSerializer.Deserialize(context.JobDetail.JobDataMap.GetString(nameof(TArgs))!); - var jobConfiguration = Options.GetJob(typeof(TArgs)); - var jobContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType!, args!, context.CancellationToken); - try - { - await JobExecuter.ExecuteAsync(jobContext); - } - catch (Exception exception) - { - var jobExecutionException = new JobExecutionException(exception); - - var retryIndex = context.JobDetail.JobDataMap.GetString(QuartzBackgroundJobManager.JobDataPrefix + QuartzBackgroundJobManager.RetryIndex)!.To(); - retryIndex++; - context.JobDetail.JobDataMap.Put(QuartzBackgroundJobManager.JobDataPrefix + QuartzBackgroundJobManager.RetryIndex, retryIndex.ToString()); - - await BackgroundJobQuartzOptions.RetryStrategy.Invoke(retryIndex, context, jobExecutionException); - - throw jobExecutionException; - } - } - } -} - -public class QuartzJobExecutionAdapter : IJob -{ - public const string JobNameKey = "AbpJobName"; - public const string JobArgsKey = "AbpJobArgs"; - - public ILogger Logger { get; set; } - - protected AbpBackgroundJobOptions Options { get; } - protected AbpBackgroundJobQuartzOptions BackgroundJobQuartzOptions { get; } - protected IServiceScopeFactory ServiceScopeFactory { get; } - protected IBackgroundJobExecuter JobExecuter { get; } - protected IJsonSerializer JsonSerializer { get; } - - public QuartzJobExecutionAdapter( - IOptions options, - IOptions backgroundJobQuartzOptions, - IBackgroundJobExecuter jobExecuter, - IServiceScopeFactory serviceScopeFactory, - IJsonSerializer jsonSerializer) - { - JobExecuter = jobExecuter; - ServiceScopeFactory = serviceScopeFactory; - JsonSerializer = jsonSerializer; - Options = options.Value; - BackgroundJobQuartzOptions = backgroundJobQuartzOptions.Value; - Logger = NullLogger.Instance; - } - - public async Task Execute(IJobExecutionContext context) - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var jobName = context.JobDetail.JobDataMap.GetString(JobNameKey)!; - 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 jobType = Options.GetJob(typeof(TArgs)).JobType; + var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: context.CancellationToken); try { await JobExecuter.ExecuteAsync(jobContext); diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueue.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueue.cs index 3b99a68437..f265c488ad 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueue.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueue.cs @@ -4,16 +4,7 @@ using Volo.Abp.Threading; namespace Volo.Abp.BackgroundJobs.RabbitMQ; -public interface IJobQueue : IRunnable, IDisposable -{ - Task EnqueueAsync( - object args, - BackgroundJobPriority priority = BackgroundJobPriority.Normal, - TimeSpan? delay = null - ); -} - -public interface IJobQueue : IJobQueue +public interface IJobQueue : IRunnable, IDisposable { Task EnqueueAsync( TArgs args, diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueueManager.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueueManager.cs index 25339ab544..928e145b44 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueueManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueueManager.cs @@ -6,6 +6,4 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ; public interface IJobQueueManager : IRunnable { Task> GetAsync(); - - Task GetAsync(string jobName); } 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 43250824f3..dff2a4e33a 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 @@ -75,20 +75,6 @@ public class JobQueue : IJobQueue ); } - async Task IJobQueue.EnqueueAsync( - object args, - BackgroundJobPriority priority, - TimeSpan? delay) - { - if (args is TArgs typedArgs) - { - return await EnqueueAsync(typedArgs, priority, delay); - } - - var serializedArgs = Serializer.Serialize(args); - return await EnqueueAsync((TArgs)Serializer.Deserialize(serializedArgs, typeof(TArgs)), priority, delay); - } - public virtual async Task EnqueueAsync( TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueManager.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueManager.cs index 48bd96bc8d..f9c7b4b1fb 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueManager.cs @@ -80,31 +80,4 @@ public class JobQueueManager : IJobQueueManager, ISingletonDependency return (IJobQueue)jobQueue; } } - - public async Task GetAsync(string jobName) - { - if (JobQueues.TryGetValue(jobName, out var jobQueue)) - { - return (IJobQueue)jobQueue; - } - - using (await SyncSemaphore.LockAsync()) - { - if (JobQueues.TryGetValue(jobName, out jobQueue)) - { - return (IJobQueue)jobQueue; - } - - var jobConfiguration = Options.GetJob(jobName); - - jobQueue = (IRunnable)ServiceProvider - .GetRequiredService(typeof(IJobQueue<>).MakeGenericType(jobConfiguration.ArgsType)); - - await jobQueue.StartAsync(); - - JobQueues.TryAdd(jobName, jobQueue); - - return (IJobQueue)jobQueue; - } - } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs index 71510e83ff..79f368a778 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs @@ -1,70 +1,25 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; -using Volo.Abp.Json; namespace Volo.Abp.BackgroundJobs.RabbitMQ; [Dependency(ReplaceServices = true)] public class RabbitMqBackgroundJobManager : IBackgroundJobManager, ITransientDependency { - protected IJobQueueManager JobQueueManager { get; } - protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; } - protected AbpBackgroundJobOptions BackgroundJobOptions { get; } - protected IJsonSerializer JsonSerializer { get; } - public ILogger Logger { get; set; } + private readonly IJobQueueManager _jobQueueManager; - public RabbitMqBackgroundJobManager( - IJobQueueManager jobQueueManager, - IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry, - IOptions backgroundJobOptions, - IJsonSerializer jsonSerializer) + public RabbitMqBackgroundJobManager(IJobQueueManager jobQueueManager) { - JobQueueManager = jobQueueManager; - AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry; - BackgroundJobOptions = backgroundJobOptions.Value; - JsonSerializer = jsonSerializer; - Logger = NullLogger.Instance; + _jobQueueManager = jobQueueManager; } - public virtual async Task EnqueueAsync( + public async Task EnqueueAsync( TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) { - var jobQueue = await JobQueueManager.GetAsync(); + var jobQueue = await _jobQueueManager.GetAsync(); return (await jobQueue.EnqueueAsync(args, priority, delay))!; } - - public virtual async Task EnqueueAsync( - string jobName, - object args, - BackgroundJobPriority priority = BackgroundJobPriority.Normal, - TimeSpan? delay = null) - { - if (ShouldWrapAsAnonymousJob(jobName)) - { - Logger.LogInformation( - "Wrapping job into anonymous transport. TransportJobName: {TransportJobName}, EffectiveJobName: {EffectiveJobName}", - AnonymousJobArgs.JobNameConstant, - 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); - return (await jobQueue.EnqueueAsync(args, priority, delay))!; - } - - protected virtual bool ShouldWrapAsAnonymousJob(string jobName) - { - return jobName != AnonymousJobArgs.JobNameConstant && - AnonymousJobHandlerRegistry.IsRegistered(jobName) && - BackgroundJobOptions.GetJobOrNull(jobName) == null; - } } 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 421fc752e6..4ff3adbfe4 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 @@ -54,8 +54,8 @@ public class AbpBackgroundJobsTickerQModule : AbpModule { var jobExecuter = serviceProvider.GetRequiredService(); var args = await TickerRequestProvider.GetRequestAsync(context, cancellationToken); - var jobConfiguration = options.GetJob(typeof(TArgs)); - var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, args!, cancellationToken: cancellationToken); + var jobType = options.GetJob(typeof(TArgs)).JobType; + var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken); await jobExecuter.ExecuteAsync(jobExecutionContext); } }; diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs index 82df9e2fe7..daeb92cc78 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs @@ -1,85 +1,39 @@ using System; -using System.Reflection; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using TickerQ.Utilities; using TickerQ.Utilities.Entities; using TickerQ.Utilities.Interfaces.Managers; using Volo.Abp.DependencyInjection; -using Volo.Abp.Json; namespace Volo.Abp.BackgroundJobs.TickerQ; [Dependency(ReplaceServices = true)] public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientDependency { - private readonly static MethodInfo CreateTickerRequestMethod = typeof(TickerHelper).GetMethod(nameof(TickerHelper.CreateTickerRequest), BindingFlags.Public | BindingFlags.Static)!; - protected ITimeTickerManager TimeTickerManager { get; } protected AbpBackgroundJobOptions Options { get; } protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; } - protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; } - protected IJsonSerializer JsonSerializer { get; } - public ILogger Logger { get; set; } public AbpTickerQBackgroundJobManager( ITimeTickerManager timeTickerManager, IOptions options, - IOptions tickerQOptions, - IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry, - IJsonSerializer jsonSerializer) + IOptions tickerQOptions) { TimeTickerManager = timeTickerManager; Options = options.Value; TickerQOptions = tickerQOptions.Value; - AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry; - JsonSerializer = jsonSerializer; - Logger = NullLogger.Instance; } public virtual async Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) { var job = Options.GetJob(typeof(TArgs)); - return await EnqueueAsync(job, args!, priority, delay); - } - - public virtual async Task EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) - { - if (ShouldWrapAsAnonymousJob(jobName)) - { - Logger.LogInformation( - "Wrapping job into anonymous transport. TransportJobName: {TransportJobName}, EffectiveJobName: {EffectiveJobName}", - AnonymousJobArgs.JobNameConstant, - 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); - return await EnqueueAsync(job, args, priority, delay); - } - - protected virtual bool ShouldWrapAsAnonymousJob(string jobName) - { - return jobName != AnonymousJobArgs.JobNameConstant && - AnonymousJobHandlerRegistry.IsRegistered(jobName) && - Options.GetJobOrNull(jobName) == null; - } - - protected virtual async Task EnqueueAsync(BackgroundJobConfiguration job, object args, BackgroundJobPriority priority, TimeSpan? delay) - { - var normalizedArgs = NormalizeArgs(job.ArgsType, args); - var timeTicker = new TimeTickerEntity { Id = Guid.NewGuid(), Function = job.JobName, ExecutionTime = delay == null ? DateTime.UtcNow : DateTime.UtcNow.Add(delay.Value), - Request = CreateTickerRequest(job.ArgsType, normalizedArgs), + Request = TickerHelper.CreateTickerRequest(args), }; var config = TickerQOptions.GetConfigurationOrNull(job.JobType); @@ -93,22 +47,4 @@ public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientD var result = await TimeTickerManager.AddAsync(timeTicker); return !result.IsSucceeded ? timeTicker.Id.ToString() : result.Result.Id.ToString(); } - - protected virtual object NormalizeArgs(Type argsType, object args) - { - if (argsType.IsInstanceOfType(args)) - { - return args; - } - - var serialized = JsonSerializer.Serialize(args); - return JsonSerializer.Deserialize(argsType, serialized)!; - } - - protected virtual byte[]? CreateTickerRequest(Type argsType, object args) - { - return (byte[]?)CreateTickerRequestMethod - .MakeGenericMethod(argsType) - .Invoke(null, [args]); - } } diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/DefaultBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/DefaultBackgroundJobManager.cs index 4714e11e72..fb0891d8aa 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/DefaultBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/DefaultBackgroundJobManager.cs @@ -1,7 +1,5 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.BackgroundWorkers; using Volo.Abp.DependencyInjection; @@ -20,50 +18,34 @@ public class DefaultBackgroundJobManager : IBackgroundJobManager, ITransientDepe protected IBackgroundJobSerializer Serializer { get; } protected IGuidGenerator GuidGenerator { get; } protected IBackgroundJobStore Store { get; } - protected IAnonymousJobHandlerRegistry AnonymousJobHandlerRegistry { get; } protected IOptions BackgroundJobOptions { get; } protected IOptions BackgroundJobWorkerOptions { get; } - public ILogger Logger { get; set; } public DefaultBackgroundJobManager( IClock clock, IBackgroundJobSerializer serializer, IBackgroundJobStore store, IGuidGenerator guidGenerator, - IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry, IOptions backgroundJobOptions, IOptions backgroundJobWorkerOptions) { Clock = clock; Serializer = serializer; GuidGenerator = guidGenerator; - AnonymousJobHandlerRegistry = anonymousJobHandlerRegistry; BackgroundJobOptions = backgroundJobOptions; BackgroundJobWorkerOptions = backgroundJobWorkerOptions; Store = store; - Logger = NullLogger.Instance; } public virtual async Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) { var jobName = BackgroundJobOptions.Value.GetBackgroundJobName(typeof(TArgs)); - return await EnqueueAsync(jobName, args!, priority, delay); + var jobId = await EnqueueAsync(jobName, args!, priority, delay); + return jobId.ToString(); } - public virtual async Task EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) + protected virtual async Task EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) { - if (ShouldWrapAsAnonymousJob(jobName)) - { - Logger.LogInformation( - "Wrapping job into anonymous transport. TransportJobName: {TransportJobName}, EffectiveJobName: {EffectiveJobName}", - AnonymousJobArgs.JobNameConstant, - jobName - ); - var jsonData = Serializer.Serialize(args); - var anonymousArgs = new AnonymousJobArgs(jobName, jsonData); - return await EnqueueAsync(AnonymousJobArgs.JobNameConstant, anonymousArgs, priority, delay); - } - var jobInfo = new BackgroundJobInfo { Id = GuidGenerator.Create(), @@ -82,13 +64,6 @@ public class DefaultBackgroundJobManager : IBackgroundJobManager, ITransientDepe await Store.InsertAsync(jobInfo); - return jobInfo.Id.ToString(); - } - - protected virtual bool ShouldWrapAsAnonymousJob(string jobName) - { - return jobName != AnonymousJobArgs.JobNameConstant && - AnonymousJobHandlerRegistry.IsRegistered(jobName) && - BackgroundJobOptions.Value.GetJobOrNull(jobName) == null; + return jobInfo.Id; } } 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 72ada6dbd8..8eaca8928d 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 @@ -14,15 +14,17 @@ public class AbpBackgroundJobsTestModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddSingleton(); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var dynamicJobManager = context.ServiceProvider.GetRequiredService(); + var tracker = context.ServiceProvider.GetRequiredService(); - Configure(options => + dynamicJobManager.RegisterHandler("TestAnonymousJob", (ctx, ct) => { - options.AddAnonymousJobHandler("TestAnonymousJob", (context, ct) => - { - var tracker = context.ServiceProvider.GetRequiredService(); - tracker.ExecutedJsonData.Add(context.JsonData); - return System.Threading.Tasks.Task.CompletedTask; - }); + tracker.ExecutedJsonData.Add(ctx.JsonData); + 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 3b7173942b..db7b5f5cc8 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,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Shouldly; @@ -137,54 +136,4 @@ public class BackgroundJobExecuter_Tests : BackgroundJobsTestBase //Assert asyncJobObject.Canceled.ShouldBeTrue(); } - - [Fact] - public async Task Should_Execute_Anonymous_Job_Handler() - { - var tracker = GetRequiredService(); - tracker.ExecutedJsonData.ShouldBeEmpty(); - - var args = new AnonymousJobArgs("TestAnonymousJob", "{\"OrderId\":\"ORD-001\"}"); - - await _backgroundJobExecuter.ExecuteAsync( - new JobExecutionContext( - ServiceProvider, - typeof(AnonymousJobExecutorAsyncBackgroundJob), - args - ) - ); - - tracker.ExecutedJsonData.Count.ShouldBe(1); - tracker.ExecutedJsonData[0].ShouldContain("ORD-001"); - } - - [Fact] - public async Task Should_Execute_Anonymous_Job_Handler_Registered_At_Runtime() - { - var handlerRegistry = GetRequiredService(); - var executedValues = new List(); - - handlerRegistry.Register("RuntimeAnonymousJob", (context, ct) => - { - executedValues.Add(context.JsonData); - return Task.CompletedTask; - }); - - var args = new AnonymousJobArgs("RuntimeAnonymousJob", "{\"Message\":\"hello-runtime\"}"); - - await _backgroundJobExecuter.ExecuteAsync( - new JobExecutionContext( - ServiceProvider, - typeof(AnonymousJobExecutorAsyncBackgroundJob), - args - ) - ); - - executedValues.Count.ShouldBe(1); - executedValues[0].ShouldContain("hello-runtime"); - - handlerRegistry.IsRegistered("RuntimeAnonymousJob").ShouldBeTrue(); - handlerRegistry.Unregister("RuntimeAnonymousJob").ShouldBeTrue(); - handlerRegistry.IsRegistered("RuntimeAnonymousJob").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 948c1b9f4a..ebfd04c20e 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 @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Shouldly; using Xunit; @@ -9,14 +8,14 @@ namespace Volo.Abp.BackgroundJobs; public class BackgroundJobManager_Tests : BackgroundJobsTestBase { private readonly IBackgroundJobManager _backgroundJobManager; + private readonly IDynamicBackgroundJobManager _dynamicBackgroundJobManager; private readonly IBackgroundJobStore _backgroundJobStore; - private readonly IAnonymousJobHandlerRegistry _anonymousJobHandlerRegistry; public BackgroundJobManager_Tests() { _backgroundJobManager = GetRequiredService(); + _dynamicBackgroundJobManager = GetRequiredService(); _backgroundJobStore = GetRequiredService(); - _anonymousJobHandlerRegistry = GetRequiredService(); } [Fact] @@ -39,7 +38,7 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase public async Task Should_Store_Jobs_With_JobName() { var jobName = BackgroundJobNameAttribute.GetName(); - var jobIdAsString = await _backgroundJobManager.EnqueueAsync(jobName, (object)new + var jobIdAsString = await _dynamicBackgroundJobManager.EnqueueAsync(jobName, new { Value = "42" }); @@ -54,10 +53,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, (object)new Dictionary() - { - ["Value"] = "42" - }); + var jobIdAsString = await _dynamicBackgroundJobManager.EnqueueAsync(jobName, new { Value = "42" }); jobIdAsString.ShouldNotBe(default); var jobInfo = await _backgroundJobStore.FindAsync(Guid.Parse(jobIdAsString)); @@ -68,7 +64,7 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase [Fact] public async Task Should_Store_Anonymous_Jobs() { - var jobIdAsString = await _backgroundJobManager.EnqueueAsync("TestAnonymousJob", new { OrderId = "ORD-001" }); + var jobIdAsString = await _dynamicBackgroundJobManager.EnqueueAsync("TestAnonymousJob", new { OrderId = "ORD-001" }); jobIdAsString.ShouldNotBe(default); var jobInfo = await _backgroundJobStore.FindAsync(Guid.Parse(jobIdAsString)); @@ -82,9 +78,9 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase public async Task Should_Not_Wrap_If_Typed_Job_Exists_For_Same_Name() { var typedJobName = BackgroundJobNameAttribute.GetName(); - _anonymousJobHandlerRegistry.Register(typedJobName, (_, _) => Task.CompletedTask); + _dynamicBackgroundJobManager.RegisterHandler(typedJobName, (_, _) => Task.CompletedTask); - var jobIdAsString = await _backgroundJobManager.EnqueueAsync(typedJobName, new { Value = "42" }); + var jobIdAsString = await _dynamicBackgroundJobManager.EnqueueAsync(typedJobName, new { Value = "42" }); jobIdAsString.ShouldNotBe(default); var jobInfo = await _backgroundJobStore.FindAsync(Guid.Parse(jobIdAsString)); @@ -92,4 +88,24 @@ public class BackgroundJobManager_Tests : BackgroundJobsTestBase jobInfo.JobName.ShouldBe(typedJobName); jobInfo.JobName.ShouldNotBe(AnonymousJobArgs.JobNameConstant); } + + [Fact] + public async Task Should_Throw_For_Unknown_Job_Name() + { + await Assert.ThrowsAsync(() => + _dynamicBackgroundJobManager.EnqueueAsync("NonExistentJob", new { Value = "42" }) + ); + } + + [Fact] + public void Should_Register_And_Unregister_Handler() + { + _dynamicBackgroundJobManager.IsHandlerRegistered("TestDynamic").ShouldBeFalse(); + + _dynamicBackgroundJobManager.RegisterHandler("TestDynamic", (_, _) => Task.CompletedTask); + _dynamicBackgroundJobManager.IsHandlerRegistered("TestDynamic").ShouldBeTrue(); + + _dynamicBackgroundJobManager.UnregisterHandler("TestDynamic").ShouldBeTrue(); + _dynamicBackgroundJobManager.IsHandlerRegistered("TestDynamic").ShouldBeFalse(); + } } 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 7e9e4076ec..3c03e7bac6 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 @@ -11,23 +11,26 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared [DependsOn(typeof(AbpMultiTenancyModule))] public class DemoAppSharedModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) + public override void OnApplicationInitialization(ApplicationInitializationContext context) { - Configure(options => + var dynamicJobManager = context.ServiceProvider.GetService(); + if (dynamicJobManager == null) { - options.AddAnonymousJobHandler("CompileTimeAnonymousJob", (ctx, ct) => + return; + } + + dynamicJobManager.RegisterHandler("CompileTimeAnonymousJob", (ctx, ct) => + { + using (var doc = JsonDocument.Parse(ctx.JsonData)) { - using (var doc = JsonDocument.Parse(ctx.JsonData)) - { - var value = doc.RootElement.TryGetProperty("value", out var prop) + var value = doc.RootElement.TryGetProperty("value", out var prop) + ? prop.GetString() + : doc.RootElement.TryGetProperty("Value", out prop) ? prop.GetString() - : doc.RootElement.TryGetProperty("Value", out prop) - ? prop.GetString() - : null; - Console.WriteLine($"[ANONYMOUS-COMPILE] {value}"); - return Task.CompletedTask; - } - }); + : null; + Console.WriteLine($"[ANONYMOUS-COMPILE] {value}"); + return Task.CompletedTask; + } }); } 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 8fa6c1af88..ac0d662e26 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 @@ -9,14 +9,14 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs public class SampleJobCreator : ITransientDependency { private readonly IBackgroundJobManager _backgroundJobManager; - private readonly IAnonymousJobHandlerRegistry _anonymousJobHandlerRegistry; + private readonly IDynamicBackgroundJobManager? _dynamicBackgroundJobManager; public SampleJobCreator( IBackgroundJobManager backgroundJobManager, - IAnonymousJobHandlerRegistry anonymousJobHandlerRegistry) + IDynamicBackgroundJobManager? dynamicBackgroundJobManager = null) { _backgroundJobManager = backgroundJobManager; - _anonymousJobHandlerRegistry = anonymousJobHandlerRegistry; + _dynamicBackgroundJobManager = dynamicBackgroundJobManager; } public void CreateJobs() @@ -26,7 +26,17 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs public async Task CreateJobsAsync() { - _anonymousJobHandlerRegistry.Register("RuntimeAnonymousJob", (context, ct) => + // Type-safe enqueue (existing) + await _backgroundJobManager.EnqueueAsync(new WriteToConsoleGreenJobArgs { Value = "test 1 (green) - typed" }); + await _backgroundJobManager.EnqueueAsync(new WriteToConsoleYellowJobArgs { Value = "test 1 (yellow) - typed" }); + + if (_dynamicBackgroundJobManager == null) + { + return; + } + + // Register runtime dynamic handler + _dynamicBackgroundJobManager.RegisterHandler("RuntimeAnonymousJob", (context, ct) => { using (var doc = JsonDocument.Parse(context.JsonData)) { @@ -40,35 +50,32 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs } }); - // 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( + // String-based enqueue with typed job (by name) + await _dynamicBackgroundJobManager.EnqueueAsync( "GreenJob", - (object)new WriteToConsoleGreenJobArgs { Value = "test 2 (green) - by name, typed args" } + new WriteToConsoleGreenJobArgs { Value = "test 2 (green) - by name, typed args" } ); - await _backgroundJobManager.EnqueueAsync( + await _dynamicBackgroundJobManager.EnqueueAsync( "YellowJob", - (object)new WriteToConsoleYellowJobArgs { Value = "test 2 (yellow) - by name, typed args" } + new WriteToConsoleYellowJobArgs { Value = "test 2 (yellow) - by name, typed args" } ); - // String-based enqueue with anonymous object - await _backgroundJobManager.EnqueueAsync( + // String-based enqueue with anonymous object (typed job path) + await _dynamicBackgroundJobManager.EnqueueAsync( "GreenJob", - (object)new { Value = "test 3 (green) - by name, anonymous", Time = DateTime.Now } + new { Value = "test 3 (green) - by name, anonymous", Time = DateTime.Now } ); - await _backgroundJobManager.EnqueueAsync( + await _dynamicBackgroundJobManager.EnqueueAsync( "YellowJob", - (object)new { Value = "test 3 (yellow) - by name, anonymous", Time = DateTime.Now } + new { Value = "test 3 (yellow) - by name, anonymous", Time = DateTime.Now } ); - await _backgroundJobManager.EnqueueAsync( + // Dynamic job handlers + await _dynamicBackgroundJobManager.EnqueueAsync( "CompileTimeAnonymousJob", new { Value = "test 4 (anonymous) - compile-time" } ); - await _backgroundJobManager.EnqueueAsync( + await _dynamicBackgroundJobManager.EnqueueAsync( "RuntimeAnonymousJob", new { Value = "test 5 (anonymous) - runtime" } );