Kana.Pipelines 2.2.0

dotnet add package Kana.Pipelines --version 2.2.0
NuGet\Install-Package Kana.Pipelines -Version 2.2.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="Kana.Pipelines" Version="2.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Kana.Pipelines --version 2.2.0
#r "nuget: Kana.Pipelines, 2.2.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.
// Install Kana.Pipelines as a Cake Addin
#addin nuget:?package=Kana.Pipelines&version=2.2.0

// Install Kana.Pipelines as a Cake Tool
#tool nuget:?package=Kana.Pipelines&version=2.2.0

Functional pipeline classes for building result driven or operation driven, modular pipelines.

Why?

The pipeline pattern allows you to build reusable, abstract, and self-contained middleware functions that can be integrated into different pipelines to achieve any number of larger tasks without duplicating code. For pipeline builders the middleware could be defined anywhere, such as given in via user parameters, self-defined or from libraries. This pattern is most most recongisable from ASP.NET Core, where middleware from many sources (UseMvc, UseStaticFiles, UseDeveloperExceptionPage etc.) integrate together to handle web request between them.

Example Snippet (with inline functions)

Pipeline definition:

var authenticationPipeline = new Pipeline<SignInInput, string>();

authenticationPipeline.Add(UserIsDeleted);
authenticationPipeline.Add(UserIsTemporary);
authenticationPipeline.Add(CredentialsCorrect);

var input = new User("kana_ki_", "randomPassword");
var output = await authenticationPipeline.RunAsync(input);

Middleware definitions:

public async Task<string> UserIsDeleted(SignInInput signIn, Func<Task<string>> next) {
  if (this.db.FindByUsername(signIn.Username).IsDeleted) {
    return "User deleted";
  }
  return await next();
}

public async Task<string> UserIsTemporary(SignInInput signIn, Func<Task<string>> next) {
  var result = await next();
  if (result == "User authenticated" && this.db.FindByUsername(signIn.Username).IsTemporary) {
    result = "User authenticated temporarily";
  }
  return result;
}

public async Task<string> CredentialsCorrect(SignInInput signIn, Func<Task<string>> next) {
  if (this.db.FindByUsername(signIn.Username).Password == hash(signIn.Password)) {
    return "User authenticated";
  }
  return "User not authenticated";
}

In the above example, 3 middleware functions are declared and are queued in the pipeline in the order UserIsDeleted, UserIsTemporary and CredentialsCorrect. Each middleware in turn has the opportunity to return a result and stop the pipeline from continuing, if they do the result propogates through the previous middlewares in the pipeline (later middlewares wouldn't run).

The UserIsDeleted middleware immediately concludes the pipeline if the user is deleted, otherwise it calls next() to pass on to the next middleware in the pipeline.

UserIsTemporary immediately passes off to the next middleware in the pipeline - the CredentialsCorrect middleware that checks the sign in detail. Once a result is received from the remainder of the pipeline it checks to see if it was a "User authenticated" result, if so and the user is a temporary user, then it modifies the pipeline result to say "User authenticated temporarily".

Each middleware knows nothing about the other middlewares in the pipeline, and such should work even if other middleware is swapped out for an alternate middleware. Building out tasks in a pipeline pattern like this can really help modularise responsibility and functionality, particularly when those tasks are complicated and/or could be achieved in a variety of combinations of operations.

Example Snippet (with injected types)

Pipeline definition:

var authenticationPipeline =
  new Pipeline<SignInInput, string>()
    .WithServiceProvider(serviceProvider)
    .Add<UserIsDeletedMiddleware>();
    .Add<UserIsTemporaryMiddleware>();
    .Add<CredentialsCorrectMiddleware>();

var input = new User("kana_ki_", "randomPassword");
var output = await authenticationPipeline.RunAsync(input);

Middleware definitions:

public class UserIsDeletedMiddleware : IMiddleware<SignInInput, string> {

  private IDatabase db;

  public UserIdDeletedMiddleware(IDatabase db) {
    this.db = repo;
  }

  public async Task<string> ExecuteAsync(SignInInput signIn, Func<Task<string>> next) {
    if (this.db.FindByUsername(signIn.Username).IsDeleted) {
      return "User deleted";
    }
    return await next();
  }
  
}

public class UserIsTemporaryMiddleware : IMiddleware<SignInInput, string> {

  private IDatabase db;

  public UserIsTemporaryMiddleware(IDatabase db) {
    this.db = repo;
  }

  public async Task<string> ExecuteAsync(SignInInput signIn, Func<Task<string>> next) {
    var result = await next();
    if (result == "User authenticated" && this.db.FindByUsername(signIn.Username).IsTemporary) {
      result = "User authenticated temporarily";
    }
    return result;
  }
  
}

public class CredentialsCorrectMiddleware : IMiddleware<SignInInput, string> {

  private IDatabase db;

  public CredentialsCorrectMiddleware(IDatabase db) {
    this.db = repo;
  }

  public async Task<string> ExecuteAsync(SignInInput signIn, Func<Task<string>> next) {
    if (this.db.FindByUsername(signIn.Username).Password == hash(signIn.Password)) {
      return "User authenticated";
    }
    return "User not authenticated";
  }
  
}

Features

  • Pipelines that do not conclude with a result (void pipelines).
  • Pipelines that do conclude with a result.
  • Middleware can conclude the pipeline early with a result, or modify the result from later middleware.
  • Middleware can be provided as inline functions, instances of IPipeline, or as references to types of IPipeline that are JIT constructor and dependency injected if given a service provider.
  • Pipelines can be made from other pipelines, making new union pipelines, via flexible Add overloads and the native C# + operator.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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. 
.NET Core netcoreapp1.0 was computed.  netcoreapp1.1 was computed.  netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard1.1 is compatible.  netstandard1.2 was computed.  netstandard1.3 was computed.  netstandard1.4 was computed.  netstandard1.5 was computed.  netstandard1.6 was computed.  netstandard2.0 was computed.  netstandard2.1 was computed. 
.NET Framework net45 was computed.  net451 was computed.  net452 was computed.  net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen30 was computed.  tizen40 was computed.  tizen60 was computed. 
Universal Windows Platform uap was computed.  uap10.0 was computed. 
Windows Phone wpa81 was computed. 
Windows Store netcore was computed.  netcore45 was computed.  netcore451 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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
2.2.0 1,223 11/21/2021
2.1.0 349 11/15/2021
2.0.0 337 5/17/2021
1.0.0 332 5/9/2021

Add support for dependency injection into Middleware Types.