MSL.Plumber.Pipeline
2.3.0
dotnet add package MSL.Plumber.Pipeline --version 2.3.0
NuGet\Install-Package MSL.Plumber.Pipeline -Version 2.3.0
<PackageReference Include="MSL.Plumber.Pipeline" Version="2.3.0" />
paket add MSL.Plumber.Pipeline --version 2.3.0
#r "nuget: MSL.Plumber.Pipeline, 2.3.0"
// Install MSL.Plumber.Pipeline as a Cake Addin #addin nuget:?package=MSL.Plumber.Pipeline&version=2.3.0 // Install MSL.Plumber.Pipeline as a Cake Tool #tool nuget:?package=MSL.Plumber.Pipeline&version=2.3.0
Build Status
Plumber
Middleware pipelines for host-free projects like AWS Lambda, Console, etc.
Plumber is a request-response pipeline that supports middleware delegates and classes. It provides configuration, dependency injection, and middleware pipeline services. It's useful for AWS Lambdas, Azure Functions, queue event handlers, and similar use cases.
References
Plumber is based on this article: How is the ASP.NET Core Middleware Pipeline Built - Steve Gorden, July 2020
Getting Started
If you're not familiar with middleware pipelines, Microsoft has a good primer on how middleware works in ASP.NET Core.
Installing
To install, use the following command in your terminal or command prompt
dotnet add package MSL.Plumber.Pipeline
Usage
- Create an
IRequestHandlerBuilder<TRequest, TResponse>
by calling one of the staticRequestHandlerBuilder.Create
methods. - Use
RequestHandlerBuilder.Create(string[] args)
to create a builder with the default configuration providers forappsettings.json
files, environment variables, command line args, and user secrets. - Use
RequestHandlerBuilder.Create(string[] args, Action<IConfiguration, string[]> configure)
to create a builder with your own set of configuration providers. - Handle additional configuration scenarios through the
IConfigurationManager Configuration
property on the builder. - Register services with the
IServiceCollection Services
property. - Use the
Build
method to create anIRequestHandler<TRequest, TResponse>
instance. - Configure the request delegate pipeline by calling the
Use
methods on the request handler. - Call the
Prepare
method on the request handler to compile the pipeline. If you don't callPrepare
, the pipeline will be compiled when the first request is processed. - Call the
InvokeAsync
method on the request handler to forward the request to the pipeline. - Any class that meets the following criteria can be used as middleware
- constructor must take
RequestMiddleware<TRequest, TResponse>
as its first argument - must contain an
InvokeAsync
method InvokeAsync
must return aTask
InvokeAsync
must takeRequestContext<TRequest, TResponse>
as its first argument
- constructor must take
- Within your middleware delegates or class implementations, always invoke
next
to pass the request context to the next delegate in the pipeline. - Always call
context.CancellationToken.ThrowIfCancellationRequested()
to check for cancellation requests before processing the request or invokingnext
. - To terminate or "short circuit" the pipeline, don't invoke
next
. - Set the
Response
property on theRequestContext<TRequest, TResponse>
argument to return a value from the pipeline execution.
Sample AWS Lambda Projects
- Samples.Lambda.SQS
- Samples.Lambda.SQS.Tests
- Samples.Lambda.APIGateway
- Samples.Lambda.APIGateway.Tests
Examples
The following examples demonstrate common usage scenarios.
Simplest Example - no config, no services, no middleware
In this sample, we create a request handler that does nothing with no configuration, no service registration, and no user-defined middleware. This is the simplest possible example.
var request = "Hello, World!";
var handler = RequestHandlerBuilder
.Create<string, string>()
.Build();
var response = await handler.InvokeAsync(request);
Assert.True(String.IsNullOrEmpty(response));
Middleware Delegate Example
In this sample, we create a request handler with user-defined middleware that converts the request to uppercase.
var request = "Hello, World!";
var handler = RequestHandlerBuilder.Create<string, string>()
.Build()
.Use(async (context, next) =>
{
context.CancellationToken.ThrowIfCancellationRequested();
context.Response = context.Request.ToUpperInvariant();
await next(context); // call next to pass the request context to the next delegate in the pipeline
});
var response = await handler.InvokeAsync(request);
Assert.Equal(request.ToUpperInvariant(), response);
Middleware Class Example
In this sample, we create a request handler with a user-defined middleware class that converts the request to lowercase.
First, we define the middleware class, which receives the next middleware delegate in the pipeline in its constructor.
The middleware is responsible for invoking the next
delegate. You will short-circuit the pipeline if you don't invoke the next
delegate.
An example short-circuit scenario might be a request validation middleware that returns an error response if the request is invalid.
The middleware is also responsible for short-circuiting when the pipeline is explicitly canceled via the context.CancellationToken
.
Constructor-based dependency injection is supported for middleware implementations,
with the condition that the next
delegate must be the first argument in the constructor.
internal sealed class ToLowerMiddleware(RequestMiddleware<string, string> next)
{
public Task InvokeAsync(RequestContext<string, string> context)
{
context.CancellationToken.ThrowIfCancellationRequested();
context.Response = context.Request.ToLowerInvariant();
// call next to pass the request context to the next delegate in the pipeline
return next(context);
}
}
InvokeAsync
dependency injection is supported for middleware implementations,
with the condition that the RequestContext<TRequest, TResponse>
must be the first argument.
internal sealed class ToLowerMiddleware(RequestMiddleware<string, string> next)
{
public Task InvokeAsync(
RequestContext<string, string> context, // context is first argument
IMyFavoriteService service) // second argument is injected by the pipeline's service scope
{
context.CancellationToken.ThrowIfCancellationRequested();
context.Response = service.DoSomethingWonderful(context.Request.ToLowerInvariant());
// call next to pass the request context to the next delegate in the pipeline
return next(context);
}
}
Next, we register the middleware with the request handler with the Use<T>
method.
var request = "Hello, World!";
var handler = RequestHandlerBuilder.Create<string, string>()
.Build()
.Use<ToLowerMiddleware>();
var response = await handler.InvokeAsync(request);
Assert.Equal(request.ToLowerInvariant(), response);
Builder Configuration Example
Use the IRequestHandlerBuilder.Configuration
property to add configuration providers, like AddInMemory
or AddJsonFile
.
// default configuration providers are added
// 1. optional appsettings.json
// 2. optional appsettings.{env}.json
// 3. environment variables
// 4. if dev env, then user secrets
// 5. command line args
var builder = RequestHandlerBuilder.Create<string, string>(args);
// extra configuraiton providers can be added
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string> { { "MyKey", "MyValue" } });
var handler = builder.Build();
Provide your own set of configuration providers with the Create
method.
// only user specified configuration providers are added
var builder = RequestHandlerBuilder.Create<string, string>(args, (args, configuration) =>
{
configuration
.AddUserSecrets("appsettings.json")
.AddInMemoryCollection(new Dictionary<string, string> { { "MyKey", "MyValue" } });
});
var handler = builder.Build();
Builder Service Registration Example
Use the IRequestHandlerBuilder.Services
property to register services.
var builder = RequestHandlerBuilder.Create<string, string>(args);
builder.Services
.AddSingleton<IMyService, MyService>()
.AddSerilog();
var handler = builder.Build();
Void Response Example
Some pipelines need to return a response. One example is an AWS Lambda triggered by APIGateway. These
handlers receive an APIGatewayHttpApiV2ProxyRequest
and return an APIGatewayHttpApiV2ProxyResponse
to the APIGateway.
However, many use cases don't require a response. For example, an AWS Lambda triggered by an SQS message.
In these cases, the Void
type can be used as the generic argument for TResponse
.
public readonly struct Void { }
You can use it as the response type for request handlers that don't need to return a response.
var handler = RequestHandlerBuilder.Create<string, Void>() // Void TResponse type
.Build();
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. 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 is compatible. 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 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. |
-
net6.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.1)
- Microsoft.Extensions.Configuration.CommandLine (>= 8.0.0)
- Microsoft.Extensions.Configuration.EnvironmentVariables (>= 8.0.0)
- Microsoft.Extensions.Configuration.Json (>= 8.0.0)
- Microsoft.Extensions.Configuration.UserSecrets (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection (>= 8.0.0)
- Microsoft.Extensions.Diagnostics.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 8.0.0)
- Microsoft.NET.ILLink.Tasks (>= 8.0.6)
- Ulid (>= 1.3.3)
-
net7.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.1)
- Microsoft.Extensions.Configuration.CommandLine (>= 8.0.0)
- Microsoft.Extensions.Configuration.EnvironmentVariables (>= 8.0.0)
- Microsoft.Extensions.Configuration.Json (>= 8.0.0)
- Microsoft.Extensions.Configuration.UserSecrets (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection (>= 8.0.0)
- Microsoft.Extensions.Diagnostics.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 8.0.0)
- Microsoft.NET.ILLink.Tasks (>= 8.0.6)
- Ulid (>= 1.3.3)
-
net8.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.1)
- Microsoft.Extensions.Configuration.CommandLine (>= 8.0.0)
- Microsoft.Extensions.Configuration.EnvironmentVariables (>= 8.0.0)
- Microsoft.Extensions.Configuration.Json (>= 8.0.0)
- Microsoft.Extensions.Configuration.UserSecrets (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection (>= 8.0.0)
- Microsoft.Extensions.Diagnostics.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 8.0.0)
- Microsoft.NET.ILLink.Tasks (>= 8.0.6)
- Ulid (>= 1.3.3)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on MSL.Plumber.Pipeline:
Package | Downloads |
---|---|
MSL.Plumber.Serilog.Extensions
Plumber.Serilog.Extensions provides Serilog middleware extensions for the Plumber pipeline libary. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.3.0 | 70 | 7/26/2024 |
2.2.0 | 140 | 7/22/2024 |
2.1.0 | 94 | 7/3/2024 |
2.0.2 | 119 | 6/26/2024 |
2.0.1 | 121 | 6/26/2024 |
2.0.0 | 74 | 6/25/2024 |
1.1.1 | 108 | 6/22/2024 |
1.1.0 | 104 | 6/22/2024 |
1.0.50 | 115 | 6/22/2024 |
1.0.49 | 144 | 5/16/2024 |
1.0.48 | 127 | 5/10/2024 |
1.0.47 | 203 | 5/7/2024 |
1.0.46 | 109 | 5/6/2024 |
1.0.45 | 113 | 5/4/2024 |
1.0.42 | 108 | 5/4/2024 |
1.0.40 | 121 | 5/4/2024 |
1.0.38 | 123 | 5/4/2024 |
1.0.37 | 103 | 5/4/2024 |
1.0.36 | 105 | 5/4/2024 |
1.0.35 | 112 | 5/4/2024 |