├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.ps1 └── src ├── PipelineNet.ServiceProvider.Tests ├── MiddlewareResolver │ ├── ActivatorUtilitiesMiddlewareResolverTests.cs │ └── ServiceProviderMiddlewareResolverTests.cs ├── PipelineNet.ServiceProvider.Tests.csproj └── ServiceCollectionExtensionsTests.cs ├── PipelineNet.ServiceProvider ├── MiddlewareResolver │ ├── ActivatorUtilitiesMiddlewareResolver.cs │ └── ServiceProviderMiddlewareResolver.cs ├── PipelineNet.ServiceProvider.csproj └── ServiceCollectionExtensions.cs ├── PipelineNet.Tests ├── ChainsOfResponsibility │ ├── AsyncResponsibilityChainTests.cs │ └── ResponsibilityChainTests.cs ├── PipelineNet.Tests.csproj └── Pipelines │ ├── AsyncPipelineTests.cs │ └── PipelineTests.cs ├── PipelineNet.sln └── PipelineNet ├── AsyncBaseMiddlewareFlow.cs ├── BaseMiddlewareFlow.cs ├── ChainsOfResponsibility ├── AsyncResponsibilityChain.cs ├── IAsyncResponsibilityChain.cs ├── IResponsibilityChain.cs └── ResponsibilityChain.cs ├── Finally ├── IAsyncFinally.WithReturn.cs ├── ICancellableAsyncFinally.WithReturn.cs └── IFinally.WithReturn.cs ├── Middleware ├── IAsyncMiddleware.WithReturn.cs ├── IAsyncMiddleware.cs ├── ICancellableAsyncMiddleware.WithReturn.cs ├── ICancellableAsyncMiddleware.cs ├── IMiddleware.WithReturn.cs └── IMiddleware.cs ├── MiddlewareResolver ├── ActivatorMiddlewareResolver.cs ├── IMiddlewareResolver.cs └── MiddlewareResolverResult.cs ├── PipelineNet.csproj └── Pipelines ├── AsyncPipeline.cs ├── IAsyncPipeline.cs ├── IPipeline.cs └── Pipeline.cs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: .NET 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | defaults: 17 | run: 18 | working-directory: ./src 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: | 26 | 6.0.x 27 | 8.0.x 28 | - name: Restore dependencies 29 | run: dotnet restore PipelineNet.sln 30 | - name: Build 31 | run: dotnet build --no-restore PipelineNet.sln 32 | - name: Test 33 | run: dotnet test --no-build --verbosity normal PipelineNet.sln 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Nuget Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | workflow_dispatch: 8 | inputs: 9 | tag_name: 10 | description: 'The name of the tag to release' 11 | required: true 12 | default: '' 13 | 14 | jobs: 15 | publish: 16 | runs-on: ubuntu-latest 17 | environment: Production 18 | 19 | env: 20 | TAG_NAME: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name || github.event.release.tag_name }} 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | with: 26 | ref: ${{ env.TAG_NAME }} 27 | 28 | - name: Setup .NET 29 | uses: actions/setup-dotnet@v4 30 | with: 31 | dotnet-version: 8.0.x 32 | 33 | - name: Fetch latest build script from master branch 34 | run: | 35 | git fetch origin master 36 | git checkout origin/master -- build.ps1 37 | 38 | - name: Build and publish 39 | shell: pwsh 40 | run: ./build.ps1 ${{ env.TAG_NAME }} 41 | env: 42 | NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} # Replace with your NuGet source (public or private) 43 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} # Replace with your NuGet API key (stored as secret) 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Cake tools folder 2 | tools/* 3 | !tools/packages.config 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | *.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.pfx 196 | *.publishsettings 197 | node_modules/ 198 | orleans.codegen.cs 199 | 200 | # Since there are multiple workflows, uncomment next line to ignore bower_components 201 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 202 | #bower_components/ 203 | 204 | # RIA/Silverlight projects 205 | Generated_Code/ 206 | 207 | # Backup & report files from converting an old project file 208 | # to a newer Visual Studio version. Backup files are not needed, 209 | # because we have git ;-) 210 | _UpgradeReport_Files/ 211 | Backup*/ 212 | UpgradeLog*.XML 213 | UpgradeLog*.htm 214 | 215 | # SQL Server files 216 | *.mdf 217 | *.ldf 218 | 219 | # Business Intelligence projects 220 | *.rdl.data 221 | *.bim.layout 222 | *.bim_*.settings 223 | 224 | # Microsoft Fakes 225 | FakesAssemblies/ 226 | 227 | # GhostDoc plugin setting file 228 | *.GhostDoc.xml 229 | 230 | # Node.js Tools for Visual Studio 231 | .ntvs_analysis.dat 232 | 233 | # Visual Studio 6 build log 234 | *.plg 235 | 236 | # Visual Studio 6 workspace options file 237 | *.opt 238 | 239 | # Visual Studio LightSwitch build output 240 | **/*.HTMLClient/GeneratedArtifacts 241 | **/*.DesktopClient/GeneratedArtifacts 242 | **/*.DesktopClient/ModelManifest.xml 243 | **/*.Server/GeneratedArtifacts 244 | **/*.Server/ModelManifest.xml 245 | _Pvt_Extensions 246 | 247 | # Paket dependency manager 248 | .paket/paket.exe 249 | paket-files/ 250 | 251 | # FAKE - F# Make 252 | .fake/ 253 | 254 | # JetBrains Rider 255 | .idea/ 256 | *.sln.iml 257 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Israel Valverde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PipelineNet 2 | [![Build status](https://github.com/ipvalverde/PipelineNet/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/ipvalverde/PipelineNet/actions/workflows/build.yml) 3 | 4 | Pipeline net is a micro framework that helps you implement the pipeline and chain of responsibility patterns. With PipelineNet you can easily separate business logic and extend your application. 5 | Pipelines can be used to execute a series of middleware sequentially without expecting a return, while chains of responsibilities do the same thing but expecting a return. And you can do it all asynchronously too. 6 | 7 | You can obtain the package from this project through nuget: 8 | ``` 9 | Install-Package PipelineNet 10 | ``` 11 | 12 | Or if you're using dotnet CLI: 13 | ``` 14 | dotnet add package PipelineNet 15 | ``` 16 | 17 | 18 | 19 | 20 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 21 | 22 | - [Simple example](#simple-example) 23 | - [Pipeline vs Chain of responsibility](#pipeline-vs-chain-of-responsibility) 24 | - [Middleware](#middleware) 25 | - [Pipelines](#pipelines) 26 | - [Chains of responsibility](#chains-of-responsibility) 27 | - [Cancellation tokens](#cancellation-tokens) 28 | - [Middleware resolver](#middleware-resolver) 29 | - [ServiceProvider implementation](#serviceprovider-implementation) 30 | - [Unity implementation](#unity-implementation) 31 | - [Migrate from PipelineNet 0.10 to 0.11](#migrate-from-pipelinenet-010-to-011) 32 | - [Changes to the `Finally` method](#changes-to-the-finally-method) 33 | - [Changes to `MiddlewareResolverResult.IsDisposable`](#changes-to-middlewareresolverresultisdisposable) 34 | - [License](#license) 35 | 36 | 37 | 38 | ## Simple example 39 | Just to check how easy it is to use PipelineNet, here is an example of exception handling using a chain of responsibility: 40 | 41 | First we define some middleware: 42 | ```C# 43 | public class OutOfMemoryExceptionHandler : IMiddleware 44 | { 45 | public bool Run(Exception parameter, Func next) 46 | { 47 | if (parameter is OutOfMemoryException) 48 | { 49 | // Handle somehow 50 | return true; 51 | } 52 | 53 | return next(parameter); 54 | } 55 | } 56 | 57 | public class ArgumentExceptionHandler : IMiddleware 58 | { 59 | public bool Run(Exception parameter, Func next) 60 | { 61 | if (parameter is ArgumentException) 62 | { 63 | // Handle somehow 64 | return true; 65 | } 66 | 67 | return next(parameter); 68 | } 69 | } 70 | ``` 71 | Now we create a chain of responsibility with the middleware: 72 | ```C# 73 | var exceptionHandlersChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 74 | .Chain() // The order of middleware being chained matters 75 | .Chain(); 76 | ``` 77 | Now your instance of `ResponsibilityChain` can be executed as many times as you want: 78 | ```C# 79 | // The following line will execute only the OutOfMemoryExceptionHandler, which is the first middleware. 80 | var result = exceptionHandlersChain.Execute(new OutOfMemoryException()); // Result will be true 81 | 82 | // This one will execute the OutOfMemoryExceptionHandler first, and then the ArgumentExceptionHandler gets executed. 83 | result = exceptionHandlersChain.Execute(new ArgumentExceptionHandler()); // Result will be true 84 | 85 | // If no middleware matches returns a value, the default of the return type is returned, which in the case of 'bool' is false. 86 | result = exceptionHandlersChain.Execute(new InvalidOperationException()); // Result will be false 87 | ``` 88 | You can even define a fallback that will be executed after your entire chain: 89 | ```C# 90 | var exceptionHandlersChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 91 | .Chain() // The order of middleware being chained matters 92 | .Chain() 93 | .Finally(); 94 | 95 | public class ExceptionHandlerFallback : IFinally 96 | { 97 | public bool Finally(Exception parameter) 98 | { 99 | // Do something 100 | return true; 101 | } 102 | } 103 | ``` 104 | Now if the same line gets executed: 105 | ```C# 106 | var result = exceptionHandlersChain.Execute(new InvalidOperationException()); // Result will be true 107 | ``` 108 | The result will be true because of the type used in the `Finally` method. 109 | 110 | You can also choose to throw an exception in the finally instead of returning a value: 111 | ```C# 112 | var exceptionHandlersChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 113 | .Chain() 114 | .Chain() 115 | .Finally(); 116 | 117 | public class ThrowInvalidOperationException : IFinally 118 | { 119 | public bool Finally(Exception parameter) 120 | { 121 | throw new InvalidOperationException("End of the chain of responsibility reached. No middleware matches returned a value."); 122 | } 123 | } 124 | ``` 125 | Now if the end of the chain was reached and no middleware matches returned a value, the `InvalidOperationException` will be thrown. 126 | 127 | ## Pipeline vs Chain of responsibility 128 | Here is the difference between those two in PipelineNet: 129 | - Chain of responsibility: 130 | - Returns a value; 131 | - Have a fallback to execute at the end of the chain; 132 | - Used when you want that only one middleware to get executed based on an input, like the exception handling example; 133 | - Pipeline: 134 | - Does not return a value; 135 | - Used when you want to execute various middleware over an input, like filterings over an image; 136 | 137 | ## Middleware 138 | In PipelineNet the middleware is a definition of a piece of code that will be executed inside a pipeline or a chain of responsibility. 139 | 140 | We have four interfaces defining middleware: 141 | - The `IMiddleware` is used exclusively for pipelines, which does not have a return value. 142 | - The `IAsyncMiddleware` the same as above but used for asynchronous pipelines. 143 | - The `IMiddleware` is used exclusively for chains of responsibility, which does have a return value. 144 | - The `IAsyncMiddleware` the same as above but used for asynchronous chains of responsibility. 145 | 146 | Besides the differences between those four kinds of middleware, they all have a similar structure, the definition of a method `Run` 147 | in which the first parameter is the parameter passed to the Pipeline/Chain of responsibility beind executed and the second one 148 | is an `Action` of `Func` to execute the next middleware in the flow. **It is important to remember to invoke the next middleware 149 | by executing the `Action`/`Func` provided as the second parameter.** 150 | 151 | ## Pipelines 152 | The pipeline can be found in two flavours: `Pipeline` and `AsyncPipeline`. Both have the same functionaly, 153 | aggregate and execute a series of middleware. 154 | 155 | Here is an example of pipeline being configured with three middleware types: 156 | ```C# 157 | var pipeline = new Pipeline(new ActivatorMiddlewareResolver()) 158 | .Add() 159 | .Add() 160 | .Add(); 161 | ``` 162 | From now on, the instance of pipeline can be used to perform the same operation over as many Bitmap instance as you like: 163 | ```C# 164 | Bitmap image1 = (Bitmap) Image.FromFile("party-photo.png"); 165 | Bitmap image2 = (Bitmap) Image.FromFile("marriage-photo.png"); 166 | Bitmap image3 = (Bitmap) Image.FromFile("matrix-wallpaper.png"); 167 | 168 | pipeline.Execute(image1); 169 | pipeline.Execute(image2); 170 | pipeline.Execute(image3); 171 | ``` 172 | If you want to, you can use the asynchronous version, using asynchronous middleware. Changing the instantiation to: 173 | ```C# 174 | var pipeline = new AsyncPipeline(new ActivatorMiddlewareResolver()) 175 | .Add() 176 | .Add() 177 | .Add(); 178 | ``` 179 | And the usage may be optimized: 180 | ```C# 181 | Bitmap image1 = (Bitmap) Image.FromFile("party-photo.png"); 182 | Task task1 = pipeline.Execute(image1); // You can also simply use "await pipeline.Execute(image1);" 183 | 184 | Bitmap image2 = (Bitmap) Image.FromFile("marriage-photo.png"); 185 | Task task2 = pipeline.Execute(image2); 186 | 187 | Bitmap image3 = (Bitmap) Image.FromFile("matrix-wallpaper.png"); 188 | Task task3 = pipeline.Execute(image3); 189 | 190 | Task.WaitAll(new Task[]{ task1, task2, task3 }); 191 | ``` 192 | 193 | ## Chains of responsibility 194 | The chain of responsibility also has two implementations: `ResponsibilityChain` and `AsyncResponsibilityChain`. 195 | Both have the same functionaly, aggregate and execute a series of middleware retrieving a return type. 196 | 197 | One difference of chain responsibility when compared to pipeline is the fallback that can be defined with 198 | the `Finally` method. You can set one finally for chain of responsibility, calling the method more than once 199 | will replace the previous type used. 200 | 201 | As we already have an example of a chain of responsibility, here is an example using the asynchronous implementation: 202 | If you want to, you can use the asynchronous version, using asynchronous middleware. Changing the instantiation to: 203 | ```C# 204 | var exceptionHandlersChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 205 | .Chain() // The order of middleware being chained matters 206 | .Chain() 207 | .Finally(); 208 | 209 | public class ExceptionHandlerAsyncFallback : IAsyncFinally 210 | { 211 | public Task Finally(Exception parameter) 212 | { 213 | parameter.Data.Add("MoreExtraInfo", "More information about the exception."); 214 | return Task.FromResult(true); 215 | } 216 | } 217 | ``` 218 | And here is the execution: 219 | ```C# 220 | // The following line will execute only the OutOfMemoryExceptionHandler, which is the first middleware. 221 | bool result = await exceptionHandlersChain.Execute(new OutOfMemoryException()); // Result will be true 222 | 223 | // This one will execute the OutOfMemoryExceptionHandler first, and then the ArgumentExceptionHandler gets executed. 224 | result = await exceptionHandlersChain.Execute(new ArgumentException()); // Result will be true 225 | 226 | // If no middleware matches returns a value, the default of the return type is returned, which in the case of 'bool' is false. 227 | result = await exceptionHandlersChain.Execute(new InvalidOperationException()); // Result will be false 228 | ``` 229 | 230 | ## Cancellation tokens 231 | If you want to pass the cancellation token to your asynchronous pipeline middleware, you can do so by implementing the `ICancellableAsyncMiddleware` interface 232 | and passing the cancellation token argument to the `IAsyncPipeline.Execute` method: 233 | ```C# 234 | var pipeline = new AsyncPipeline(new ActivatorMiddlewareResolver()) 235 | .AddCancellable() 236 | .Add() // You can mix both kinds of asynchronous middleware 237 | .AddCancellable(); 238 | 239 | CancellationToken cancellationToken = CancellationToken.None; 240 | 241 | Bitmap image = (Bitmap) Image.FromFile("party-photo.png"); 242 | await pipeline.Execute(image, cancellationToken); 243 | 244 | public class RoudCornersCancellableAsyncMiddleware : ICancellableAsyncMiddleware 245 | { 246 | public async Task Run(Bitmap parameter, Func next, CancellationToken cancellationToken) 247 | { 248 | // Handle somehow 249 | await next(parameter); 250 | } 251 | } 252 | ``` 253 | And to pass the cancellation token to your asynchronous chain of responsibility middleware, you can implement the `ICancellableAsyncMiddleware` interface 254 | and pass the cancellation token argument to the `IAsynchChainOfResponsibility.Execute` method. 255 | 256 | ## Middleware resolver 257 | You may be wondering what is all this `ActivatorMiddlewareResolver` class being passed to every instance of pipeline and chain of responsibility. 258 | This is a default implementation of the `IMiddlewareResolver`, which is used to create instances of the middleware types. 259 | 260 | When configuring a pipeline/chain of responsibility you define the types of the middleware, when the flow is executed those middleware 261 | needs to be instantiated, so `IMiddlewareResolver` is responsible for that. Instantiated middleware are disposed automatically if they implement `IDisposable` or `IAsyncDisposable`. You can even create your own implementation, since the 262 | `ActivatorMiddlewareResolver` only works for parametersless constructors. 263 | 264 | ### ServiceProvider implementation 265 | 266 | An implementation of the middleware resolver for `IServiceProvider` was provided by [@mariusz96](https://github.com/mariusz96). It is tested against Microsoft.Extensions.DependencyInjection `8.X.X`, but should work with any dependency injection container that implements `IServiceProvider`. 267 | 268 | You can grab it from nuget with: 269 | 270 | ``` 271 | Install-Package PipelineNet.ServiceProvider 272 | ``` 273 | 274 | Use it with dependency injection: 275 | ```C# 276 | services.AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly); 277 | services.AddScoped>(serviceProvider => new AsyncPipeline(new ServiceProviderMiddlewareResolver(serviceProvider)) 278 | .Add() 279 | .Add() 280 | .Add()); 281 | services.AddScoped(); 282 | 283 | public interface IMyService 284 | { 285 | Task DoSomething(); 286 | } 287 | 288 | public class MyService : IMyService 289 | { 290 | private readonly IAsyncPipeline _pipeline; 291 | 292 | public MyService(IAsyncPipeline pipeline) 293 | { 294 | _pipeline = pipeline; 295 | } 296 | 297 | public async Task DoSomething() 298 | { 299 | Bitmap image = (Bitmap) Image.FromFile("party-photo.png"); 300 | await _pipeline.Execute(image); 301 | } 302 | } 303 | 304 | public class RoudCornersAsyncMiddleware : IAsyncMiddleware 305 | { 306 | private readonly ILogger _logger; 307 | 308 | public RoudCornersAsyncMiddleware(ILogger logger) 309 | { 310 | _logger = logger; 311 | } 312 | 313 | public async Task Run(Bitmap parameter, Func next) 314 | { 315 | _logger.LogInformation("Running RoudCornersAsyncMiddleware."); 316 | // Handle somehow 317 | await next(parameter); 318 | } 319 | } 320 | ``` 321 | 322 | Or instantiate it directly: 323 | ```C# 324 | services.AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly); 325 | 326 | public class MyService : IMyService 327 | { 328 | public async Task DoSomething() 329 | { 330 | IServiceProvider serviceProvider = GetServiceProvider(); 331 | 332 | IAsyncPipeline pipeline = new AsyncPipeline(new ServiceProviderMiddlewareResolver(serviceProvider)) 333 | .Add() 334 | .Add() 335 | .Add(); 336 | 337 | Bitmap image = (Bitmap) Image.FromFile("party-photo.png"); 338 | await pipeline.Execute(image); 339 | } 340 | 341 | private IServiceProvider GetServiceProvider() => // Get service provider somehow 342 | } 343 | ``` 344 | 345 | Note that `IServiceProvider` lifetime can vary based on the lifetime of the containing class. For example, if you resolve service from a scope, and it takes an `IServiceProvider`, it'll be a scoped instance. 346 | 347 | For more information on dependency injection, see: [Dependency injection - .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection). 348 | 349 | ### Unity implementation 350 | 351 | An implementation of the [middleware resolver for Unity](https://github.com/ShaneYu/PipelineNet.Unity) was kindly provided by [@ShaneYu](https://github.com/ShaneYu). It is tested against Unity.Container `5.X.X`, you can grab it from nuget with: 352 | 353 | ``` 354 | Install-Package PipelineNet.Unity 355 | ``` 356 | 357 | ## Migrate from PipelineNet 0.10 to 0.11 358 | 359 | ### Changes to the `Finally` method 360 | 361 | The `Finally` overloads that use `Func` have been made obsolete. This will be removed in the next major version. 362 | 363 | To migrate replace: 364 | ```C# 365 | var exceptionHandlersChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 366 | .Chain() 367 | .Chain() 368 | .Finally((parameter) => 369 | { 370 | // Do something 371 | return true; 372 | }); 373 | ``` 374 | With: 375 | ```C# 376 | var exceptionHandlersChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 377 | .Chain() 378 | .Chain() 379 | .Finally(); 380 | 381 | public class ExceptionHandlerFallback : IFinally 382 | { 383 | public bool Finally(Exception parameter) 384 | { 385 | // Do something 386 | return true; 387 | } 388 | } 389 | ``` 390 | 391 | ### Changes to `MiddlewareResolverResult.IsDisposable` 392 | 393 | The `IsDisposable` property is now marked as obsolete, please use the `Dispose` instead. The `IsDisposable` will be removed in the next major version. 394 | 395 | ## License 396 | This project is licensed under MIT. Please, feel free to contribute with code, issues or tips :) 397 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$true)] 3 | [string] $packageVersion 4 | ) 5 | 6 | function Invoke-CommandWithLog { 7 | param([string] $Command, [string] $CommandName) 8 | Write-Host "----------" -ForegroundColor Yellow 9 | Write-Host "Starting $CommandName process" -ForegroundColor Yellow 10 | Invoke-Expression $Command 11 | Write-Host "$CommandName finished`n" -ForegroundColor Yellow 12 | } 13 | 14 | function Publish-NugetPackage { 15 | param([string] $TestProject, [string] $Project, [string] $PackageName) 16 | 17 | $TestProjectPath = "$TestProject/$TestProject.csproj" 18 | $ProjectPath = "$Project/$Project.csproj" 19 | 20 | Invoke-CommandWithLog -Command "dotnet test $TestProjectPath -c Release --no-build" -CommandName "test" 21 | Invoke-CommandWithLog -Command "dotnet pack $ProjectPath --no-build -c Release --include-symbols -o artifacts -p:Version=$packageVersion" -CommandName "pack" 22 | 23 | 24 | Invoke-CommandWithLog -Command "dotnet nuget push artifacts/$PackageName.nupkg -s $($Env:NUGET_SOURCE) -k $($Env:NUGET_API_KEY)" -CommandName "publish" 25 | Invoke-CommandWithLog -Command "dotnet nuget push artifacts/$PackageName.symbols.nupkg -s $($Env:NUGET_SOURCE) -k $($Env:NUGET_API_KEY)" -CommandName "publish symbols" 26 | } 27 | 28 | $solutionPath = "PipelineNet.sln" 29 | 30 | Write-Host "`nGit version tag: '$packageVersion'`n" 31 | if ($packageVersion.StartsWith("v")) { 32 | $packageVersion = $packageVersion.Substring(1) 33 | } 34 | 35 | Set-Location -Path "src" -PassThru 36 | 37 | Write-Host "Package version: $packageVersion" -ForegroundColor Yellow 38 | 39 | Invoke-CommandWithLog -Command "dotnet build $solutionPath -c Release -p:Version=$packageVersion" -CommandName "build" 40 | 41 | 42 | Publish-NugetPackage -TestProject "PipelineNet.Tests" -Project "PipelineNet" -PackageName "PipelineNet.$packageVersion" 43 | Publish-NugetPackage -TestProject "PipelineNet.ServiceProvider.Tests" -Project "PipelineNet.ServiceProvider" -PackageName "PipelineNet.ServiceProvider.$packageVersion" 44 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider.Tests/MiddlewareResolver/ActivatorUtilitiesMiddlewareResolverTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using PipelineNet.ServiceProvider.MiddlewareResolver; 3 | using PipelineNet.Middleware; 4 | 5 | namespace PipelineNet.ServiceProvider.Tests.MiddlewareResolver 6 | { 7 | public class ActivatorUtilitiesMiddlewareResolverTests 8 | { 9 | #region Service defintions 10 | public interface ITransientService 11 | { 12 | bool Disposed { get; } 13 | } 14 | 15 | public class TransientService : ITransientService, IDisposable 16 | { 17 | public bool Disposed { get; set; } 18 | 19 | public void Dispose() 20 | { 21 | Disposed = true; 22 | } 23 | } 24 | 25 | public interface IScopedService 26 | { 27 | bool Disposed { get; } 28 | } 29 | 30 | public class ScopedService : IScopedService, IDisposable 31 | { 32 | public bool Disposed { get; set; } 33 | 34 | public void Dispose() 35 | { 36 | Disposed = true; 37 | } 38 | } 39 | 40 | public interface ISingletonService 41 | { 42 | bool Disposed { get; } 43 | } 44 | 45 | public class SingletonService : ISingletonService, IDisposable 46 | { 47 | public bool Disposed { get; set; } 48 | 49 | public void Dispose() 50 | { 51 | Disposed = true; 52 | } 53 | } 54 | #endregion 55 | 56 | #region Middleware definitions 57 | public class ParameterlessConstructorMiddleware : IMiddleware 58 | { 59 | public void Run(object parameter, Action next) 60 | { 61 | } 62 | } 63 | 64 | public class TransientMiddleware : IMiddleware 65 | { 66 | public ITransientService Service { get; } 67 | 68 | public TransientMiddleware(ITransientService service) 69 | { 70 | Service = service; 71 | } 72 | 73 | public void Run(object parameter, Action next) 74 | { 75 | } 76 | } 77 | 78 | public class ScopedMiddleware : IMiddleware 79 | { 80 | public IScopedService Service { get; } 81 | 82 | public ScopedMiddleware(IScopedService service) 83 | { 84 | Service = service; 85 | } 86 | 87 | public void Run(object parameter, Action next) 88 | { 89 | } 90 | } 91 | 92 | public class SingletonMiddleware : IMiddleware 93 | { 94 | public ISingletonService Service { get; } 95 | 96 | public SingletonMiddleware(ISingletonService service) 97 | { 98 | Service = service; 99 | } 100 | 101 | public void Run(object parameter, Action next) 102 | { 103 | } 104 | } 105 | 106 | public class DisposableMiddleware : IMiddleware, IDisposable 107 | { 108 | public void Run(object parameter, Action next) 109 | { 110 | } 111 | 112 | public void Dispose() 113 | { 114 | } 115 | } 116 | #endregion 117 | 118 | [Fact] 119 | public void Resolve_ResolvesParameterlessConstructorMiddleware() 120 | { 121 | var serviceProvider = new ServiceCollection() 122 | .BuildServiceProvider(validateScopes: true); 123 | var resolver = new ActivatorUtilitiesMiddlewareResolver(serviceProvider); 124 | 125 | var resolverResult = resolver.Resolve(typeof(ParameterlessConstructorMiddleware)); 126 | 127 | Assert.NotNull(resolverResult.Middleware); 128 | Assert.True(resolverResult.Dispose); 129 | } 130 | 131 | [Fact] 132 | public void Resolve_ResolvesTransientMiddleware() 133 | { 134 | var serviceProvider = new ServiceCollection() 135 | .AddTransient() 136 | .BuildServiceProvider(validateScopes: true); 137 | var resolver = new ActivatorUtilitiesMiddlewareResolver(serviceProvider); 138 | 139 | var resolverResult = resolver.Resolve(typeof(TransientMiddleware)); 140 | 141 | Assert.NotNull(resolverResult.Middleware); 142 | Assert.True(resolverResult.Dispose); 143 | } 144 | 145 | [Fact] 146 | public void Resolve_ResolvesScopedMiddleware() 147 | { 148 | var serviceProvider = new ServiceCollection() 149 | .AddScoped() 150 | .BuildServiceProvider(validateScopes: true); 151 | var scope = serviceProvider.CreateScope(); 152 | var resolver = new ActivatorUtilitiesMiddlewareResolver(scope.ServiceProvider); 153 | 154 | var resolverResult = resolver.Resolve(typeof(ScopedMiddleware)); 155 | 156 | Assert.NotNull(resolverResult.Middleware); 157 | Assert.True(resolverResult.Dispose); 158 | } 159 | 160 | [Fact] 161 | public void Resolve_ResolvesSingletonMiddleware() 162 | { 163 | var serviceProvider = new ServiceCollection() 164 | .AddSingleton() 165 | .BuildServiceProvider(validateScopes: true); 166 | var resolver = new ActivatorUtilitiesMiddlewareResolver(serviceProvider); 167 | 168 | var resolverResult = resolver.Resolve(typeof(SingletonMiddleware)); 169 | 170 | Assert.NotNull(resolverResult.Middleware); 171 | Assert.True(resolverResult.Dispose); 172 | } 173 | 174 | [Fact] 175 | public void Resolve_ResolvesDisposableMiddleware() 176 | { 177 | var serviceProvider = new ServiceCollection() 178 | .AddSingleton() 179 | .BuildServiceProvider(validateScopes: true); 180 | var resolver = new ActivatorUtilitiesMiddlewareResolver(serviceProvider); 181 | 182 | var resolverResult = resolver.Resolve(typeof(DisposableMiddleware)); 183 | 184 | Assert.NotNull(resolverResult.Middleware); 185 | Assert.True(resolverResult.Dispose); 186 | } 187 | 188 | [Fact] 189 | public void Resolve_TransientServiceGetsDisposedWhenRootServiceProviderIsDisposed() 190 | { 191 | var serviceProvider = new ServiceCollection() 192 | .AddTransient() 193 | .BuildServiceProvider(validateScopes: true); 194 | var resolver = new ActivatorUtilitiesMiddlewareResolver(serviceProvider); 195 | 196 | var resolverResult = resolver.Resolve(typeof(TransientMiddleware)); 197 | var middleware = (TransientMiddleware)resolverResult.Middleware; 198 | serviceProvider.Dispose(); 199 | 200 | Assert.True(middleware.Service.Disposed); 201 | } 202 | 203 | [Fact] 204 | public void Resolve_TransientServiceGetsDisposedWhenScopeIsDisposed() 205 | { 206 | var serviceProvider = new ServiceCollection() 207 | .AddTransient() 208 | .BuildServiceProvider(validateScopes: true); 209 | var scope = serviceProvider.CreateScope(); 210 | var resolver = new ActivatorUtilitiesMiddlewareResolver(scope.ServiceProvider); 211 | 212 | var resolverResult = resolver.Resolve(typeof(TransientMiddleware)); 213 | var middleware = (TransientMiddleware)resolverResult.Middleware; 214 | scope.Dispose(); 215 | 216 | Assert.True(middleware.Service.Disposed); 217 | } 218 | 219 | [Fact] 220 | public void Resolve_ScopedServiceGetsDisposed() 221 | { 222 | var serviceProvider = new ServiceCollection() 223 | .AddScoped() 224 | .BuildServiceProvider(validateScopes: true); 225 | var scope = serviceProvider.CreateScope(); 226 | var resolver = new ActivatorUtilitiesMiddlewareResolver(scope.ServiceProvider); 227 | 228 | var resolverResult = resolver.Resolve(typeof(ScopedMiddleware)); 229 | var middleware = (ScopedMiddleware)resolverResult.Middleware; 230 | scope.Dispose(); 231 | 232 | Assert.True(middleware.Service.Disposed); 233 | } 234 | 235 | [Fact] 236 | public void Resolve_SingletonServiceGetsDisposed() 237 | { 238 | var serviceProvider = new ServiceCollection() 239 | .AddSingleton() 240 | .BuildServiceProvider(validateScopes: true); 241 | var resolver = new ActivatorUtilitiesMiddlewareResolver(serviceProvider); 242 | 243 | var resolverResult = resolver.Resolve(typeof(SingletonMiddleware)); 244 | var middleware = (SingletonMiddleware)resolverResult.Middleware; 245 | serviceProvider.Dispose(); 246 | 247 | Assert.True(middleware.Service.Disposed); 248 | } 249 | 250 | [Fact] 251 | public void Resolve_ThrowsWhenServiceIsNotRegistered() 252 | { 253 | var serviceProvider = new ServiceCollection() 254 | .BuildServiceProvider(validateScopes: true); 255 | var resolver = new ActivatorUtilitiesMiddlewareResolver(serviceProvider); 256 | 257 | Assert.ThrowsAny(() => 258 | resolver.Resolve(typeof(TransientMiddleware))); 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider.Tests/MiddlewareResolver/ServiceProviderMiddlewareResolverTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using PipelineNet.ServiceProvider.MiddlewareResolver; 3 | using PipelineNet.Middleware; 4 | 5 | namespace PipelineNet.ServiceProvider.Tests.MiddlewareResolver 6 | { 7 | public class ServiceProviderMiddlewareResolverTests 8 | { 9 | #region Service defintions 10 | public interface ITransientService 11 | { 12 | } 13 | 14 | public class TransientService : ITransientService 15 | { 16 | } 17 | 18 | public interface IScopedService 19 | { 20 | } 21 | 22 | public class ScopedService : IScopedService 23 | { 24 | } 25 | 26 | public interface ISingletonService 27 | { 28 | } 29 | 30 | public class SingletonService : ISingletonService 31 | { 32 | } 33 | #endregion 34 | 35 | #region Middleware definitions 36 | public class ParameterlessConstructorMiddleware : IMiddleware 37 | { 38 | public void Run(object parameter, Action next) 39 | { 40 | } 41 | } 42 | 43 | public class TransientMiddleware : IMiddleware 44 | { 45 | public ITransientService Service { get; } 46 | 47 | public TransientMiddleware(ITransientService service) 48 | { 49 | Service = service; 50 | } 51 | 52 | public void Run(object parameter, Action next) 53 | { 54 | } 55 | } 56 | 57 | public class ScopedMiddleware : IMiddleware 58 | { 59 | public IScopedService Service { get; } 60 | 61 | public ScopedMiddleware(IScopedService service) 62 | { 63 | Service = service; 64 | } 65 | 66 | public void Run(object parameter, Action next) 67 | { 68 | } 69 | } 70 | 71 | public class SingletonMiddleware : IMiddleware 72 | { 73 | public ISingletonService Service { get; } 74 | 75 | public SingletonMiddleware(ISingletonService service) 76 | { 77 | Service = service; 78 | } 79 | 80 | public void Run(object parameter, Action next) 81 | { 82 | } 83 | } 84 | 85 | public class DisposableMiddleware : IMiddleware, IDisposable 86 | { 87 | public void Run(object parameter, Action next) 88 | { 89 | } 90 | 91 | public void Dispose() 92 | { 93 | } 94 | } 95 | #endregion 96 | 97 | [Fact] 98 | public void Resolve_ResolvesParameterlessConstructorMiddleware() 99 | { 100 | var serviceProvider = new ServiceCollection() 101 | .AddTransient() 102 | .BuildServiceProvider(validateScopes: true); 103 | var resolver = new ServiceProviderMiddlewareResolver(serviceProvider); 104 | 105 | var resolverResult = resolver.Resolve(typeof(ParameterlessConstructorMiddleware)); 106 | 107 | Assert.NotNull(resolverResult.Middleware); 108 | Assert.False(resolverResult.Dispose); 109 | } 110 | 111 | [Fact] 112 | public void Resolve_ResolvesTransientMiddleware() 113 | { 114 | var serviceProvider = new ServiceCollection() 115 | .AddTransient() 116 | .AddTransient() 117 | .BuildServiceProvider(validateScopes: true); 118 | var resolver = new ServiceProviderMiddlewareResolver(serviceProvider); 119 | 120 | var resolverResult = resolver.Resolve(typeof(TransientMiddleware)); 121 | 122 | Assert.NotNull(resolverResult.Middleware); 123 | Assert.False(resolverResult.Dispose); 124 | } 125 | 126 | [Fact] 127 | public void Resolve_ResolvesScopedMiddleware() 128 | { 129 | var serviceProvider = new ServiceCollection() 130 | .AddScoped() 131 | .AddScoped() 132 | .BuildServiceProvider(validateScopes: true); 133 | var scope = serviceProvider.CreateScope(); 134 | var resolver = new ServiceProviderMiddlewareResolver(scope.ServiceProvider); 135 | 136 | var resolverResult = resolver.Resolve(typeof(ScopedMiddleware)); 137 | 138 | Assert.NotNull(resolverResult.Middleware); 139 | Assert.False(resolverResult.Dispose); 140 | } 141 | 142 | [Fact] 143 | public void Resolve_ResolvesSingletonMiddleware() 144 | { 145 | var serviceProvider = new ServiceCollection() 146 | .AddSingleton() 147 | .AddSingleton() 148 | .BuildServiceProvider(validateScopes: true); 149 | var resolver = new ServiceProviderMiddlewareResolver(serviceProvider); 150 | 151 | var resolverResult = resolver.Resolve(typeof(SingletonMiddleware)); 152 | 153 | Assert.NotNull(resolverResult.Middleware); 154 | Assert.False(resolverResult.Dispose); 155 | } 156 | 157 | [Fact] 158 | public void Resolve_ResolvesDisposableMiddleware() 159 | { 160 | var serviceProvider = new ServiceCollection() 161 | .AddSingleton() 162 | .AddSingleton() 163 | .BuildServiceProvider(validateScopes: true); 164 | var resolver = new ServiceProviderMiddlewareResolver(serviceProvider); 165 | 166 | var resolverResult = resolver.Resolve(typeof(DisposableMiddleware)); 167 | 168 | Assert.NotNull(resolverResult.Middleware); 169 | Assert.False(resolverResult.Dispose); 170 | } 171 | 172 | [Fact] 173 | public void Resolve_ThrowsInvalidOperationExceptionWhenServiceIsNotRegistered() 174 | { 175 | var serviceProvider = new ServiceCollection() 176 | .BuildServiceProvider(validateScopes: true); 177 | var resolver = new ServiceProviderMiddlewareResolver(serviceProvider); 178 | 179 | Assert.Throws(() => 180 | resolver.Resolve(typeof(TransientMiddleware))); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider.Tests/PipelineNet.ServiceProvider.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider.Tests/ServiceCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using PipelineNet.Middleware; 4 | using PipelineNet.Pipelines; 5 | using PipelineNet.ServiceProvider.MiddlewareResolver; 6 | using Xunit.Abstractions; 7 | 8 | namespace PipelineNet.ServiceProvider.Tests 9 | { 10 | public class ServiceCollectionExtensionsTests 11 | { 12 | #region Parameter definitions 13 | public class Image 14 | { 15 | public static Image FromFile(string filename) => new Bitmap(); 16 | } 17 | 18 | public class Bitmap : Image 19 | { 20 | } 21 | #endregion 22 | 23 | #region Service definitions 24 | public interface IMyService 25 | { 26 | Task DoSomething(); 27 | } 28 | 29 | public class MyService : IMyService 30 | { 31 | private readonly IAsyncPipeline _pipeline; 32 | 33 | public MyService(IAsyncPipeline pipeline) 34 | { 35 | _pipeline = pipeline; 36 | } 37 | 38 | public async Task DoSomething() 39 | { 40 | Bitmap image = (Bitmap) Image.FromFile("party-photo.png"); 41 | await _pipeline.Execute(image); 42 | } 43 | } 44 | #endregion 45 | 46 | #region Middleware definitions 47 | public class RoudCornersAsyncMiddleware : IAsyncMiddleware 48 | { 49 | private readonly ILogger _logger; 50 | 51 | public RoudCornersAsyncMiddleware(ILogger logger) 52 | { 53 | _logger = logger; 54 | } 55 | 56 | public async Task Run(Bitmap parameter, Func next) 57 | { 58 | _logger.LogInformation("Running RoudCornersAsyncMiddleware."); 59 | // Handle somehow 60 | await next(parameter); 61 | } 62 | } 63 | 64 | public class AddTransparencyAsyncMiddleware : IAsyncMiddleware 65 | { 66 | private readonly ILogger _logger; 67 | 68 | public AddTransparencyAsyncMiddleware(ILogger logger) 69 | { 70 | _logger = logger; 71 | } 72 | 73 | public async Task Run(Bitmap parameter, Func next) 74 | { 75 | _logger.LogInformation("Running AddTransparencyAsyncMiddleware."); 76 | // Handle somehow 77 | await next(parameter); 78 | } 79 | } 80 | 81 | public class AddWatermarkAsyncMiddleware : IAsyncMiddleware 82 | { 83 | private readonly ILogger _logger; 84 | 85 | public AddWatermarkAsyncMiddleware(ILogger logger) 86 | { 87 | _logger = logger; 88 | } 89 | 90 | public async Task Run(Bitmap parameter, Func next) 91 | { 92 | _logger.LogInformation("Running AddWatermarkAsyncMiddleware."); 93 | // Handle somehow 94 | await next(parameter); 95 | } 96 | } 97 | #endregion 98 | 99 | #region Logger definitions 100 | public class TestOutputHelperLoggerProvider : ILoggerProvider 101 | { 102 | private readonly ITestOutputHelper _output; 103 | 104 | public TestOutputHelperLoggerProvider(ITestOutputHelper output) 105 | { 106 | _output = output; 107 | } 108 | 109 | public ILogger CreateLogger(string categoryName) => 110 | new TestOutputHelperLogger(_output); 111 | 112 | public void Dispose() 113 | { 114 | } 115 | } 116 | 117 | public class TestOutputHelperLogger : ILogger 118 | { 119 | private readonly ITestOutputHelper _output; 120 | 121 | public TestOutputHelperLogger(ITestOutputHelper output) 122 | { 123 | _output = output; 124 | } 125 | 126 | public IDisposable? BeginScope(TState state) where TState : notnull => 127 | new NullScope(); 128 | 129 | public bool IsEnabled(LogLevel logLevel) => true; 130 | 131 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => 132 | _output.WriteLine($"{eventId}:{logLevel}:{formatter(state, exception)}"); 133 | 134 | private class NullScope : IDisposable 135 | { 136 | public void Dispose() 137 | { 138 | } 139 | } 140 | } 141 | #endregion 142 | 143 | private readonly ITestOutputHelper _output; 144 | 145 | public ServiceCollectionExtensionsTests(ITestOutputHelper output) 146 | { 147 | _output = output; 148 | } 149 | 150 | [Fact] 151 | public async Task AddMiddlewareFromAssembly_Works() 152 | { 153 | var serviceProvider = new ServiceCollection() 154 | .AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly) 155 | .AddScoped>(serviceProvider => new AsyncPipeline(new ServiceProviderMiddlewareResolver(serviceProvider)) 156 | .Add() 157 | .Add() 158 | .Add()) 159 | .AddScoped() 160 | .AddLogging(bulider => bulider.Services.AddSingleton(new TestOutputHelperLoggerProvider(_output))) 161 | .BuildServiceProvider(); 162 | var scope = serviceProvider.CreateScope(); 163 | var service = scope.ServiceProvider.GetRequiredService(); 164 | 165 | await service.DoSomething(); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider/MiddlewareResolver/ActivatorUtilitiesMiddlewareResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using PipelineNet.MiddlewareResolver; 3 | using System; 4 | 5 | namespace PipelineNet.ServiceProvider.MiddlewareResolver 6 | { 7 | /// 8 | /// An implementation of that creates 9 | /// instances using the . 10 | /// 11 | public class ActivatorUtilitiesMiddlewareResolver : IMiddlewareResolver 12 | { 13 | private readonly IServiceProvider _serviceProvider; 14 | 15 | /// 16 | /// Creates a new . 17 | /// 18 | /// The . 19 | public ActivatorUtilitiesMiddlewareResolver(IServiceProvider serviceProvider) 20 | { 21 | _serviceProvider = serviceProvider ?? throw new ArgumentNullException("serviceProvider", 22 | "An instance of IServiceProvider must be provided."); ; 23 | } 24 | 25 | /// 26 | public MiddlewareResolverResult Resolve(Type type) 27 | { 28 | var middleware = ActivatorUtilities.CreateInstance(_serviceProvider, type); 29 | bool dispose = true; 30 | 31 | return new MiddlewareResolverResult() 32 | { 33 | Middleware = middleware, 34 | Dispose = dispose 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider/MiddlewareResolver/ServiceProviderMiddlewareResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using PipelineNet.MiddlewareResolver; 3 | using System; 4 | 5 | namespace PipelineNet.ServiceProvider.MiddlewareResolver 6 | { 7 | /// 8 | /// An implementation of that resolves 9 | /// instances using the . 10 | /// 11 | public class ServiceProviderMiddlewareResolver : IMiddlewareResolver 12 | { 13 | private readonly IServiceProvider _serviceProvider; 14 | 15 | /// 16 | /// Creates a new . 17 | /// 18 | /// The . 19 | public ServiceProviderMiddlewareResolver(IServiceProvider serviceProvider) 20 | { 21 | _serviceProvider = serviceProvider ?? throw new ArgumentNullException("serviceProvider", 22 | "An instance of IServiceProvider must be provided."); ; 23 | } 24 | 25 | /// 26 | /// Resolves an instance of the give middleware type. 27 | /// 28 | /// The middleware type that will be resolved. 29 | /// An instance of the middleware. 30 | public MiddlewareResolverResult Resolve(Type type) 31 | { 32 | var middleware = _serviceProvider.GetRequiredService(type); 33 | bool dispose = false; 34 | 35 | return new MiddlewareResolverResult() 36 | { 37 | Middleware = middleware, 38 | Dispose = dispose 39 | }; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider/PipelineNet.ServiceProvider.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1 5 | 6 | 7 | 8 | 1.0.0 9 | PipelineNet.ServiceProvider 10 | PipelineNet.ServiceProvider 11 | $(Version) 12 | Mariusz Stępień 13 | A micro framework that helps you implement pipeline and chain of responsibility patterns. 14 | https://github.com/ipvalverde/PipelineNet 15 | MIT 16 | Copyright © Mariusz Stępień, Israel Valverde 17 | Pipeline .NetCore Portable Chain Responsibility ChainOfResponsibility Core NetStandard DI IOC DependencyInjection 18 | README.md 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/PipelineNet.ServiceProvider/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using PipelineNet.Finally; 4 | using PipelineNet.Middleware; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | 10 | namespace PipelineNet.ServiceProvider 11 | { 12 | /// 13 | /// Extension methods to the service collection. 14 | /// 15 | public static class ServiceCollectionExtensions 16 | { 17 | /// 18 | /// Adds all middleware from the assembly. 19 | /// 20 | /// The service collection. 21 | /// The assembly to scan. 22 | /// The lifetime of the registered middleware. 23 | /// The service collection. 24 | public static IServiceCollection AddMiddlewareFromAssembly( 25 | this IServiceCollection services, 26 | Assembly assembly, 27 | ServiceLifetime lifetime = ServiceLifetime.Scoped) 28 | { 29 | if (services == null) throw new ArgumentNullException("services"); 30 | if (assembly == null) throw new ArgumentNullException("assembly"); 31 | 32 | var openGenericTypes = new List() 33 | { 34 | typeof(IMiddleware<>), 35 | typeof(IAsyncMiddleware<>), 36 | typeof(ICancellableAsyncMiddleware<>), 37 | typeof(IMiddleware<,>), 38 | typeof(IAsyncMiddleware<,>), 39 | typeof(ICancellableAsyncMiddleware<,>), 40 | typeof(IFinally<,>), 41 | typeof(IAsyncFinally<,>), 42 | typeof(ICancellableAsyncFinally<,>) 43 | }; 44 | 45 | var types = assembly.GetTypes(); 46 | 47 | foreach (var type in types) 48 | { 49 | if (!type.IsAbstract 50 | && !type.IsGenericTypeDefinition 51 | && type.GetInterfaces().Any(i => i.IsGenericType && openGenericTypes.Contains(i.GetGenericTypeDefinition()))) 52 | { 53 | services.TryAdd(new ServiceDescriptor(type, type, lifetime)); 54 | } 55 | } 56 | 57 | return services; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/PipelineNet.Tests/ChainsOfResponsibility/AsyncResponsibilityChainTests.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.ChainsOfResponsibility; 2 | using PipelineNet.Finally; 3 | using PipelineNet.Middleware; 4 | using PipelineNet.MiddlewareResolver; 5 | using Xunit; 6 | 7 | namespace PipelineNet.Tests.ChainsOfResponsibility 8 | { 9 | public class AsyncResponsibilityChainTests 10 | { 11 | #region Parameter definitions 12 | public class MyException : Exception 13 | { 14 | public string HandlerName { get; set; } 15 | } 16 | 17 | public class InvalidateDataException : MyException 18 | { } 19 | 20 | public class UnavailableResourcesException : MyException 21 | { } 22 | #endregion 23 | 24 | #region Middleware definitions 25 | public class SyncReplaceNewLineMiddleware : IAsyncMiddleware 26 | { 27 | public Task Run(string input, Func> executeNext) 28 | { 29 | var newLineReplaced = input.Replace("\n", " "); 30 | 31 | var nextMiddleware = executeNext(newLineReplaced); 32 | var result = nextMiddleware.Result; 33 | return Task.FromResult(result); 34 | } 35 | } 36 | 37 | public class SyncTrimMiddleware : IAsyncMiddleware 38 | { 39 | public Task Run(string input, Func> executeNext) 40 | { 41 | var trimmedString = input.Trim(); 42 | 43 | var nextMiddleware = executeNext(trimmedString); 44 | var result = nextMiddleware.Result; 45 | return Task.FromResult(result); 46 | } 47 | } 48 | 49 | public class UnavailableResourcesExceptionHandler : IAsyncMiddleware 50 | { 51 | public async Task Run(Exception exception, Func> executeNext) 52 | { 53 | var castedException = exception as UnavailableResourcesException; 54 | if (castedException != null) 55 | { 56 | castedException.HandlerName = this.GetType().Name; 57 | return true; 58 | } 59 | return await executeNext(exception); 60 | } 61 | } 62 | 63 | public class InvalidateDataExceptionHandler : IAsyncMiddleware 64 | { 65 | public async Task Run(Exception exception, Func> executeNext) 66 | { 67 | var castedException = exception as InvalidateDataException; 68 | if (castedException != null) 69 | { 70 | castedException.HandlerName = this.GetType().Name; 71 | return true; 72 | } 73 | return await executeNext(exception); 74 | } 75 | } 76 | 77 | public class MyExceptionHandler : IAsyncMiddleware 78 | { 79 | public async Task Run(Exception exception, Func> executeNext) 80 | { 81 | var castedException = exception as MyException; 82 | if (castedException != null) 83 | { 84 | castedException.HandlerName = this.GetType().Name; 85 | return true; 86 | } 87 | return await executeNext(exception); 88 | } 89 | } 90 | 91 | public class ThrowIfCancellationRequestedMiddleware : ICancellableAsyncMiddleware 92 | { 93 | public async Task Run(Exception exception, Func> executeNext, CancellationToken cancellationToken) 94 | { 95 | cancellationToken.ThrowIfCancellationRequested(); 96 | return await executeNext(exception); 97 | } 98 | } 99 | 100 | public class FinallyThrow : IAsyncFinally 101 | { 102 | public Task Finally(Exception exception) 103 | { 104 | throw new InvalidOperationException( 105 | "End of the asynchronous chain of responsibility reached. No middleware matches returned a value."); 106 | } 107 | } 108 | 109 | public class FinallyThrowIfCancellationRequested : ICancellableAsyncFinally 110 | { 111 | public Task Finally(Exception exception, CancellationToken cancellationToken) 112 | { 113 | cancellationToken.ThrowIfCancellationRequested(); 114 | return Task.FromResult(default(bool)); 115 | } 116 | } 117 | #endregion 118 | 119 | [Fact] 120 | public async Task Execute_CreateChainOfMiddlewareToHandleException_TheRightMiddleHandlesTheException() 121 | { 122 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 123 | .Chain() 124 | .Chain() 125 | .Chain(); 126 | 127 | // Creates an invalid exception that should be handled by 'InvalidateDataExceptionHandler'. 128 | var invalidException = new InvalidateDataException(); 129 | 130 | var result = await responsibilityChain.Execute(invalidException); 131 | 132 | // Check if the given exception was handled 133 | Assert.True(result); 134 | 135 | // Check if the correct handler handled the exception. 136 | Assert.Equal(typeof(InvalidateDataExceptionHandler).Name, invalidException.HandlerName); 137 | } 138 | 139 | [Fact] 140 | public async Task Execute_ChainOfMiddlewareThatDoesNotHandleTheException_ChainReturnsDefaultValue() 141 | { 142 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 143 | .Chain() 144 | .Chain() 145 | .Chain(); 146 | 147 | // Creates an ArgumentNullException, that will not be handled by any middleware. 148 | var excception = new ArgumentNullException(); 149 | 150 | // The result should be the default for 'bool'. 151 | var result = await responsibilityChain.Execute(excception); 152 | 153 | Assert.Equal(default(bool), result); 154 | } 155 | 156 | #pragma warning disable CS0618 // Type or member is obsolete 157 | [Fact] 158 | public async Task Execute_ChainOfMiddlewareWithFinallyFunc_FinallyFuncIsExecuted() 159 | { 160 | const string ExceptionSource = "EXCEPTION_SOURCE"; 161 | 162 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 163 | .Chain() 164 | .Chain(typeof(InvalidateDataExceptionHandler)) 165 | .Chain() 166 | .Finally((ex) => 167 | { 168 | ex.Source = ExceptionSource; 169 | return Task.FromResult(true); 170 | }); 171 | 172 | // Creates an ArgumentNullException, that will not be handled by any middleware. 173 | var exception = new ArgumentNullException(); 174 | 175 | // The result should true, since the finally function will be executed. 176 | var result = await responsibilityChain.Execute(exception); 177 | 178 | Assert.True(result); 179 | 180 | Assert.Equal(ExceptionSource, exception.Source); 181 | } 182 | #pragma warning restore CS0618 // Type or member is obsolete 183 | 184 | /// 185 | /// Tests the method. 186 | /// 187 | [Fact] 188 | public void Chain_AddTypeThatIsNotAMiddleware_ThrowsException() 189 | { 190 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()); 191 | Assert.Throws(() => 192 | { 193 | responsibilityChain.Chain(typeof(ResponsibilityChainTests)); 194 | }); 195 | } 196 | 197 | 198 | 199 | #pragma warning disable CS0618 // Type or member is obsolete 200 | /// 201 | /// Try to generate a deadlock in synchronous middleware. 202 | /// 203 | [Fact] 204 | public void Execute_SynchronousChainOfResponsibility_SuccessfullyExecute() 205 | { 206 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 207 | .Chain() 208 | .Chain() 209 | .Finally(input => Task.FromResult(input)); 210 | 211 | var resultTask = responsibilityChain.Execute(" Test\nwith spaces\n and new lines \n "); 212 | var result = resultTask.Result; 213 | 214 | Assert.Equal("Test with spaces and new lines", result); 215 | } 216 | 217 | [Fact] 218 | public async Task Execute_ChainOfMiddlewareWithCancellableMiddleware_CancellableMiddlewareIsExecuted() 219 | { 220 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 221 | .Chain() 222 | .Chain(typeof(InvalidateDataExceptionHandler)) 223 | .Chain() 224 | .ChainCancellable(); 225 | 226 | // Creates an ArgumentNullException. The 'ThrowIfCancellationRequestedMiddleware' 227 | // middleware should be the last one to execute. 228 | var exception = new ArgumentNullException(); 229 | 230 | // Create the cancellation token in the canceled state. 231 | var cancellationToken = new CancellationToken(canceled: true); 232 | 233 | // The 'ThrowIfCancellationRequestedMiddleware' should throw 'OperationCanceledException'. 234 | await Assert.ThrowsAsync(() => responsibilityChain.Execute(exception, cancellationToken)); 235 | } 236 | #pragma warning restore CS0618 // Type or member is obsolete 237 | 238 | [Fact] 239 | public async Task Execute_ChainOfMiddlewareWithFinally_FinallyIsExecuted() 240 | { 241 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 242 | .Chain() 243 | .Chain(typeof(InvalidateDataExceptionHandler)) 244 | .Chain() 245 | .Finally(); 246 | 247 | // Creates an ArgumentNullException. The 'MyExceptionHandler' 248 | // middleware should be the last one to execute. 249 | var exception = new ArgumentNullException(); 250 | 251 | // The 'FinallyThrow' should throw 'InvalidOperationException'. 252 | await Assert.ThrowsAsync(() => responsibilityChain.Execute(exception)); 253 | } 254 | 255 | [Fact] 256 | public async Task Execute_ChainOfMiddlewareWithCancellableFinally_CancellableFinallyIsExecuted() 257 | { 258 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 259 | .Chain() 260 | .Chain(typeof(InvalidateDataExceptionHandler)) 261 | .Chain() 262 | .CancellableFinally(); 263 | 264 | // Creates an ArgumentNullException. The 'MyExceptionHandler' 265 | // middleware should be the last one to execute. 266 | var exception = new ArgumentNullException(); 267 | 268 | // Create the cancellation token in the canceled state. 269 | var cancellationToken = new CancellationToken(canceled: true); 270 | 271 | // The 'FinallyThrowIfCancellationRequested' should throw 'OperationCanceledException'. 272 | await Assert.ThrowsAsync(() => responsibilityChain.Execute(exception, cancellationToken)); 273 | } 274 | 275 | [Fact] 276 | public async Task Execute_EmptyChainOfMiddlewareWithFinally_FinallyIsExecuted() 277 | { 278 | var responsibilityChain = new AsyncResponsibilityChain(new ActivatorMiddlewareResolver()) 279 | .Finally(); 280 | 281 | // Creates an ArgumentNullException. 282 | var exception = new ArgumentNullException(); 283 | 284 | // The 'FinallyThrow' should throw 'InvalidOperationException'. 285 | await Assert.ThrowsAsync(() => responsibilityChain.Execute(exception)); 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/PipelineNet.Tests/ChainsOfResponsibility/ResponsibilityChainTests.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.ChainsOfResponsibility; 2 | using PipelineNet.Finally; 3 | using PipelineNet.Middleware; 4 | using PipelineNet.MiddlewareResolver; 5 | using Xunit; 6 | 7 | namespace PipelineNet.Tests.ChainsOfResponsibility 8 | { 9 | public class ResponsibilityChainTests 10 | { 11 | #region Parameter definitions 12 | public class MyException : Exception 13 | { 14 | public string HandlerName { get; set; } 15 | } 16 | 17 | public class InvalidateDataException : MyException 18 | { } 19 | 20 | public class UnavailableResourcesException : MyException 21 | { } 22 | #endregion 23 | 24 | #region Middleware definitions 25 | public class UnavailableResourcesExceptionHandler : IMiddleware 26 | { 27 | public bool Run(Exception exception, Func executeNext) 28 | { 29 | var castedException = exception as UnavailableResourcesException; 30 | if (castedException != null) 31 | { 32 | castedException.HandlerName = this.GetType().Name; 33 | return true; 34 | } 35 | return executeNext(exception); 36 | } 37 | } 38 | 39 | public class InvalidateDataExceptionHandler : IMiddleware 40 | { 41 | public bool Run(Exception exception, Func executeNext) 42 | { 43 | var castedException = exception as InvalidateDataException; 44 | if (castedException != null) 45 | { 46 | castedException.HandlerName = this.GetType().Name; 47 | return true; 48 | } 49 | return executeNext(exception); 50 | } 51 | } 52 | 53 | public class MyExceptionHandler : IMiddleware 54 | { 55 | public bool Run(Exception exception, Func executeNext) 56 | { 57 | var castedException = exception as MyException; 58 | if (castedException != null) 59 | { 60 | castedException.HandlerName = this.GetType().Name; 61 | return true; 62 | } 63 | return executeNext(exception); 64 | } 65 | } 66 | 67 | public class FinallyThrow : IFinally 68 | { 69 | public bool Finally(Exception parameter) 70 | { 71 | throw new InvalidOperationException( 72 | "End of the chain of responsibility reached. No middleware matches returned a value."); 73 | } 74 | } 75 | #endregion 76 | 77 | [Fact] 78 | public void Execute_CreateChainOfMiddlewareToHandleException_TheRightMiddleHandlesTheException() 79 | { 80 | var responsibilityChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 81 | .Chain() 82 | .Chain() 83 | .Chain(); 84 | 85 | // Creates an invalid exception that should be handled by 'InvalidateDataExceptionHandler'. 86 | var invalidException = new InvalidateDataException(); 87 | 88 | var result = responsibilityChain.Execute(invalidException); 89 | 90 | // Check if the given exception was handled 91 | Assert.True(result); 92 | 93 | // Check if the correct handler handled the exception. 94 | Assert.Equal(typeof(InvalidateDataExceptionHandler).Name, invalidException.HandlerName); 95 | } 96 | 97 | [Fact] 98 | public void Execute_ChainOfMiddlewareThatDoesNotHandleTheException_ChainReturnsDefaultValue() 99 | { 100 | var responsibilityChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 101 | .Chain() 102 | .Chain() 103 | .Chain(); 104 | 105 | // Creates an ArgumentNullException, that will not be handled by any middleware. 106 | var excception = new ArgumentNullException(); 107 | 108 | // The result should be the default for 'bool'. 109 | var result = responsibilityChain.Execute(excception); 110 | 111 | Assert.Equal(default(bool), result); 112 | } 113 | 114 | #pragma warning disable CS0618 // Type or member is obsolete 115 | [Fact] 116 | public void Execute_ChainOfMiddlewareWithFinallyFunc_FinallyFuncIsExecuted() 117 | { 118 | const string ExceptionSource = "EXCEPTION_SOURCE"; 119 | 120 | var responsibilityChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 121 | .Chain() 122 | .Chain(typeof(InvalidateDataExceptionHandler)) 123 | .Chain() 124 | .Finally((ex) => 125 | { 126 | ex.Source = ExceptionSource; 127 | return true; 128 | }); 129 | 130 | // Creates an ArgumentNullException, that will not be handled by any middleware. 131 | var exception = new ArgumentNullException(); 132 | 133 | // The result should true, since the finally function will be executed. 134 | var result = responsibilityChain.Execute(exception); 135 | 136 | Assert.True(result); 137 | 138 | Assert.Equal(ExceptionSource, exception.Source); 139 | } 140 | #pragma warning restore CS0618 // Type or member is obsolete 141 | 142 | /// 143 | /// Tests the method. 144 | /// 145 | [Fact] 146 | public void Chain_AddTypeThatIsNotAMiddleware_ThrowsException() 147 | { 148 | var responsibilityChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()); 149 | Assert.Throws(() => 150 | { 151 | responsibilityChain.Chain(typeof(ResponsibilityChainTests)); 152 | }); 153 | } 154 | 155 | [Fact] 156 | public void Execute_ChainOfMiddlewareWithFinally_FinallyIsExecuted() 157 | { 158 | var responsibilityChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 159 | .Chain() 160 | .Chain(typeof(InvalidateDataExceptionHandler)) 161 | .Chain() 162 | .Finally(); 163 | 164 | // Creates an ArgumentNullException. The 'MyExceptionHandler' 165 | // middleware should be the last one to execute. 166 | var exception = new ArgumentNullException(); 167 | 168 | // The 'FinallyThrow' should throw 'InvalidOperationException'. 169 | Assert.Throws(() => responsibilityChain.Execute(exception)); 170 | } 171 | 172 | [Fact] 173 | public void Execute_EmptyChainOfMiddlewareWithFinally_FinallyIsExecuted() 174 | { 175 | var responsibilityChain = new ResponsibilityChain(new ActivatorMiddlewareResolver()) 176 | .Finally(); 177 | 178 | // Creates an ArgumentNullException. 179 | var exception = new ArgumentNullException(); 180 | 181 | // The 'FinallyThrow' should throw 'InvalidOperationException'. 182 | Assert.Throws(() => responsibilityChain.Execute(exception)); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/PipelineNet.Tests/PipelineNet.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | disable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/PipelineNet.Tests/Pipelines/AsyncPipelineTests.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Middleware; 2 | using PipelineNet.MiddlewareResolver; 3 | using PipelineNet.Pipelines; 4 | using System; 5 | using System.Text.RegularExpressions; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace PipelineNet.Tests.Pipelines 11 | { 12 | public class AsyncPipelineTests 13 | { 14 | #region Parameter definitions 15 | public enum Gender 16 | { 17 | Male, 18 | Female, 19 | Other 20 | } 21 | 22 | public class PersonModel 23 | { 24 | public int? Id { get; set; } 25 | public string Name { get; set; } 26 | public Gender? Gender { get; set; } 27 | 28 | public int Level { get; set; } 29 | } 30 | #endregion 31 | 32 | #region Middleware definitions 33 | public class PersonWithEvenId : IAsyncMiddleware 34 | { 35 | public async Task Run(PersonModel context, Func executeNext) 36 | { 37 | if (context.Id.HasValue && context.Id.Value % 2 == 0) 38 | context.Level = 1; 39 | await executeNext(context); 40 | } 41 | } 42 | 43 | public class PersonWithOddId : IAsyncMiddleware 44 | { 45 | public async Task Run(PersonModel context, Func executeNext) 46 | { 47 | if (context.Id.HasValue && context.Id.Value % 2 != 0) 48 | context.Level = 2; 49 | await executeNext(context); 50 | } 51 | } 52 | 53 | public class PersonWithEmailName : IAsyncMiddleware 54 | { 55 | public async Task Run(PersonModel context, Func executeNext) 56 | { 57 | if (!string.IsNullOrWhiteSpace(context.Name) && Regex.IsMatch(context.Name, @"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$")) 58 | context.Level = 3; 59 | await executeNext(context); 60 | } 61 | } 62 | 63 | public class PersonWithGenderProperty : IAsyncMiddleware 64 | { 65 | public async Task Run(PersonModel context, Func executeNext) 66 | { 67 | if (context.Gender.HasValue) 68 | context.Level = 4; 69 | 70 | Thread.Sleep(10); 71 | await executeNext(context); 72 | } 73 | } 74 | 75 | public class ThrowIfCancellationRequestedMiddleware : ICancellableAsyncMiddleware 76 | { 77 | public async Task Run(PersonModel context, Func executeNext, CancellationToken cancellationToken) 78 | { 79 | cancellationToken.ThrowIfCancellationRequested(); 80 | await executeNext(context); 81 | } 82 | } 83 | #endregion 84 | 85 | [Fact] 86 | public async Task Execute_RunSeveralMiddleware_SuccessfullyExecute() 87 | { 88 | var pipeline = new AsyncPipeline(new ActivatorMiddlewareResolver()) 89 | .Add() 90 | .Add() 91 | .Add() 92 | .Add(); 93 | 94 | // This person model has a name that matches the 'PersonWithEmailName' middleware. 95 | var personModel = new PersonModel 96 | { 97 | Name = "this_is_my_email@servername.js" 98 | }; 99 | 100 | await pipeline.Execute(personModel); 101 | 102 | // Check if the level of 'personModel' is 3, which is configured by 'PersonWithEmailName' middleware. 103 | Assert.Equal(3, personModel.Level); 104 | } 105 | 106 | [Fact] 107 | public async Task Execute_RunSamePipelineTwice_SuccessfullyExecute() 108 | { 109 | var pipeline = new AsyncPipeline(new ActivatorMiddlewareResolver()) 110 | .Add() 111 | .Add() 112 | .Add() 113 | .Add(); 114 | 115 | // This person model has a name that matches the 'PersonWithEmailName' middleware. 116 | var personModel = new PersonModel 117 | { 118 | Name = "this_is_my_email@servername.js" 119 | }; 120 | 121 | await pipeline.Execute(personModel); 122 | 123 | // Check if the level of 'personModel' is 3, which is configured by 'PersonWithEmailName' middleware. 124 | Assert.Equal(3, personModel.Level); 125 | 126 | // Creates a new instance with a 'Gender' property. The 'PersonWithGenderProperty' 127 | // middleware should be the last one to be executed. 128 | personModel = new PersonModel 129 | { 130 | Name = "this_is_my_email@servername.js", 131 | Gender = Gender.Other 132 | }; 133 | 134 | pipeline.Execute(personModel); 135 | 136 | // Check if the level of 'personModel' is 4, which is configured by 'PersonWithGenderProperty' middleware. 137 | Assert.Equal(4, personModel.Level); 138 | } 139 | 140 | [Fact] 141 | public async Task Execute_RunSeveralMiddlewareWithTwoBeingDynamiccalyAdded_SuccessfullyExecute() 142 | { 143 | var pipeline = new AsyncPipeline(new ActivatorMiddlewareResolver()) 144 | .Add() 145 | .Add(typeof(PersonWithOddId)) 146 | .Add() 147 | .Add(typeof(PersonWithGenderProperty)); 148 | 149 | // This person model has a gender, so the last middleware will be the one handling the input. 150 | var personModel = new PersonModel 151 | { 152 | Gender = Gender.Female 153 | }; 154 | 155 | await pipeline.Execute(personModel); 156 | 157 | // Check if the level of 'personModel' is 4, which is configured by 'PersonWithGenderProperty' middleware. 158 | Assert.Equal(4, personModel.Level); 159 | } 160 | 161 | /// 162 | /// Tests the method. 163 | /// 164 | [Fact] 165 | public void Add_AddTypeThatIsNotAMiddleware_ThrowsException() 166 | { 167 | var pipeline = new AsyncPipeline(new ActivatorMiddlewareResolver()); 168 | Assert.Throws(() => 169 | { 170 | pipeline.Add(typeof(AsyncPipelineTests)); 171 | }); 172 | } 173 | 174 | [Fact] 175 | public async Task Execute_RunPipelineWithCancellableMiddleware_CancellableMiddlewareIsExecuted() 176 | { 177 | var pipeline = new AsyncPipeline(new ActivatorMiddlewareResolver()) 178 | .Add() 179 | .Add() 180 | .Add() 181 | .Add() 182 | .AddCancellable(); 183 | 184 | // Create a new instance with a 'Gender' property. The 'ThrowIfCancellationRequestedMiddleware' 185 | // middleware should be the last one to execute. 186 | var personModel = new PersonModel 187 | { 188 | Name = "this_is_my_email@servername.js", 189 | Gender = Gender.Other 190 | }; 191 | 192 | // Create the cancellation token in the canceled state. 193 | var cancellationToken = new CancellationToken(canceled: true); 194 | 195 | // Check if 'ThrowIfCancellationRequestedMiddleware' threw 'OperationCanceledException'. 196 | await Assert.ThrowsAsync(() => pipeline.Execute(personModel, cancellationToken)); 197 | 198 | // Check if the level of 'personModel' is 4, which is configured by 'PersonWithGenderProperty' middleware. 199 | Assert.Equal(4, personModel.Level); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/PipelineNet.Tests/Pipelines/PipelineTests.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Middleware; 2 | using PipelineNet.MiddlewareResolver; 3 | using PipelineNet.Pipelines; 4 | using System; 5 | using System.Text.RegularExpressions; 6 | using Xunit; 7 | 8 | namespace PipelineNet.Tests.Pipelines 9 | { 10 | public class PipelineTests 11 | { 12 | #region Parameter definitions 13 | public enum Gender 14 | { 15 | Male, 16 | Female, 17 | Other 18 | } 19 | 20 | public class PersonModel 21 | { 22 | public int? Id { get; set; } 23 | public string Name { get; set; } 24 | public Gender? Gender { get; set; } 25 | 26 | public int Level { get; set; } 27 | } 28 | #endregion 29 | 30 | #region Middleware definitions 31 | public class PersonWithEvenId : IMiddleware 32 | { 33 | public void Run(PersonModel context, Action executeNext) 34 | { 35 | if (context.Id.HasValue && context.Id.Value % 2 == 0) 36 | context.Level = 1; 37 | executeNext(context); 38 | } 39 | } 40 | 41 | public class PersonWithOddId : IMiddleware 42 | { 43 | public void Run(PersonModel context, Action executeNext) 44 | { 45 | if (context.Id.HasValue && context.Id.Value % 2 != 0) 46 | context.Level = 2; 47 | executeNext(context); 48 | } 49 | } 50 | 51 | public class PersonWithEmailName : IMiddleware 52 | { 53 | public void Run(PersonModel context, Action executeNext) 54 | { 55 | if (!string.IsNullOrWhiteSpace(context.Name) && Regex.IsMatch(context.Name, @"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$")) 56 | context.Level = 3; 57 | executeNext(context); 58 | } 59 | } 60 | 61 | public class PersonWithGenderProperty : IMiddleware 62 | { 63 | public void Run(PersonModel context, Action executeNext) 64 | { 65 | if (context.Gender.HasValue) 66 | context.Level = 4; 67 | executeNext(context); 68 | } 69 | } 70 | #endregion 71 | 72 | [Fact] 73 | public void Execute_RunSeveralMiddleware_SuccessfullyExecute() 74 | { 75 | var pipeline = new Pipeline(new ActivatorMiddlewareResolver()) 76 | .Add() 77 | .Add() 78 | .Add() 79 | .Add(); 80 | 81 | // This person model has a name that matches the 'PersonWithEmailName' middleware. 82 | var personModel = new PersonModel 83 | { 84 | Name = "this_is_my_email@servername.js" 85 | }; 86 | 87 | pipeline.Execute(personModel); 88 | 89 | // Check if the level of 'personModel' is 3, which is configured by 'PersonWithEmailName' middleware. 90 | Assert.Equal(3, personModel.Level); 91 | } 92 | 93 | [Fact] 94 | public void Execute_RunSamePipelineTwice_SuccessfullyExecute() 95 | { 96 | var pipeline = new Pipeline(new ActivatorMiddlewareResolver()) 97 | .Add() 98 | .Add() 99 | .Add() 100 | .Add(); 101 | 102 | // This person model has a name that matches the 'PersonWithEmailName' middleware. 103 | var personModel = new PersonModel 104 | { 105 | Name = "this_is_my_email@servername.js" 106 | }; 107 | 108 | pipeline.Execute(personModel); 109 | 110 | // Check if the level of 'personModel' is 3, which is configured by 'PersonWithEmailName' middleware. 111 | Assert.Equal(3, personModel.Level); 112 | 113 | // Creates a new instance with a 'Gender' property. The 'PersonWithGenderProperty' 114 | // middleware should be the last one to be executed. 115 | personModel = new PersonModel 116 | { 117 | Name = "this_is_my_email@servername.js", 118 | Gender = Gender.Other 119 | }; 120 | 121 | pipeline.Execute(personModel); 122 | 123 | // Check if the level of 'personModel' is 4, which is configured by 'PersonWithGenderProperty' middleware. 124 | Assert.Equal(4, personModel.Level); 125 | } 126 | 127 | [Fact] 128 | public void Execute_RunSeveralMiddlewareWithTwoBeingDynamiccalyAdded_SuccessfullyExecute() 129 | { 130 | var pipeline = new Pipeline(new ActivatorMiddlewareResolver()) 131 | .Add() 132 | .Add(typeof(PersonWithOddId)) 133 | .Add() 134 | .Add(typeof(PersonWithGenderProperty)); 135 | 136 | // This person model has a gender, so the last middleware will be the one handling the input. 137 | var personModel = new PersonModel 138 | { 139 | Gender = Gender.Female 140 | }; 141 | 142 | pipeline.Execute(personModel); 143 | 144 | // Check if the level of 'personModel' is 4, which is configured by 'PersonWithGenderProperty' middleware. 145 | Assert.Equal(4, personModel.Level); 146 | } 147 | 148 | /// 149 | /// Tests the method. 150 | /// 151 | [Fact] 152 | public void Add_AddTypeThatIsNotAMiddleware_ThrowsException() 153 | { 154 | var pipeline = new Pipeline(new ActivatorMiddlewareResolver()); 155 | Assert.Throws(() => 156 | { 157 | pipeline.Add(typeof(PipelineTests)); 158 | }); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/PipelineNet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.34928.147 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PipelineNet", "PipelineNet\PipelineNet.csproj", "{1308EEF1-A460-4E1B-8ACC-3F913010D7F1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PipelineNet.Tests", "PipelineNet.Tests\PipelineNet.Tests.csproj", "{559B8284-C4C9-4281-8DDD-688AF4DABEEB}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PipelineNet.ServiceProvider", "PipelineNet.ServiceProvider\PipelineNet.ServiceProvider.csproj", "{95B65398-BF95-4BBD-86C7-E13957FB1A1E}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PipelineNet.ServiceProvider.Tests", "PipelineNet.ServiceProvider.Tests\PipelineNet.ServiceProvider.Tests.csproj", "{FB784AAC-F0C7-4D5D-9C7A-3CBA3DFD31B0}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {1308EEF1-A460-4E1B-8ACC-3F913010D7F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {1308EEF1-A460-4E1B-8ACC-3F913010D7F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {1308EEF1-A460-4E1B-8ACC-3F913010D7F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {1308EEF1-A460-4E1B-8ACC-3F913010D7F1}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {559B8284-C4C9-4281-8DDD-688AF4DABEEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {559B8284-C4C9-4281-8DDD-688AF4DABEEB}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {559B8284-C4C9-4281-8DDD-688AF4DABEEB}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {559B8284-C4C9-4281-8DDD-688AF4DABEEB}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {95B65398-BF95-4BBD-86C7-E13957FB1A1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {95B65398-BF95-4BBD-86C7-E13957FB1A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {95B65398-BF95-4BBD-86C7-E13957FB1A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {95B65398-BF95-4BBD-86C7-E13957FB1A1E}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {FB784AAC-F0C7-4D5D-9C7A-3CBA3DFD31B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {FB784AAC-F0C7-4D5D-9C7A-3CBA3DFD31B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {FB784AAC-F0C7-4D5D-9C7A-3CBA3DFD31B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {FB784AAC-F0C7-4D5D-9C7A-3CBA3DFD31B0}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {9AEAEE1B-7214-4265-8C6D-D62792D71E16} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /src/PipelineNet/AsyncBaseMiddlewareFlow.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.MiddlewareResolver; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | 7 | namespace PipelineNet 8 | { 9 | /// 10 | /// Defines the base class for asynchronous middleware flows. 11 | /// 12 | /// The middleware type. 13 | /// The cancellable middleware type. 14 | public abstract class AsyncBaseMiddlewareFlow 15 | { 16 | /// 17 | /// The list of middleware types. 18 | /// 19 | protected IList MiddlewareTypes { get; private set; } 20 | 21 | /// 22 | /// The resolver used to create the middleware types. 23 | /// 24 | protected IMiddlewareResolver MiddlewareResolver { get; private set; } 25 | 26 | internal AsyncBaseMiddlewareFlow(IMiddlewareResolver middlewareResolver) 27 | { 28 | MiddlewareResolver = middlewareResolver ?? throw new ArgumentNullException("middlewareResolver", 29 | "An instance of IMiddlewareResolver must be provided. You can use ActivatorMiddlewareResolver."); 30 | MiddlewareTypes = new List(); 31 | } 32 | 33 | /// 34 | /// Stores the of the middleware type. 35 | /// 36 | private static readonly TypeInfo MiddlewareTypeInfo = typeof(TMiddleware).GetTypeInfo(); 37 | 38 | /// 39 | /// Stores the of the cancellable middleware type. 40 | /// 41 | private static readonly TypeInfo CancellableMiddlewareTypeInfo = typeof(TCancellableMiddleware).GetTypeInfo(); 42 | 43 | /// 44 | /// Adds a new middleware type to the internal list of types. 45 | /// Middleware will be executed in the same order they are added. 46 | /// 47 | /// The middleware type to be executed. 48 | /// Thrown if the is 49 | /// not an implementation of or . 50 | /// Thrown if is null. 51 | protected void AddMiddleware(Type middlewareType) 52 | { 53 | if (middlewareType == null) throw new ArgumentNullException("middlewareType"); 54 | 55 | bool isAssignableFromMiddleware = MiddlewareTypeInfo.IsAssignableFrom(middlewareType.GetTypeInfo()) 56 | || CancellableMiddlewareTypeInfo.IsAssignableFrom(middlewareType.GetTypeInfo()); 57 | if (!isAssignableFromMiddleware) 58 | throw new ArgumentException( 59 | $"The middleware type must implement \"{typeof(TMiddleware)}\" or \"{typeof(TCancellableMiddleware)}\"."); 60 | 61 | this.MiddlewareTypes.Add(middlewareType); 62 | } 63 | 64 | internal void EnsureMiddlewareNotNull(MiddlewareResolverResult middlewareResolverResult, Type middlewareType) 65 | { 66 | if (middlewareResolverResult == null || middlewareResolverResult.Middleware == null) 67 | { 68 | throw new InvalidOperationException($"'{MiddlewareResolver.GetType()}' failed to resolve middleware of type '{middlewareType}'."); 69 | } 70 | } 71 | 72 | internal static async Task DisposeMiddlewareAsync(MiddlewareResolverResult middlewareResolverResult) 73 | { 74 | if (middlewareResolverResult != null && middlewareResolverResult.Dispose) 75 | { 76 | var middleware = middlewareResolverResult.Middleware; 77 | if (middleware != null) 78 | { 79 | #if NETSTANDARD2_1_OR_GREATER 80 | if (middleware is IAsyncDisposable asyncDisposable) 81 | { 82 | await asyncDisposable.DisposeAsync().ConfigureAwait(false); 83 | } 84 | else 85 | #endif 86 | if (middleware is IDisposable disposable) 87 | { 88 | disposable.Dispose(); 89 | } 90 | 91 | var completedTask = Task.FromResult(0); 92 | await completedTask.ConfigureAwait(false); 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/PipelineNet/BaseMiddlewareFlow.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.MiddlewareResolver; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | namespace PipelineNet 7 | { 8 | /// 9 | /// Defines the base class for middleware flows. 10 | /// 11 | /// The middleware type. 12 | public abstract class BaseMiddlewareFlow 13 | { 14 | /// 15 | /// The list of middleware types. 16 | /// 17 | protected IList MiddlewareTypes { get; private set; } 18 | 19 | /// 20 | /// The resolver used to create the middleware types. 21 | /// 22 | protected IMiddlewareResolver MiddlewareResolver { get; private set; } 23 | 24 | internal BaseMiddlewareFlow(IMiddlewareResolver middlewareResolver) 25 | { 26 | MiddlewareResolver = middlewareResolver ?? throw new ArgumentNullException("middlewareResolver", 27 | "An instance of IMiddlewareResolver must be provided. You can use ActivatorMiddlewareResolver."); 28 | MiddlewareTypes = new List(); 29 | } 30 | 31 | /// 32 | /// Stores the of the middleware type. 33 | /// 34 | private static readonly TypeInfo MiddlewareTypeInfo = typeof(TMiddleware).GetTypeInfo(); 35 | 36 | /// 37 | /// Adds a new middleware type to the internal list of types. 38 | /// Middleware will be executed in the same order they are added. 39 | /// 40 | /// The middleware type to be executed. 41 | /// Thrown if the is 42 | /// not an implementation of . 43 | /// Thrown if is null. 44 | protected void AddMiddleware(Type middlewareType) 45 | { 46 | if (middlewareType == null) throw new ArgumentNullException("middlewareType"); 47 | 48 | bool isAssignableFromMiddleware = MiddlewareTypeInfo.IsAssignableFrom(middlewareType.GetTypeInfo()); 49 | if (!isAssignableFromMiddleware) 50 | throw new ArgumentException( 51 | $"The middleware type must implement \"{typeof(TMiddleware)}\"."); 52 | 53 | this.MiddlewareTypes.Add(middlewareType); 54 | } 55 | 56 | internal void EnsureMiddlewareNotNull(MiddlewareResolverResult middlewareResolverResult, Type middlewareType) 57 | { 58 | if (middlewareResolverResult == null || middlewareResolverResult.Middleware == null) 59 | { 60 | throw new InvalidOperationException($"'{MiddlewareResolver.GetType()}' failed to resolve middleware of type '{middlewareType}'."); 61 | } 62 | } 63 | 64 | internal static void DisposeMiddleware(MiddlewareResolverResult middlewareResolverResult) 65 | { 66 | if (middlewareResolverResult != null && middlewareResolverResult.Dispose) 67 | { 68 | var middleware = middlewareResolverResult.Middleware; 69 | if (middleware != null) 70 | { 71 | if (middleware is IDisposable disposable) 72 | { 73 | disposable.Dispose(); 74 | } 75 | #if NETSTANDARD2_1_OR_GREATER 76 | else if (middleware is IAsyncDisposable) 77 | { 78 | throw new InvalidOperationException($"'{middleware.GetType()}' type only implements IAsyncDisposable." + 79 | " Use AsyncPipeline/AsyncResponsibilityChain to execute the configured pipeline/chain fo responsibility."); 80 | } 81 | #endif 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/PipelineNet/ChainsOfResponsibility/AsyncResponsibilityChain.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Finally; 2 | using PipelineNet.Middleware; 3 | using PipelineNet.MiddlewareResolver; 4 | using System; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace PipelineNet.ChainsOfResponsibility 10 | { 11 | /// 12 | /// Defines the asynchronous chain of responsibility. 13 | /// 14 | /// The input type for the chain. 15 | /// The return type of the chain. 16 | public class AsyncResponsibilityChain : AsyncBaseMiddlewareFlow, ICancellableAsyncMiddleware>, 17 | IAsyncResponsibilityChain 18 | { 19 | /// 20 | /// Stores the of the finally type. 21 | /// 22 | private static readonly TypeInfo FinallyTypeInfo = typeof(IAsyncFinally).GetTypeInfo(); 23 | 24 | /// 25 | /// Stores the of the cancellable finally type. 26 | /// 27 | private static readonly TypeInfo CancellableFinallyTypeInfo = typeof(ICancellableAsyncFinally).GetTypeInfo(); 28 | 29 | /// 30 | /// Stores the shared instance of . 31 | /// 32 | private static readonly IAsyncFinally DefaultFinallyInstance = new DefaultFinally(); 33 | 34 | private Type _finallyType; 35 | private Func> _finallyFunc; 36 | 37 | /// 38 | /// Creates a new asynchronous chain of responsibility. 39 | /// 40 | /// The resolver used to create the middleware types. 41 | public AsyncResponsibilityChain(IMiddlewareResolver middlewareResolver) : base(middlewareResolver) 42 | { 43 | } 44 | 45 | /// 46 | /// Chains a new middleware to the chain of responsibility. 47 | /// Middleware will be executed in the same order they are added. 48 | /// 49 | /// The new middleware being added. 50 | /// The current instance of . 51 | public IAsyncResponsibilityChain Chain() where TMiddleware : IAsyncMiddleware 52 | { 53 | MiddlewareTypes.Add(typeof(TMiddleware)); 54 | return this; 55 | } 56 | 57 | /// 58 | /// Chains a new cancellable middleware to the chain of responsibility. 59 | /// Middleware will be executed in the same order they are added. 60 | /// 61 | /// The new middleware being added. 62 | /// The current instance of . 63 | public IAsyncResponsibilityChain ChainCancellable() where TCancellableMiddleware : ICancellableAsyncMiddleware 64 | { 65 | MiddlewareTypes.Add(typeof(TCancellableMiddleware)); 66 | return this; 67 | } 68 | 69 | /// 70 | /// Chains a new middleware type to the chain of responsibility. 71 | /// Middleware will be executed in the same order they are added. 72 | /// 73 | /// The middleware type to be executed. 74 | /// Thrown if the is 75 | /// not an implementation of or . 76 | /// Thrown if is null. 77 | /// The current instance of . 78 | public IAsyncResponsibilityChain Chain(Type middlewareType) 79 | { 80 | base.AddMiddleware(middlewareType); 81 | return this; 82 | } 83 | 84 | /// 85 | /// Executes the configured chain of responsibility. 86 | /// 87 | /// 88 | public async Task Execute(TParameter parameter) => 89 | await Execute(parameter, default).ConfigureAwait(false); 90 | 91 | /// 92 | /// Executes the configured chain of responsibility. 93 | /// 94 | /// 95 | /// The cancellation token that will be passed to all middleware. 96 | public async Task Execute(TParameter parameter, CancellationToken cancellationToken) 97 | { 98 | if (MiddlewareTypes.Count == 0) 99 | { 100 | MiddlewareResolverResult finallyResolverResult = null; 101 | try 102 | { 103 | if (_finallyType != null) 104 | { 105 | finallyResolverResult = MiddlewareResolver.Resolve(_finallyType); 106 | EnsureMiddlewareNotNull(finallyResolverResult, _finallyType); 107 | return await RunFinallyAsync(finallyResolverResult, parameter, cancellationToken).ConfigureAwait(false); 108 | } 109 | else if (_finallyFunc != null) 110 | { 111 | return await _finallyFunc(parameter).ConfigureAwait(false); 112 | } 113 | else 114 | { 115 | return await DefaultFinallyInstance.Finally(parameter).ConfigureAwait(false); 116 | } 117 | } 118 | finally 119 | { 120 | await DisposeMiddlewareAsync(finallyResolverResult).ConfigureAwait(false); 121 | } 122 | } 123 | 124 | int index = 0; 125 | Func> next = null; 126 | next = async (parameter2) => 127 | { 128 | MiddlewareResolverResult middlewareResolverResult = null; 129 | MiddlewareResolverResult finallyResolverResult = null; 130 | try 131 | { 132 | var middlewaretype = MiddlewareTypes[index]; 133 | middlewareResolverResult = MiddlewareResolver.Resolve(middlewaretype); 134 | 135 | index++; 136 | // If the current instance of middleware is the last one in the list, 137 | // the "next" function is assigned to the finally function or a 138 | // default empty function. 139 | if (index == MiddlewareTypes.Count) 140 | { 141 | if (_finallyType != null) 142 | { 143 | finallyResolverResult = MiddlewareResolver.Resolve(_finallyType); 144 | EnsureMiddlewareNotNull(finallyResolverResult, _finallyType); 145 | next = async (p) => await RunFinallyAsync(finallyResolverResult, p, cancellationToken).ConfigureAwait(false); 146 | } 147 | else if (_finallyFunc != null) 148 | { 149 | next = _finallyFunc; 150 | } 151 | else 152 | { 153 | next = async (p) => await DefaultFinallyInstance.Finally(p).ConfigureAwait(false); 154 | } 155 | } 156 | 157 | EnsureMiddlewareNotNull(middlewareResolverResult, middlewaretype); 158 | return await RunMiddlewareAsync(middlewareResolverResult, parameter2, next, cancellationToken).ConfigureAwait(false); 159 | } 160 | finally 161 | { 162 | await DisposeMiddlewareAsync(middlewareResolverResult).ConfigureAwait(false); 163 | await DisposeMiddlewareAsync(finallyResolverResult).ConfigureAwait(false); 164 | } 165 | }; 166 | 167 | return await next(parameter).ConfigureAwait(false); 168 | } 169 | 170 | private static async Task RunMiddlewareAsync( 171 | MiddlewareResolverResult middlewareResolverResult, 172 | TParameter parameter, 173 | Func> next, 174 | CancellationToken cancellationToken) 175 | { 176 | if (middlewareResolverResult.Middleware is ICancellableAsyncMiddleware cancellableMiddleware) 177 | { 178 | return await cancellableMiddleware.Run(parameter, next, cancellationToken).ConfigureAwait(false); 179 | } 180 | else 181 | { 182 | var middleware = (IAsyncMiddleware)middlewareResolverResult.Middleware; 183 | return await middleware.Run(parameter, next).ConfigureAwait(false); 184 | } 185 | } 186 | 187 | private static async Task RunFinallyAsync( 188 | MiddlewareResolverResult finallyResolverResult, 189 | TParameter parameter, 190 | CancellationToken cancellationToken) 191 | { 192 | if (finallyResolverResult.Middleware is ICancellableAsyncFinally cancellableFinally) 193 | { 194 | return await cancellableFinally.Finally(parameter, cancellationToken).ConfigureAwait(false); 195 | } 196 | else 197 | { 198 | var @finally = (IAsyncFinally)finallyResolverResult.Middleware; 199 | return await @finally.Finally(parameter).ConfigureAwait(false); 200 | } 201 | } 202 | 203 | /// 204 | /// Sets the finally to be executed at the end of the chain as a fallback. 205 | /// A chain can only have one finally type. Calling this method more 206 | /// a second time will just replace the existing finally type. 207 | /// 208 | /// The finally being set. 209 | /// The current instance of . 210 | public IAsyncResponsibilityChain Finally() 211 | where TFinally : IAsyncFinally 212 | { 213 | _finallyType = typeof(TFinally); 214 | return this; 215 | } 216 | 217 | /// 218 | /// Sets the cancellable finally to be executed at the end of the chain as a fallback. 219 | /// A chain can only have one finally type. Calling this method more 220 | /// a second time will just replace the existing finally type. 221 | /// 222 | /// The cancellable finally being set. 223 | /// The current instance of . 224 | public IAsyncResponsibilityChain CancellableFinally() 225 | where TCancellableFinally : ICancellableAsyncFinally 226 | { 227 | _finallyType = typeof(TCancellableFinally); 228 | return this; 229 | } 230 | 231 | /// 232 | /// Sets the finally to be executed at the end of the chain as a fallback. 233 | /// A chain can only have one finally type. Calling this method more 234 | /// a second time will just replace the existing finally type. 235 | /// 236 | /// The or that will be execute at the end of chain. 237 | /// The current instance of . 238 | public IAsyncResponsibilityChain Finally(Type finallyType) 239 | { 240 | if (finallyType == null) throw new ArgumentNullException("finallyType"); 241 | 242 | bool isAssignableFromFinally = FinallyTypeInfo.IsAssignableFrom(finallyType.GetTypeInfo()) 243 | || CancellableFinallyTypeInfo.IsAssignableFrom(finallyType.GetTypeInfo()); 244 | if (!isAssignableFromFinally) 245 | throw new ArgumentException( 246 | $"The finally type must implement \"{typeof(IAsyncFinally)}\" or \"{typeof(ICancellableAsyncFinally)}\"."); 247 | 248 | _finallyType = finallyType; 249 | return this; 250 | } 251 | 252 | /// 253 | /// Sets the function to be executed at the end of the chain as a fallback. 254 | /// A chain can only have one finally function. Calling this method more 255 | /// a second time will just replace the existing finally . 256 | /// 257 | /// The function that will be execute at the end of chain. 258 | /// The current instance of . 259 | [Obsolete("This overload is obsolete. Use Finally or CancellableFinally.")] 260 | public IAsyncResponsibilityChain Finally(Func> finallyFunc) 261 | { 262 | this._finallyFunc = finallyFunc; 263 | return this; 264 | } 265 | 266 | private class DefaultFinally : IAsyncFinally 267 | { 268 | public async Task Finally(TParameter parameter) => 269 | await Task.FromResult(default(TReturn)).ConfigureAwait(false); 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/PipelineNet/ChainsOfResponsibility/IAsyncResponsibilityChain.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Finally; 2 | using PipelineNet.Middleware; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace PipelineNet.ChainsOfResponsibility 8 | { 9 | /// 10 | /// Defines the asynchronous chain of responsibility. 11 | /// 12 | /// The input type for the chain. 13 | /// The return type of the chain. 14 | public interface IAsyncResponsibilityChain 15 | { 16 | /// 17 | /// Sets the finally to be executed at the end of the chain as a fallback. 18 | /// A chain can only have one finally type. Calling this method more 19 | /// a second time will just replace the existing finally type. 20 | /// 21 | /// The finally being set. 22 | /// The current instance of . 23 | IAsyncResponsibilityChain Finally() 24 | where TFinally : IAsyncFinally; 25 | 26 | /// 27 | /// Sets the cancellable finally to be executed at the end of the chain as a fallback. 28 | /// A chain can only have one finally type. Calling this method more 29 | /// a second time will just replace the existing finally type. 30 | /// 31 | /// The cancellable finally being set. 32 | /// The current instance of . 33 | IAsyncResponsibilityChain CancellableFinally() 34 | where TCancellableFinally : ICancellableAsyncFinally; 35 | 36 | /// 37 | /// Sets the finally to be executed at the end of the chain as a fallback. 38 | /// A chain can only have one finally type. Calling this method more 39 | /// a second time will just replace the existing finally type. 40 | /// 41 | /// The or that will be execute at the end of chain. 42 | /// The current instance of . 43 | IAsyncResponsibilityChain Finally(Type finallyType); 44 | 45 | /// 46 | /// Sets the function to be executed at the end of the chain as a fallback. 47 | /// A chain can only have one finally function. Calling this method more 48 | /// a second time will just replace the existing finally . 49 | /// 50 | /// The function that will be execute at the end of chain. 51 | /// The current instance of . 52 | [Obsolete("This overload is obsolete. Use Finally or CancellableFinally.")] 53 | IAsyncResponsibilityChain Finally(Func> finallyFunc); 54 | 55 | /// 56 | /// Chains a new middleware to the chain of responsibility. 57 | /// Middleware will be executed in the same order they are added. 58 | /// 59 | /// The new middleware being added. 60 | /// The current instance of . 61 | IAsyncResponsibilityChain Chain() 62 | where TMiddleware : IAsyncMiddleware; 63 | 64 | /// 65 | /// Chains a new cancellable middleware to the chain of responsibility. 66 | /// Middleware will be executed in the same order they are added. 67 | /// 68 | /// The new cancellable middleware being added. 69 | /// The current instance of . 70 | IAsyncResponsibilityChain ChainCancellable() 71 | where TCancellableMiddleware : ICancellableAsyncMiddleware; 72 | 73 | /// 74 | /// Chains a new middleware type to the chain of responsibility. 75 | /// Middleware will be executed in the same order they are added. 76 | /// 77 | /// The middleware type to be executed. 78 | /// Thrown if the is 79 | /// not an implementation of or . 80 | /// Thrown if is null. 81 | /// The current instance of . 82 | IAsyncResponsibilityChain Chain(Type middlewareType); 83 | 84 | /// 85 | /// Executes the configured chain of responsibility. 86 | /// 87 | /// 88 | Task Execute(TParameter parameter); 89 | 90 | /// 91 | /// Executes the configured chain of responsibility. 92 | /// 93 | /// 94 | /// The cancellation token that will be passed to all middleware. 95 | Task Execute(TParameter parameter, CancellationToken cancellationToken); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/PipelineNet/ChainsOfResponsibility/IResponsibilityChain.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Finally; 2 | using PipelineNet.Middleware; 3 | using System; 4 | 5 | namespace PipelineNet.ChainsOfResponsibility 6 | { 7 | /// 8 | /// Defines the chain of responsibility. 9 | /// 10 | /// The input type for the chain. 11 | /// The return type of the chain. 12 | public interface IResponsibilityChain 13 | { 14 | /// 15 | /// Sets the finally to be executed at the end of the chain as a fallback. 16 | /// A chain can only have one finally type. Calling this method more 17 | /// a second time will just replace the existing finally type. 18 | /// 19 | /// The finally being set. 20 | /// The current instance of . 21 | IResponsibilityChain Finally() 22 | where TFinally : IFinally; 23 | 24 | /// 25 | /// Sets the finally to be executed at the end of the chain as a fallback. 26 | /// A chain can only have one finally type. Calling this method more 27 | /// a second time will just replace the existing finally type. 28 | /// 29 | /// The that will be execute at the end of chain. 30 | /// The current instance of . 31 | IResponsibilityChain Finally(Type finallyType); 32 | 33 | /// 34 | /// Sets the function to be executed at the end of the chain as a fallback. 35 | /// A chain can only have one finally function. Calling this method more 36 | /// a second time will just replace the existing finally . 37 | /// 38 | /// The that will be execute at the end of chain. 39 | /// The current instance of . 40 | [Obsolete("This overload is obsolete. Use Finally.")] 41 | IResponsibilityChain Finally(Func finallyFunc); 42 | 43 | /// 44 | /// Chains a new middleware to the chain of responsibility. 45 | /// Middleware will be executed in the same order they are added. 46 | /// 47 | /// The new middleware being added. 48 | /// The current instance of . 49 | IResponsibilityChain Chain() 50 | where TMiddleware : IMiddleware; 51 | 52 | /// 53 | /// Chains a new middleware type to the chain of responsibility. 54 | /// Middleware will be executed in the same order they are added. 55 | /// 56 | /// The middleware type to be executed. 57 | /// Thrown if the is 58 | /// not an implementation of . 59 | /// Thrown if is null. 60 | /// The current instance of . 61 | IResponsibilityChain Chain(Type middlewareType); 62 | 63 | /// 64 | /// Executes the configured chain of responsibility. 65 | /// 66 | /// 67 | TReturn Execute(TParameter parameter); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/PipelineNet/ChainsOfResponsibility/ResponsibilityChain.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Finally; 2 | using PipelineNet.Middleware; 3 | using PipelineNet.MiddlewareResolver; 4 | using System; 5 | using System.Reflection; 6 | 7 | namespace PipelineNet.ChainsOfResponsibility 8 | { 9 | /// 10 | /// Defines the chain of responsibility. 11 | /// 12 | /// The input type for the chain. 13 | /// The return type of the chain. 14 | public class ResponsibilityChain : BaseMiddlewareFlow>, 15 | IResponsibilityChain 16 | { 17 | /// 18 | /// Stores the of the finally type. 19 | /// 20 | private static readonly TypeInfo FinallyTypeInfo = typeof(IFinally).GetTypeInfo(); 21 | 22 | /// 23 | /// Stores the shared instance of . 24 | /// 25 | private static readonly IFinally DefaultFinallyInstance = new DefaultFinally(); 26 | 27 | private Type _finallyType; 28 | private Func _finallyFunc; 29 | 30 | /// 31 | /// Creates a new chain of responsibility. 32 | /// 33 | /// 34 | public ResponsibilityChain(IMiddlewareResolver middlewareResolver) : base(middlewareResolver) 35 | { 36 | } 37 | 38 | /// 39 | /// Sets the finally to be executed at the end of the chain as a fallback. 40 | /// A chain can only have one finally type. Calling this method more 41 | /// a second time will just replace the existing finally type. 42 | /// 43 | /// The finally being set. 44 | /// The current instance of . 45 | public IResponsibilityChain Finally() 46 | where TFinally : IFinally 47 | { 48 | _finallyType = typeof(TFinally); 49 | return this; 50 | } 51 | 52 | /// 53 | /// Sets the finally to be executed at the end of the chain as a fallback. 54 | /// A chain can only have one finally type. Calling this method more 55 | /// a second time will just replace the existing finally type. 56 | /// 57 | /// The that will be execute at the end of chain. 58 | /// The current instance of . 59 | public IResponsibilityChain Finally(Type finallyType) 60 | { 61 | if (finallyType == null) throw new ArgumentNullException("finallyType"); 62 | 63 | bool isAssignableFromFinally = FinallyTypeInfo.IsAssignableFrom(finallyType.GetTypeInfo()); 64 | if (!isAssignableFromFinally) 65 | throw new ArgumentException( 66 | $"The finally type must implement \"{typeof(IFinally)}\"."); 67 | 68 | _finallyType = finallyType; 69 | return this; 70 | } 71 | 72 | /// 73 | /// Sets the function to be executed at the end of the chain as a fallback. 74 | /// A chain can only have one finally function. Calling this method more 75 | /// a second time will just replace the existing finally . 76 | /// 77 | /// The that will be execute at the end of chain. 78 | /// The current instance of . 79 | [Obsolete("This overload is obsolete. Use Finally.")] 80 | public IResponsibilityChain Finally(Func finallyFunc) 81 | { 82 | this._finallyFunc = finallyFunc; 83 | return this; 84 | } 85 | 86 | /// 87 | /// Chains a new middleware to the chain of responsibility. 88 | /// Middleware will be executed in the same order they are added. 89 | /// 90 | /// The new middleware being added. 91 | /// The current instance of . 92 | public IResponsibilityChain Chain() 93 | where TMiddleware : IMiddleware 94 | { 95 | MiddlewareTypes.Add(typeof(TMiddleware)); 96 | return this; 97 | } 98 | 99 | /// 100 | /// Chains a new middleware type to the chain of responsibility. 101 | /// Middleware will be executed in the same order they are added. 102 | /// 103 | /// The middleware type to be executed. 104 | /// Thrown if the is 105 | /// not an implementation of . 106 | /// Thrown if is null. 107 | /// The current instance of . 108 | public IResponsibilityChain Chain(Type middlewareType) 109 | { 110 | base.AddMiddleware(middlewareType); 111 | return this; 112 | } 113 | 114 | /// 115 | /// Execute the configured chain of responsibility. 116 | /// 117 | /// 118 | public TReturn Execute(TParameter parameter) 119 | { 120 | if (MiddlewareTypes.Count == 0) 121 | { 122 | MiddlewareResolverResult finallyResolverResult = null; 123 | try 124 | { 125 | if (_finallyType != null) 126 | { 127 | finallyResolverResult = MiddlewareResolver.Resolve(_finallyType); 128 | EnsureMiddlewareNotNull(finallyResolverResult, _finallyType); 129 | return RunFinally(finallyResolverResult, parameter); 130 | } 131 | else if (_finallyFunc != null) 132 | { 133 | return _finallyFunc(parameter); 134 | } 135 | else 136 | { 137 | return DefaultFinallyInstance.Finally(parameter); 138 | } 139 | } 140 | finally 141 | { 142 | DisposeMiddleware(finallyResolverResult); 143 | } 144 | } 145 | 146 | int index = 0; 147 | Func next = null; 148 | next = (parameter2) => 149 | { 150 | MiddlewareResolverResult middlewareResolverResult = null; 151 | MiddlewareResolverResult finallyResolverResult = null; 152 | try 153 | { 154 | var middlewaretype = MiddlewareTypes[index]; 155 | middlewareResolverResult = MiddlewareResolver.Resolve(middlewaretype); 156 | 157 | index++; 158 | // If the current instance of middleware is the last one in the list, 159 | // the "next" function is assigned to the finally function or a 160 | // default empty function. 161 | if (index == MiddlewareTypes.Count) 162 | { 163 | if (_finallyType != null) 164 | { 165 | finallyResolverResult = MiddlewareResolver.Resolve(_finallyType); 166 | EnsureMiddlewareNotNull(finallyResolverResult, _finallyType); 167 | next = (p) => RunFinally(finallyResolverResult, p); 168 | } 169 | else if (_finallyFunc != null) 170 | { 171 | next = _finallyFunc; 172 | } 173 | else 174 | { 175 | next = (p) => DefaultFinallyInstance.Finally(p); 176 | } 177 | } 178 | 179 | EnsureMiddlewareNotNull(middlewareResolverResult, middlewaretype); 180 | return RunMiddleware(middlewareResolverResult, parameter2, next); 181 | } 182 | finally 183 | { 184 | DisposeMiddleware(middlewareResolverResult); 185 | DisposeMiddleware(finallyResolverResult); 186 | } 187 | }; 188 | 189 | return next(parameter); 190 | } 191 | 192 | private static TReturn RunMiddleware(MiddlewareResolverResult middlewareResolverResult, TParameter parameter, Func next) 193 | { 194 | var middleware = (IMiddleware)middlewareResolverResult.Middleware; 195 | return middleware.Run(parameter, next); 196 | } 197 | 198 | private static TReturn RunFinally(MiddlewareResolverResult finallyResolverResult, TParameter parameter) 199 | { 200 | var @finally = (IFinally)finallyResolverResult.Middleware; 201 | return @finally.Finally(parameter); 202 | } 203 | 204 | private class DefaultFinally : IFinally 205 | { 206 | public TReturn Finally(TParameter parameter) => 207 | default(TReturn); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/PipelineNet/Finally/IAsyncFinally.WithReturn.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace PipelineNet.Finally 4 | { 5 | /// 6 | /// Defines the asynchronous chain of responsibility finally. 7 | /// Finally will be executed at the end of the chain as a fallback. 8 | /// 9 | /// The input type for the finally. 10 | /// The return type of the finally. 11 | public interface IAsyncFinally 12 | { 13 | /// 14 | /// Executes the finally. 15 | /// 16 | /// The input parameter. 17 | /// The return value. 18 | Task Finally(TParameter parameter); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/PipelineNet/Finally/ICancellableAsyncFinally.WithReturn.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace PipelineNet.Finally 5 | { 6 | /// 7 | /// Defines the asynchronous chain of responsibility finally with cancellation token. 8 | /// Finally will be executed at the end of the chain as a fallback. 9 | /// 10 | /// The input type for the finally. 11 | /// The return type of the finally. 12 | public interface ICancellableAsyncFinally 13 | { 14 | /// 15 | /// Executes the finally. 16 | /// 17 | /// The input parameter. 18 | /// The cancellation token. 19 | /// The return value. 20 | Task Finally(TParameter parameter, CancellationToken cancellationToken); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/PipelineNet/Finally/IFinally.WithReturn.cs: -------------------------------------------------------------------------------- 1 | namespace PipelineNet.Finally 2 | { 3 | /// 4 | /// Defines the chain of responsibility finally. 5 | /// Finally will be executed at the end of the chain as a fallback. 6 | /// 7 | /// The input type for the finally. 8 | /// The return type of the finally. 9 | public interface IFinally 10 | { 11 | /// 12 | /// Executes the finally. 13 | /// 14 | /// The input parameter. 15 | TReturn Finally(TParameter parameter); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/PipelineNet/Middleware/IAsyncMiddleware.WithReturn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace PipelineNet.Middleware 5 | { 6 | /// 7 | /// Defines the asynchronous chain of responsibility middleware. 8 | /// 9 | /// The input type for the middleware. 10 | /// The return type of the middleware. 11 | public interface IAsyncMiddleware 12 | { 13 | /// 14 | /// Runs the middleware. 15 | /// 16 | /// The input parameter. 17 | /// The next middleware in the flow. 18 | /// The return value. 19 | Task Run(TParameter parameter, Func> next); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/PipelineNet/Middleware/IAsyncMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace PipelineNet.Middleware 5 | { 6 | /// 7 | /// Defines the asynchronous pipeline middleware. 8 | /// 9 | /// The type that will be the input for the middleware. 10 | public interface IAsyncMiddleware 11 | { 12 | /// 13 | /// Runs the middleware. 14 | /// 15 | /// The input parameter. 16 | /// The next middleware in the flow. 17 | Task Run(TParameter parameter, Func next); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/PipelineNet/Middleware/ICancellableAsyncMiddleware.WithReturn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace PipelineNet.Middleware 6 | { 7 | /// 8 | /// Defines the asynchronous chain of responsibility middleware with cancellation token. 9 | /// 10 | /// The input type for the middleware. 11 | /// The return type of the middleware. 12 | public interface ICancellableAsyncMiddleware 13 | { 14 | /// 15 | /// Runs the middleware. 16 | /// 17 | /// The input parameter. 18 | /// The next middleware in the flow. 19 | /// The cancellation token. 20 | /// The return value. 21 | Task Run(TParameter parameter, Func> next, CancellationToken cancellationToken); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/PipelineNet/Middleware/ICancellableAsyncMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace PipelineNet.Middleware 6 | { 7 | /// 8 | /// Defines the asynchronous pipeline middleware with cancellation token. 9 | /// 10 | /// The type that will be the input for the middleware. 11 | public interface ICancellableAsyncMiddleware 12 | { 13 | /// 14 | /// Runs the middleware. 15 | /// 16 | /// The input parameter. 17 | /// The next middleware in the flow. 18 | /// The cancellation token. 19 | /// The return value. 20 | Task Run(TParameter parameter, Func next, CancellationToken cancellationToken); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/PipelineNet/Middleware/IMiddleware.WithReturn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PipelineNet.Middleware 4 | { 5 | /// 6 | /// Defines the chain of responsibility middleware. 7 | /// 8 | /// The input type for the middleware. 9 | /// The return type of the middleware. 10 | public interface IMiddleware 11 | { 12 | /// 13 | /// Runs the middleware. 14 | /// 15 | /// The input parameter. 16 | /// The next middleware in the flow. 17 | /// The return value. 18 | TReturn Run(TParameter parameter, Func next); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/PipelineNet/Middleware/IMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PipelineNet.Middleware 4 | { 5 | /// 6 | /// Defines the pipeline middleware. 7 | /// 8 | /// The type that will be the input for the middleware. 9 | public interface IMiddleware 10 | { 11 | /// 12 | /// Runs the middleware. 13 | /// 14 | /// The input parameter. 15 | /// The next middleware in the flow. 16 | void Run(TParameter parameter, Action next); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/PipelineNet/MiddlewareResolver/ActivatorMiddlewareResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PipelineNet.MiddlewareResolver 4 | { 5 | /// 6 | /// A default implementation of that creates 7 | /// instances using the . 8 | /// 9 | public class ActivatorMiddlewareResolver : IMiddlewareResolver 10 | { 11 | /// 12 | public MiddlewareResolverResult Resolve(Type type) 13 | { 14 | var middleware = Activator.CreateInstance(type); 15 | bool dispose = true; 16 | 17 | return new MiddlewareResolverResult() 18 | { 19 | Middleware = middleware, 20 | Dispose = dispose 21 | }; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/PipelineNet/MiddlewareResolver/IMiddlewareResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PipelineNet.MiddlewareResolver 4 | { 5 | /// 6 | /// Used to create instances of middleware. 7 | /// You can implement this interface for your preferred dependency injection container. 8 | /// 9 | public interface IMiddlewareResolver 10 | { 11 | /// 12 | /// Creates an instance of the give middleware type. 13 | /// 14 | /// The middleware type that will be created. 15 | /// An instance of the middleware. 16 | MiddlewareResolverResult Resolve(Type type); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/PipelineNet/MiddlewareResolver/MiddlewareResolverResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PipelineNet.MiddlewareResolver 4 | { 5 | /// 6 | /// Contains the result of . 7 | /// 8 | public class MiddlewareResolverResult 9 | { 10 | /// 11 | /// The instance of the middleware. 12 | /// 13 | public object Middleware { get; set; } 14 | 15 | /// 16 | /// Gets or sets the value indicating whether the middleware should be disposed. 17 | /// Set this to if the middleware is IDisposable or 18 | /// IAsyncDisposable (requires .NET Standard 2.1 or greater) 19 | /// and is not disposed by a dependency injection container. 20 | /// 21 | [Obsolete("This property is obsolete. Use Dispose.")] 22 | public bool IsDisposable 23 | { 24 | get => Dispose; 25 | set => Dispose = value; 26 | } 27 | 28 | /// 29 | /// Gets or sets the value indicating whether the middleware should be disposed. 30 | /// Set this to if the middleware is not disposed 31 | /// by a dependency injection container. 32 | /// 33 | public bool Dispose { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/PipelineNet/PipelineNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.1;netstandard2.0;netstandard2.1 5 | 6 | 7 | 8 | 1.0.0 9 | PipelineNet 10 | PipelineNet 11 | $(Version) 12 | Israel Valverde 13 | A micro framework that helps you implement pipeline and chain of responsibility patterns. 14 | https://github.com/ipvalverde/PipelineNet 15 | MIT 16 | Copyright © Israel Valverde 17 | Pipeline .NetCore Portable Chain Responsibility ChainOfResponsibility Core NetStandard 18 | README.md 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/PipelineNet/Pipelines/AsyncPipeline.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Middleware; 2 | using PipelineNet.MiddlewareResolver; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace PipelineNet.Pipelines 8 | { 9 | /// 10 | /// An asynchronous pipeline stores middleware that are executed when is called. 11 | /// The middleware are executed in the same order they are added. 12 | /// 13 | /// The type that will be the input for all the middleware. 14 | public class AsyncPipeline : AsyncBaseMiddlewareFlow, ICancellableAsyncMiddleware>, IAsyncPipeline 15 | { 16 | /// 17 | /// Creates a new instance of asynchronous Pipeline. 18 | /// 19 | /// Resolver responsible for resolving instances out of middleware types. 20 | public AsyncPipeline(IMiddlewareResolver middlewareResolver) : base(middlewareResolver) 21 | { } 22 | 23 | /// 24 | /// Adds a middleware type to be executed. 25 | /// 26 | /// 27 | /// 28 | public IAsyncPipeline Add() 29 | where TMiddleware : IAsyncMiddleware 30 | { 31 | MiddlewareTypes.Add(typeof(TMiddleware)); 32 | return this; 33 | } 34 | 35 | /// 36 | /// Adds a cancellable middleware type to be executed. 37 | /// 38 | /// 39 | /// 40 | public IAsyncPipeline AddCancellable() 41 | where TCancellableMiddleware : ICancellableAsyncMiddleware 42 | { 43 | MiddlewareTypes.Add(typeof(TCancellableMiddleware)); 44 | return this; 45 | } 46 | 47 | /// 48 | /// Adds a middleware type to be executed. 49 | /// 50 | /// The middleware type to be executed. 51 | /// 52 | /// Thrown if the is 53 | /// not an implementation of or . 54 | /// Thrown if is null. 55 | public IAsyncPipeline Add(Type middlewareType) 56 | { 57 | base.AddMiddleware(middlewareType); 58 | return this; 59 | } 60 | 61 | /// 62 | /// Execute the configured pipeline. 63 | /// 64 | /// 65 | public async Task Execute(TParameter parameter) => 66 | await Execute(parameter, default).ConfigureAwait(false); 67 | 68 | /// 69 | /// Execute the configured pipeline. 70 | /// 71 | /// 72 | /// The cancellation token that will be passed to all middleware. 73 | public async Task Execute(TParameter parameter, CancellationToken cancellationToken) 74 | { 75 | if (MiddlewareTypes.Count == 0) 76 | return; 77 | 78 | int index = 0; 79 | Func next = null; 80 | next = async (parameter2) => 81 | { 82 | MiddlewareResolverResult middlewareResolverResult = null; 83 | try 84 | { 85 | var middlewareType = MiddlewareTypes[index]; 86 | middlewareResolverResult = MiddlewareResolver.Resolve(middlewareType); 87 | 88 | index++; 89 | if (index == MiddlewareTypes.Count) 90 | { 91 | var completedTask = Task.FromResult(0); 92 | next = async (p) => await completedTask.ConfigureAwait(false); 93 | } 94 | 95 | EnsureMiddlewareNotNull(middlewareResolverResult, middlewareType); 96 | await RunMiddlewareAsync(middlewareResolverResult, parameter2, next, cancellationToken).ConfigureAwait(false); 97 | } 98 | finally 99 | { 100 | await DisposeMiddlewareAsync(middlewareResolverResult).ConfigureAwait(false); 101 | } 102 | }; 103 | 104 | await next(parameter).ConfigureAwait(false); 105 | } 106 | 107 | private static async Task RunMiddlewareAsync( 108 | MiddlewareResolverResult resolverResult, 109 | TParameter parameter, 110 | Func next, 111 | CancellationToken cancellationToken) 112 | { 113 | if (resolverResult.Middleware is ICancellableAsyncMiddleware cancellableMiddleware) 114 | { 115 | await cancellableMiddleware.Run(parameter, next, cancellationToken).ConfigureAwait(false); 116 | } 117 | else 118 | { 119 | var middleware = (IAsyncMiddleware)resolverResult.Middleware; 120 | await middleware.Run(parameter, next).ConfigureAwait(false); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/PipelineNet/Pipelines/IAsyncPipeline.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Middleware; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace PipelineNet.Pipelines 7 | { 8 | /// 9 | /// An asynchronous pipeline stores middleware that are executed when is called. 10 | /// The middleware are executed in the same order they are added. 11 | /// 12 | /// The type that will be the input for all the middleware. 13 | public interface IAsyncPipeline 14 | { 15 | /// 16 | /// Adds a middleware type to be executed. 17 | /// 18 | /// 19 | /// 20 | IAsyncPipeline Add() 21 | where TMiddleware : IAsyncMiddleware; 22 | 23 | /// 24 | /// Adds a cancellable middleware type to be executed. 25 | /// 26 | /// 27 | /// 28 | IAsyncPipeline AddCancellable() 29 | where TCancellableMiddleware : ICancellableAsyncMiddleware; 30 | 31 | /// 32 | /// Execute the configured pipeline. 33 | /// 34 | /// 35 | Task Execute(TParameter parameter); 36 | 37 | /// 38 | /// Execute the configured pipeline. 39 | /// 40 | /// 41 | /// The cancellation token that will be passed to all middleware. 42 | Task Execute(TParameter parameter, CancellationToken cancellationToken); 43 | 44 | /// 45 | /// Adds a middleware type to be executed. 46 | /// 47 | /// The middleware type to be executed. 48 | /// 49 | /// Thrown if the is 50 | /// not an implementation of or . 51 | /// Thrown if is null. 52 | IAsyncPipeline Add(Type middlewareType); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/PipelineNet/Pipelines/IPipeline.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Middleware; 2 | using System; 3 | 4 | namespace PipelineNet.Pipelines 5 | { 6 | /// 7 | /// A pipeline stores middleware that are executed when is called. 8 | /// The middleware are executed in the same order they are added. 9 | /// 10 | /// The type that will be the input for all the middleware. 11 | public interface IPipeline 12 | { 13 | /// 14 | /// Adds a middleware type to be executed. 15 | /// 16 | /// 17 | /// 18 | IPipeline Add() 19 | where TMiddleware : IMiddleware; 20 | 21 | /// 22 | /// Executes the configured pipeline. 23 | /// 24 | /// The input that will be provided to all middleware. 25 | void Execute(TParameter parameter); 26 | 27 | /// 28 | /// Adds a middleware type to be executed. 29 | /// 30 | /// The middleware type to be executed. 31 | /// 32 | /// Thrown if the is 33 | /// not an implementation of . 34 | /// Thrown if is null. 35 | IPipeline Add(Type middlewareType); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/PipelineNet/Pipelines/Pipeline.cs: -------------------------------------------------------------------------------- 1 | using PipelineNet.Middleware; 2 | using PipelineNet.MiddlewareResolver; 3 | using System; 4 | 5 | namespace PipelineNet.Pipelines 6 | { 7 | /// 8 | /// A pipeline stores middleware that are executed when is called. 9 | /// The middleware are executed in the same order they are added. 10 | /// 11 | /// The type that will be the input for all the middleware. 12 | public class Pipeline : BaseMiddlewareFlow>, IPipeline 13 | { 14 | /// 15 | /// Creates a new instance of Pipeline. 16 | /// 17 | /// Resolver responsible for resolving instances out of middleware types. 18 | public Pipeline(IMiddlewareResolver middlewareResolver) : base(middlewareResolver) 19 | {} 20 | 21 | /// 22 | /// Adds a middleware type to be executed. 23 | /// 24 | /// 25 | /// 26 | public IPipeline Add() 27 | where TMiddleware : IMiddleware 28 | { 29 | MiddlewareTypes.Add(typeof(TMiddleware)); 30 | return this; 31 | } 32 | 33 | /// 34 | /// Adds a middleware type to be executed. 35 | /// 36 | /// The middleware type to be executed. 37 | /// 38 | /// Thrown if the is 39 | /// not an implementation of . 40 | /// Thrown if is null. 41 | public IPipeline Add(Type middlewareType) 42 | { 43 | base.AddMiddleware(middlewareType); 44 | return this; 45 | } 46 | 47 | /// 48 | /// Executes the configured pipeline. 49 | /// 50 | /// The input that will be provided to all middleware. 51 | public void Execute(TParameter parameter) 52 | { 53 | if (MiddlewareTypes.Count == 0) 54 | return; 55 | 56 | int index = 0; 57 | Action next = null; 58 | next = (parameter2) => 59 | { 60 | MiddlewareResolverResult middlewareResolverResult = null; 61 | try 62 | { 63 | var middlewareType = MiddlewareTypes[index]; 64 | middlewareResolverResult = MiddlewareResolver.Resolve(middlewareType); 65 | 66 | index++; 67 | if (index == MiddlewareTypes.Count) 68 | next = (p) => { }; 69 | 70 | EnsureMiddlewareNotNull(middlewareResolverResult, middlewareType); 71 | RunMiddleware(middlewareResolverResult, parameter2, next); 72 | } 73 | finally 74 | { 75 | DisposeMiddleware(middlewareResolverResult); 76 | } 77 | }; 78 | 79 | next(parameter); 80 | } 81 | 82 | private static void RunMiddleware(MiddlewareResolverResult middlewareResolverResult, TParameter parameter, Action next) 83 | { 84 | var middleware = (IMiddleware)middlewareResolverResult.Middleware; 85 | middleware.Run(parameter, next); 86 | } 87 | } 88 | } 89 | --------------------------------------------------------------------------------