Browse Source

Use AnonymousJobExecutionContext for anonymous jobs

Introduce AnonymousJobExecutionContext and switch anonymous job handler APIs to accept it (Func<AnonymousJobExecutionContext, CancellationToken, Task> / Action<AnonymousJobExecutionContext, CancellationToken>). Update AbpBackgroundJobOptions, IAnonymousJobHandlerRegistry, AnonymousJobHandlerRegistry and AnonymousJobExecutorAsyncBackgroundJob to use the new context and to obtain a cancellation token via ICancellationTokenProvider. Update all callsites (tests, demo module, sample job creator) and documentation to show registering/enqueuing anonymous handlers by name and explain Hangfire display behavior. Also add a .cursor hooks state file. The demo no longer skips enqueuing anonymous jobs for RabbitMQ in this change.
pull/25059/head
SALİH ÖZKARA 6 months ago
parent
commit
ffa880b5fc
  1. 2
      docs/en/framework/infrastructure/background-jobs/hangfire.md
  2. 66
      docs/en/framework/infrastructure/background-jobs/index.md
  3. 12
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs
  4. 22
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutionContext.cs
  5. 7
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs
  6. 12
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobHandlerRegistry.cs
  7. 7
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAnonymousJobHandlerRegistry.cs
  8. 6
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/AbpBackgroundJobsTestModule.cs
  9. 4
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs
  10. 4
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs
  11. 24
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Jobs/SampleJobCreator.cs

2
docs/en/framework/infrastructure/background-jobs/hangfire.md

@ -149,6 +149,8 @@ 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 (`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:

66
docs/en/framework/infrastructure/background-jobs/index.md

@ -197,6 +197,72 @@ 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.
### Queue by Job Name
You can also enqueue jobs by their name at runtime:
```csharp
await _backgroundJobManager.EnqueueAsync(
"emails",
new
{
EmailAddress = "user@abp.io",
Subject = "Welcome",
Body = "..."
}
);
```
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<AbpBackgroundJobOptions>(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 (`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.

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

@ -11,7 +11,7 @@ public class AbpBackgroundJobOptions
{
private readonly Dictionary<Type, BackgroundJobConfiguration> _jobConfigurationsByArgsType;
private readonly ConcurrentDictionary<string, BackgroundJobConfiguration> _jobConfigurationsByName;
private readonly ConcurrentDictionary<string, Func<string, IServiceProvider, CancellationToken, Task>> _anonymousHandlers = new();
private readonly ConcurrentDictionary<string, Func<AnonymousJobExecutionContext, CancellationToken, Task>> _anonymousHandlers = new();
/// <summary>
/// Default: true.
@ -86,7 +86,7 @@ public class AbpBackgroundJobOptions
_jobConfigurationsByName[jobConfiguration.JobName] = jobConfiguration;
}
public void AddAnonymousJobHandler(string jobName, Func<string, IServiceProvider, CancellationToken, Task> handler)
public void AddAnonymousJobHandler(string jobName, Func<AnonymousJobExecutionContext, CancellationToken, Task> handler)
{
Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
Check.NotNull(handler, nameof(handler));
@ -94,16 +94,16 @@ public class AbpBackgroundJobOptions
_anonymousHandlers[jobName] = handler;
}
public void AddAnonymousJobHandler(string jobName, Action<string, IServiceProvider, CancellationToken> handler)
public void AddAnonymousJobHandler(string jobName, Action<AnonymousJobExecutionContext, CancellationToken> handler)
{
AddAnonymousJobHandler(jobName, (jsonData, sp, ct) =>
AddAnonymousJobHandler(jobName, (context, ct) =>
{
handler(jsonData, sp, ct);
handler(context, ct);
return Task.CompletedTask;
});
}
internal bool TryGetAnonymousHandler(string jobName, out Func<string, IServiceProvider, CancellationToken, Task>? handler)
internal bool TryGetAnonymousHandler(string jobName, out Func<AnonymousJobExecutionContext, CancellationToken, Task>? handler)
{
return _anonymousHandlers.TryGetValue(jobName, out handler);
}

22
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutionContext.cs

@ -0,0 +1,22 @@
using System;
namespace Volo.Abp.BackgroundJobs;
public class AnonymousJobExecutionContext
{
public string JobName { get; }
public string JsonData { get; }
public IServiceProvider ServiceProvider { get; }
public AnonymousJobExecutionContext(
string jobName,
string jsonData,
IServiceProvider serviceProvider)
{
JobName = Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
JsonData = Check.NotNull(jsonData, nameof(jsonData));
ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider));
}
}

7
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AnonymousJobExecutorAsyncBackgroundJob.cs

@ -1,8 +1,9 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs;
@ -33,6 +34,8 @@ public class AnonymousJobExecutorAsyncBackgroundJob : AsyncBackgroundJob<Anonymo
throw new AbpException("No anonymous job handler registered for: " + args.JobName);
}
await handler(args.JsonData, ServiceProvider, default(CancellationToken));
var cancellationToken = ServiceProvider.GetRequiredService<ICancellationTokenProvider>().Token;
var executionContext = new AnonymousJobExecutionContext(args.JobName, args.JsonData, ServiceProvider);
await handler(executionContext, cancellationToken);
}
}

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

@ -9,7 +9,7 @@ namespace Volo.Abp.BackgroundJobs;
public class AnonymousJobHandlerRegistry : IAnonymousJobHandlerRegistry, ISingletonDependency
{
private readonly ConcurrentDictionary<string, Func<string, IServiceProvider, CancellationToken, Task>> _handlers = new();
private readonly ConcurrentDictionary<string, Func<AnonymousJobExecutionContext, CancellationToken, Task>> _handlers = new();
private readonly AbpBackgroundJobOptions _options;
public AnonymousJobHandlerRegistry(IOptions<AbpBackgroundJobOptions> options)
@ -17,7 +17,7 @@ public class AnonymousJobHandlerRegistry : IAnonymousJobHandlerRegistry, ISingle
_options = options.Value;
}
public virtual void Register(string jobName, Func<string, IServiceProvider, CancellationToken, Task> handler)
public virtual void Register(string jobName, Func<AnonymousJobExecutionContext, CancellationToken, Task> handler)
{
Check.NotNullOrWhiteSpace(jobName, nameof(jobName));
Check.NotNull(handler, nameof(handler));
@ -25,11 +25,11 @@ public class AnonymousJobHandlerRegistry : IAnonymousJobHandlerRegistry, ISingle
_handlers[jobName] = handler;
}
public virtual void Register(string jobName, Action<string, IServiceProvider, CancellationToken> handler)
public virtual void Register(string jobName, Action<AnonymousJobExecutionContext, CancellationToken> handler)
{
Register(jobName, (jsonData, sp, ct) =>
Register(jobName, (context, ct) =>
{
handler(jsonData, sp, ct);
handler(context, ct);
return Task.CompletedTask;
});
}
@ -44,7 +44,7 @@ public class AnonymousJobHandlerRegistry : IAnonymousJobHandlerRegistry, ISingle
return _handlers.ContainsKey(jobName) || _options.IsAnonymousJobRegistered(jobName);
}
public virtual Func<string, IServiceProvider, CancellationToken, Task>? Get(string jobName)
public virtual Func<AnonymousJobExecutionContext, CancellationToken, Task>? Get(string jobName)
{
if (_handlers.TryGetValue(jobName, out var handler))
{

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

@ -1,18 +1,17 @@
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, Func<AnonymousJobExecutionContext, System.Threading.CancellationToken, Task> handler);
void Register(string jobName, Action<string, IServiceProvider, System.Threading.CancellationToken> handler);
void Register(string jobName, Action<AnonymousJobExecutionContext, System.Threading.CancellationToken> handler);
bool Unregister(string jobName);
bool IsRegistered(string jobName);
Func<string, IServiceProvider, System.Threading.CancellationToken, Task>? Get(string jobName);
Func<AnonymousJobExecutionContext, System.Threading.CancellationToken, Task>? Get(string jobName);
}

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

@ -17,10 +17,10 @@ public class AbpBackgroundJobsTestModule : AbpModule
Configure<AbpBackgroundJobOptions>(options =>
{
options.AddAnonymousJobHandler("TestAnonymousJob", (jsonData, sp, ct) =>
options.AddAnonymousJobHandler("TestAnonymousJob", (context, ct) =>
{
var tracker = sp.GetRequiredService<AnonymousJobExecutionTracker>();
tracker.ExecutedJsonData.Add(jsonData);
var tracker = context.ServiceProvider.GetRequiredService<AnonymousJobExecutionTracker>();
tracker.ExecutedJsonData.Add(context.JsonData);
return System.Threading.Tasks.Task.CompletedTask;
});
});

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

@ -164,9 +164,9 @@ public class BackgroundJobExecuter_Tests : BackgroundJobsTestBase
var handlerRegistry = GetRequiredService<IAnonymousJobHandlerRegistry>();
var executedValues = new List<string>();
handlerRegistry.Register("RuntimeAnonymousJob", (jsonData, sp, ct) =>
handlerRegistry.Register("RuntimeAnonymousJob", (context, ct) =>
{
executedValues.Add(jsonData);
executedValues.Add(context.JsonData);
return Task.CompletedTask;
});

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

@ -15,9 +15,9 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared
{
Configure<AbpBackgroundJobOptions>(options =>
{
options.AddAnonymousJobHandler("CompileTimeAnonymousJob", (jsonData, sp, ct) =>
options.AddAnonymousJobHandler("CompileTimeAnonymousJob", (context, ct) =>
{
using var doc = JsonDocument.Parse(jsonData);
using var doc = JsonDocument.Parse(context.JsonData);
var value = doc.RootElement.TryGetProperty("value", out var prop)
? prop.GetString()
: doc.RootElement.TryGetProperty("Value", out prop)

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

@ -26,9 +26,9 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs
public async Task CreateJobsAsync()
{
_anonymousJobHandlerRegistry.Register("RuntimeAnonymousJob", (jsonData, sp, ct) =>
_anonymousJobHandlerRegistry.Register("RuntimeAnonymousJob", (context, ct) =>
{
using var doc = JsonDocument.Parse(jsonData);
using var doc = JsonDocument.Parse(context.JsonData);
var value = doc.RootElement.TryGetProperty("value", out var prop)
? prop.GetString()
: doc.RootElement.TryGetProperty("Value", out prop)
@ -62,18 +62,14 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs
(object)new { Value = "test 3 (yellow) - by name, anonymous", Time = DateTime.Now }
);
// Anonymous job enqueue (compile-time and runtime handlers)
if (!_backgroundJobManager.GetType().Name.ToUpperInvariant().Contains("RABBITMQ"))
{
await _backgroundJobManager.EnqueueAsync(
"CompileTimeAnonymousJob",
new { Value = "test 4 (anonymous) - compile-time" }
);
await _backgroundJobManager.EnqueueAsync(
"RuntimeAnonymousJob",
new { Value = "test 5 (anonymous) - runtime" }
);
}
await _backgroundJobManager.EnqueueAsync(
"CompileTimeAnonymousJob",
new { Value = "test 4 (anonymous) - compile-time" }
);
await _backgroundJobManager.EnqueueAsync(
"RuntimeAnonymousJob",
new { Value = "test 5 (anonymous) - runtime" }
);
}
}
}

Loading…
Cancel
Save