Asc.Utils.Commander
1.0.0
dotnet add package Asc.Utils.Commander --version 1.0.0
NuGet\Install-Package Asc.Utils.Commander -Version 1.0.0
<PackageReference Include="Asc.Utils.Commander" Version="1.0.0" />
<PackageVersion Include="Asc.Utils.Commander" Version="1.0.0" />
<PackageReference Include="Asc.Utils.Commander" />
paket add Asc.Utils.Commander --version 1.0.0
#r "nuget: Asc.Utils.Commander, 1.0.0"
#:package Asc.Utils.Commander@1.0.0
#addin nuget:?package=Asc.Utils.Commander&version=1.0.0
#tool nuget:?package=Asc.Utils.Commander&version=1.0.0
Asc.Utils.Commander
Utility for simplifying the use of the command pattern. It includes concepts from the unit of work pattern but without data persistence. It allows commands to be managed as reusable objects in memory. It simplifies the management of all commands executed by a given command processor, such as what to execute in case of success or failure.
The commands generated by this utility can be processed sequentially or simultaneously. Works with both synchronous and asynchronous delegates.
For each command you can define:
- Unique identifier (mandatory).
- Parameters that are accessible by all delegates associated with the command.
- The job you want the command to execute (mandatory).
- What should be executed if the job runs successfully.
- What should be executed if the job execution fails and generates an exception of a certain type.
- What should be executed if the job execution fails and generates an exception of any type
- What should be executed when the job completes, whether successfully or not.
For each command processor you can define:
- What should be executed immediately before the commands job.
- What should be executed when a command job is executed successfully.
- What should be executed when a command job fails and throws an exception of a certain type.
- What should be executed when a command job fails and throws an exception of any type (mandatory).
- What should be executed when a command's job completes, whether successfully or not.
- Set if commands are processed sequentially or simultaneously (mandatory).
- Set the maximum number of commands that can be processed simultaneously.
With these options, this utility is especially useful for implementing metrics, logs, or statistics. It forces the developer to think of a command as an atomic unit of code. It improves exception handling by unifying it in the command processor definition.
Installation
Install-Package Asc.Utils.Commander
Usage
Metrics, logs and exception management example
public void AppStart(IAppBuilder appBuilder)
{
//Some other code
ICommandProcessor commandProcessor = Commander.Instance.GetConcurrentCommandProcessorBuilder()
.SetMaxThreads(10)
.OnBeforeAnyJob((ICommand command) =>
{
IEventTracker tracker = GetTrackerFromCommand(command);
logger.LogInfo($"Running {command.Id}");
if (tracker is null)
return
TrackCommandParameters(tracker, GetOptionalParametersFromCommand(command));
tracker.Start();
})
.OnAnyJobFailure((TimeoutException ex, IExecutedCommand command) =>
{
logger.LogWarning($"Operation {command.Id} failed due to timeout");
GetTrackerFromCommand(command)?.TrackException(ex);
})
//This is mandatory
.OnAnyJobFailure((Exception ex, IExecutedCommand command) =>
{
logger.LogException(ex, $"{command.Id} failed. Check exception");
GetTrackerFromCommand(command)?.TrackException(ex);
})
.OnAfterAnyJob(IExecutedCommand command) =>
{
if (command.CommandResult == ExecutedCommandResult.Succeeded)
logger.LogInfo($"Command {command.Id} executed. Elapsed time: {command.JobElapsedTime}");
else
logger.LogWarning($"Command {command.Id} execution failed");
GetTrackerFromCommand(command)?.End();
}
.Build();
appBuilder.Services.RegisterInstance<ICommandProcessor>(commandProcessor);
//Some other code
}
private IEventTracker GetTrackerFromCommand(ICommand command)
{
if (command is null || command.Parameters is null
|| !command.Parameters.ContainsKey(Constants.TRACKER_PARAM_KEY))
{
return null;
}
return command.Parameters[Constants.TRACKER_PARAM_KEY].OfType<IEventTracker>();
}
private IEnumerable<KeyValuePair<string, string>> GetOptionalParametersFromCommand(ICommand command)
{
if (command is null || command.Parameters is null || !command.Parameters.Any())
return;
return command.Parameters
.Where(it => it.Key != Constants.TRACKER_PARAM_KEY)
.Select(it => new KeyValuePair<string, string>(it.Key, it.Value.OfType<string>()));
}
private void TrackCommandParameters(IEventTracker tracker, Dictionary<string, string> parameters)
{
if (tracker is null || parameters is null || !parameters.Any())
return;
foreach (KeyValuePair<string, string> parameter in parameters)
tracker.AddParameter(parameter.Key, parameter.Value);
}
// ...
//Later, somewhere in you app
// ...
public void PurchaseProducts()
{
ICommandProcessor commandProcessor = App.Services.Resolve<ICommandProcessor>();
//Products will be purchased in parallel
//because this command processor was created as a concurrent one
foreach (Product product in currentShoppingCart.Products)
commandProcessor.ProcessCommand(GetCommandToPurchaseProduct(product));
}
private ICommand GetCommandToPurchaseProduct(Product product)
{
string commandId = "Product.Purchase";
IProductProcessor productProcessor = App.Services.Resolve<IProductProcessor>();
IToastService toastService = App.Services.Resolve<IToastService>();
ITrackingService trackingService = App.Services.Resolve<ITrackingService>();
return Commander.Instance.GetCommandBuilder<PurchasedProduct>()
.Job(async () => return await productProcessor.PurchaseProduct(product)) //This is mandatory
.OnSuccess((PurchasedProduct purchasedProduct) =>
toastService.ShowToast(
$"Purchased {purchasedProduct.Amount} items of {purchasedProduct.Name} product"
)
)
.OnFailure((InsufficientStockException ex) =>
{
string message = $"Cannot purchase {product.Amount} items of {product.Name} since there is only {ex.ProductStockSize} in stock";
toastService.ShowWarningToast(message);
})
.OnFailure((Exception _) =>
toastService.ShowWarningToast($"Product {product.Name} purchase failed. Try it later"))
.AddOrReplaceParameter(Constants.TRACKER_PARAM_KEY, trackingService.BuildTrackerForEvent(commandId))
.AddOrReplaceParameter("productId", product.Id)
.AddOrReplaceParameter("amount", product.Amount)
.SetId(commandId) //This is mandatory
.Build();
}
UI Commands
public void AppStart(IAppBuilder appBuilder)
{
//Some other code
ICommandProcessor uiCommandProcessor = Commander.Instance.GetSequentialCommandProcessorBuilder()
.OnBeforeAnyJob(() =>
{
navigationService.CurrentPage.IsBusy = true;
navigationService.CurrentPage.ShowSpinner();
})
//This is mandatory
.OnAnyJobFailure((Exception ex, IExecutedCommand command) =>
logger.LogException(ex, $"{command.Id} failed. Check exception"))
.OnAfterAnyJob(() =>
{
navigationService.CurrentPage.HideSpinner();
navigationService.CurrentPage.IsBusy = false;
})
.Build();
appBuilder.Services.RegisterInstance<ICommandProcessor>(commandProcessor);
//Some other code
}
// ...
//Later, somewhere in you app
// ...
private ICommand? purchaseProductsCommand;
private void ShoppingCartPurchaseButton_Clicked()
{
if (IsBusy)
return;
ICommandProcessor commandProcessor = App.Services.Resolve<ICommandProcessor>();
ICommand command = GetPurchaseProductsCommand();
//Commands will be processed one by one
//since the used command processor has been created as a sequential one.
commandProcessor.ProcessCommand(command);
}
//Purchase cart products have always same logic so you can reuse an ICommand instance
private ICommand GetPurchaseProductsCommand()
{
if (purchaseProductsCommand is not null)
return purchaseProductsCommand;
purchaseProductsCommand = Commander.Instance.GetCommandBuilder()
.Job(() => shoppingCartService.PurchaseProducts())
.OnFailure((Exception _) => toastService.ShowWarningToast("Something failed. Try it later"))
.SetId("Cart.PurchaseItems")
.Build();
return purchaseProductsCommand;
}
Bad practices
- Calling ICommandProcessor.ProcessCommand recursively from within a command's job that will be processed by the same command processor.
- Do not propagate an exception that occurs within a command's job. This way, no delegates registered for error cases will be invoked.
More info
See public interfaces summaries:
- ICommand
- IExecutedCommand
- ICommandBuilder
- ICommandBuilder<>
- ICommander
- ICommandParameter
- ICommandProcessor
- ISequentialCommandProcessorBuilder
- IConcurrentCommandProcessorBuilder
- ICommandProcessorBuilder
to get more info about how to use this utility.
Icon from Flaticon:
<a href="https://www.flaticon.com/free-icons/soldier" title="soldier icons">Soldier icons created by imaginationlol - Flaticon</a>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 254 | 3/17/2025 |
net9.0 version