Bjija Action Task Manager is a flexible and extensible .NET library designed to streamline the task execution pipeline based on various action triggers. It provides a clean abstraction for associating tasks with actions, allowing developers to automate workflows and perform asynchronous operations seamlessly.
- Action-Based Task Execution: Execute one or more tasks when a specific action occurs in your application.
- Profile-Based Task Management: Run multiple tasks sequentially or in parallel for specific user profiles.
- Extensible Decorators: Easily add pre-execution or post-execution behavior to tasks.
- Dynamic Task Pipelines: Create, modify, and manage task pipelines at runtime.
- Logging and Monitoring: Built-in support for task logging and event-based monitoring.
Ideal for applications that require modular and configurable task automation based on various events or triggers.
dotnet add package Bjija.ActionTaskManager --version 1.0.0
Install-Package Bjija.ActionTaskManager -Version 1.0.0
Add the following line in your .csproj file:
<PackageReference Include="Bjija.ActionTaskManager" Version="1.0.0" />
To get started, you need to register the Bjija Action Task Manager in your application's dependency injection container.
Register the Bjija Action Task Manager in the Startup.cs
or equivalent configuration file:
var services = new ServiceCollection();
services.AddBjijaActionTaskManager();
You can enable logging decorators for all tasks by setting the EnableLoggingDecorator
option to true
.
services.AddBjijaActionTaskManager(options =>
{
options.EnableLoggingDecorator = true;
});
The library integrates with Microsoft's ILogger
abstraction and uses the console logger by default.
If you have Serilog configured in your application, the library will automatically use it:
services.AddLogging(builder =>
{
builder.AddSerilog();
});
If you want to plug in any logger that implements ILogger
, you can set the LoggerFactory
option:
services.AddBjijaActionTaskManager(options =>
{
options.EnableLoggingDecorator = false;
options.LoggerFactory = LoggerFactory.Create(builder => { builder.AddSerilog(); });
});
Define a payload class to pass data to your tasks. Here's an example:
Payload payload = new Payload
{
BirthDay = new DateTime(1985, 09, 24),
Email = "no-reply@gmail.com",
Name = "Bouchalkha",
};
Tasks must inherit from the base class Task<Payload>
and override the ExecuteCoreAsync
method. Here's an example:
public class WelcomeEmailTask : Bjija.ActionTaskManager.Task<Payload>
{
protected override async Task ExecuteCoreAsync(object sender, ActionEventArgs<Payload> args)
{
string username = args.Data.Email;
await SendEmailAsync(username);
}
private Task SendEmailAsync(string username)
{
Console.WriteLine($"Sending email task to {username}");
return Task.CompletedTask;
}
}
Actions must implement the IActionTrigger<Payload>
interface. Here's an example for user login:
public class UserLoginAction : IActionTrigger<Payload>
{
public string ProfileName => null;
public event EventHandler<ActionEventArgs<Payload>> ActionOccurred;
public void UserLoggedIn(Payload payload)
{
ActionOccurred?.Invoke(this, new ActionEventArgs<Payload>(payload));
}
}
Universal decorators are decorators that are applied to all tasks, regardless of the action they are associated with. This is useful when you want to apply common behavior, like logging, to all tasks. You can register a universal decorator using the AddUniversalDecorator
method.
taskManager.AddUniversalDecorator(typeof(LoggingTaskDecorator<string>));
By using this method, all tasks will be decorated with LoggingTaskDecorator, enabling consistent logging behavior across tasks.
To register tasks for a specific action, you can use the Register
method provided by IActionTaskManager
. Here's how to register the WelcomeEmailTask
and SetupUserSettings
for the UserLoginAction
:
taskManager.Register<UserLoginAction, Payload, SetupUserSettings>()
.Register<UserLoginAction, Payload, WelcomeEmailTask>();
taskManager.Register<UserLoginAction, Payload, SetupUserSettings>(typeof(AnotherDecorator<Payload>))
.Register<UserLoginAction, Payload, WelcomeEmailTask>(typeof(LoggingTaskDecorator<Payload>));
Sometimes you may want to conditionally execute tasks based on certain criteria. You can achieve this by providing a predicate function during task registration.
Create a predicate function that takes ActionEventArgs<Payload>
and returns a bool
. The task will only execute if the function returns true
.
Func<ActionEventArgs<Payload>, bool> predicate = args => args.Data.Email != "no-reply@gmail.com";
Now, the WelcomeEmailTask
will only execute if the user's email is not "no-reply@gmail.com".
Register the task and provide the predicate function as a parameter.
taskManager.Register<UserLoginAction, Payload, WelcomeEmailTask>(predicate);
Or predicate with a task decorator
taskManager.Register<UserLoginAction, Payload, WelcomeEmailTask>(args => args.Data.Email != "no-reply@gmail.com", typeof(AnotherDecorator<Payload>));
After registering tasks, you need to link them to the corresponding actions using the LinkActionToTasksAsync
method:
await taskManager.LinkActionToTasksAsync(userLoginAction);
Finally, to trigger the action and consequently execute the associated tasks, you can call a method on the action object that fires the action. In our UserLoginAction
example, this would be the UserLoggedIn
method:
userLoginAction.UserLoggedIn(payload);
By calling this method, all tasks registered for this action will be executed in the order they were registered.
var userLoginAction = new UserLoginAction();
taskManager.Register<UserLoginAction, Payload, WelcomeEmailTask>(args => args.Data.Email != "no-reply@gmail.com")
.Register<UserLoginAction, Payload, SomeOtherTask>(typeof(LoggingTaskDecorator<Payload>));
taskManager.LinkActionToTasksAsync(userLoginAction);
userLoginAction.UserLoggedIn(payload);
For profile-based tasks, use the RegisterProfileTask
method provided by IActionTaskManager
. Below is an example of registering the SetupUserSettingsTask
and SendUserWelcomeEmailTask
for the profile "UserLogin":
taskManager.RegisterProfileTask("UserLogin", new SetupUserSettingsTask())
.Register(new SendUserWelcomeEmailTask(), typeof(LoggingTaskDecorator<Payload>));
As with action-based tasks, you can also conditionally execute profile-based tasks.
Func<ActionEventArgs<Payload>, bool> predicate = args => args.Data.Email != string.Empty;
You can register a profile-based task with a predicate like this:
taskManager.RegisterProfileTask("UserLogin", new SetupUserSettingsTask(), predicate);
After registering the profile-based tasks, you can link them to the corresponding profiles using the LinkActionToTasksAsync
method:
await taskManager.LinkActionToTasksAsync(userProfileAction, "UserLogin");
To trigger the profile-based actions, you can call a method on the action object that fires the action and pass in the profile name. In our example, this would be the ChooseProfile
method on the UserProfileChoiceAction
:
userProfileAction.ChooseProfile("UserLogin");
By doing so, all tasks registered for this profile will be executed in the order they were registered.
var userProfileAction = new UserProfileChoiceAction(payload);
taskManager.RegisterProfileTask("UserLogin", new SetupUserSettingsTask())
.Register(new SendUserWelcomeEmailTask(), typeof(LoggingTaskDecorator<Payload>));
await taskManager.LinkActionToTasksAsync(userProfileAction, "UserLogin");
userProfileAction.ChooseProfile("UserLogin");
For more complex scenarios, you can create a task pipeline using the ITaskPipeline<T>
interface:
var eCommercePipeline = serviceProvider.GetRequiredService<ITaskPipeline<OrderData>>();
The concept of a task pipeline in Bjija Action Task Manager is analogous to the middleware pipeline in ASP.NET Core. Just like middleware, tasks in a pipeline are executed in a specific order determined by their priority. They all share and consume the same payload, allowing one task to prepare data or set states that the following task can then use.
In the task pipeline, all tasks consume the same payload. For example, in a pipeline handling OrderData
, the ValidateCustomer
task might validate the customer details and mark the order as validated. The next task, AdjustInventory
, can then use this validated flag to proceed with its own operations.
Tasks can be registered in the pipeline with priority and an optional predicate for conditional execution.
eCommercePipeline.RegisterTask(new ValidateCustomer(), priority: 1, predicate: args => args.Data.CustomerId != string.Empty);
eCommercePipeline.RegisterTask(new AdjustInventory(), priority: 2);
eCommercePipeline.RegisterTask(new UpdateCustomer(), priority: 3);
eCommercePipeline.RegisterTask(new ProcessBilling(), priority: 4);
eCommercePipeline.RegisterTask(new SendNotification(), priority: 5);
Once your pipeline is set up, you need to register it with an action:
taskManager.RegisterPipeline<CommerceManager, OrderData>(eCommercePipeline);
You can subscribe to various pipeline events to monitor its status:
eCommercePipeline.PipelineStarted += (sender, args) => Console.WriteLine("Pipeline started");
eCommercePipeline.PipelineCompleted += (sender, args) => Console.WriteLine("Pipeline completed");
eCommercePipeline.PipelineFailed += (sender, args) => Console.WriteLine($"Pipeline failed: {args.Error}");
The pipeline can be linked to actions in the same way as individual tasks:
await taskManager.LinkActionToTasksAsync(commerceManager);
Triggering the action will execute the tasks in the pipeline in the order set by their priorities:
var commerceManager = new CommerceManager();
commerceManager.ProcessOrder(orderData);
var commerceManager = new CommerceManager();
var eCommercePipeline = serviceProvider.GetRequiredService<ITaskPipeline<OrderData>>();
eCommercePipeline.RegisterTask(new ValidateCustomer(), priority: 1, predicate: args => args.Data.CustomerId != string.Empty);
eCommercePipeline.RegisterTask(new AdjustInventory(), priority: 2);
eCommercePipeline.RegisterTask(new UpdateCustomer(), priority: 3);
eCommercePipeline.RegisterTask(new ProcessBilling(), priority: 4);
eCommercePipeline.RegisterTask(new SendNotification(), priority: 5);
taskManager.RegisterPipeline<CommerceManager, OrderData>(eCommercePipeline);
eCommercePipeline.PipelineStarted += (sender, args) => Console.WriteLine("Pipeline started");
eCommercePipeline.PipelineCompleted += (sender, args) => Console.WriteLine("Pipeline completed");
eCommercePipeline.PipelineFailed += (sender, args) => Console.WriteLine($"Pipeline failed: {args.Error}");
await taskManager.LinkActionToTasksAsync(commerceManager);
commerceManager.ProcessOrder(orderData);