Chainer 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Chainer --version 1.0.0
                    
NuGet\Install-Package Chainer -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Chainer" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Chainer" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Chainer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Chainer --version 1.0.0
                    
#r "nuget: Chainer, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Chainer@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Chainer&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Chainer&version=1.0.0
                    
Install as a Cake Tool

Chainer

What is Chainer?

Chainer provides an abstraction that makes it easy to execute a series of actions on a given context in sequence. If an action fails internally, or fails during execution, the error message is caught and reflected in the result

The primary use case for the library is defining a series of processes that should apply to some context with built-in error handling.

How to define a chain

The simplest chain is can be created using the ChainExecutor class. First, make a context.

//Must be a class and implment IClonable and have a parameterless constructor
public class FileContext : ICloneable
{
    public string Content { get; set; } = "default";

    public object Clone()
    {
        return new FileContext
        {
            Content = Content
        };
    }
}

Then create some handlers.

public class FileHandlerRemoveComma : IChainHandler<FileContext>
{
    public Task<Result<FileContext>> Handle(FileContext context, ILogger? logger = null, CancellationToken cancellationToken = default)
    {
        context.Content = context.Content.Replace(",", "");
        return Task.FromResult<Result<FileContext>>(context);
    }
}

public class FileHandlerIsLegit : IChainHandler<FileContext>
{
    public Task<Result<FileContext>> Handle(FileContext context, ILogger? logger = null, CancellationToken cancellationToken = default)
    {
        return !context.Content.Contains("Legit", StringComparison.InvariantCultureIgnoreCase)
            ? Task.FromResult(Result.Failure<FileContext>("This ain't legit"))
            : Task.FromResult<Result<FileContext>>(context);
    }
}

Then define the chain using the method syntax.

var chain = new ChainExecutor<FileContext>()
            .AddHandler(new FileHandlerRemoveComma())
            .AddHandler(new FileHandlerIsLegit());

Or the constructor syntax.

var chain = new ChainExecutor<FileContext>([new FileHandlerRemoveComma(), new FileHandlerIsLegit()]);

Note that a logger can optionally be passed into the constructor. The logger will be forwarded to the handlers.

Execute

To execute, call:

var result = await chain.Execute(context);

If a null value is passed, the context will be newed up on execution. This could be useful if one of the handler's populates context information.

Execute With History

Or to execute with metadata about the execution:

var result = await chain.ExecuteWithHistory(context);
Console.WriteLine(result);

Outputs

----------------------------------------
Context: Chainer.SourceGen.Sample.FileContextChain.FileContext
Success: True
Error: None
Start: 2024-06-20T00:21:08
End: 2024-06-20T00:21:08
Execution Time: 0:00:00.0000505
Applied Handlers
        -Chainer.SourceGen.Sample.FileContextChain.Handlers.FileHandlerRemoveComma; Duration: 0:00:00.0000093
        -Chainer.SourceGen.Sample.FileContextChain.Handlers.FileHandlerIsLegit; Duration: 0:00:00.0000047
----------------------------------------

By default, the ExecuteWithHistory will clone and store the context at each step, but if that isn't wanted, the method has a parameter to prevent that.

public async Task<ContextHistoryResult<TContext>> ExecuteWithHistory(TContext? context,
        bool doNotCloneContext = false,
        CancellationToken cancellationToken = default)

General Use

The ChainExecutor can be useful for chains that aren't predefined. They can also have some use being defined as keyed services.

Handlers are not restricted in their scope and can be used for data import, export, validation, and so on.

Chain Service

For predefined chains or chains that should be created via dependency injection, a class can be defined that inherits from ChainService. The executor will find the types specified from the DI container and execute the chain.

public class FileChain(IServiceProvider services, ILogger<FileChain> logger) : ChainService<FileContext>(services, logger)
{
    //Can disable logging if wanted
    protected override bool LoggingEnabled => false;
    
    protected override List<Type> ChainHandlers { get; } = new List<Type>
    {
        typeof(FileHandlerRemoveComma), typeof(FileHandlerIsLegit)
    };
}

If using the source generator the following case be used to override the ChainHandlers and add all the types to the registration method.

[RegisterChains<FileContext>(
    typeof(FileHandlerRemoveComma),
    typeof(FileHandlerIsLegit))]
public partial class FileChain(IServiceProvider services, ILogger<FileChain> logger) : ChainService<FileContext>(services, logger);

Call the RegisterChains() method to register the services.

var builder = Host.CreateApplicationBuilder();
builder.Services.RegisterChains();
var host = builder.Build();

The RegisterChains() will register all the services as follows.

public static class ChainerRegistrar
{
    public static void RegisterChains(this IServiceCollection services)
    {
        services.TryAddScoped<FileChain>();
        services.TryAddScoped<FileHandlerRemoveComma>();
        services.TryAddScoped<FileHandlerIsLegit>();
    }
}

Future Plans

Though the library is intended to be limited is scope, feel free to give suggestions or submit pull requests. The base functionality of the library is present, but there could likely be improvements in testing and performance.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.1.0 140 6/2/2025
1.0.0 140 7/9/2024