Skip to content

Commit

Permalink
add IFinally
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusz96 committed Aug 23, 2024
1 parent bd73c5c commit 68107ef
Show file tree
Hide file tree
Showing 10 changed files with 509 additions and 28 deletions.
97 changes: 79 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ dotnet add package PipelineNet
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Simple example](#simple-example)
- [Pipeline vs Chain of responsibility](#pipeline-vs-chain-of-responsibility)
- [Middleware](#middleware)
- [Pipelines](#pipelines)
- [Chains of responsibility](#chains-of-responsibility)
- [Middleware resolver](#middleware-resolver)
- [License](#license)
- [Simple example](#simple-example)
- [Pipeline vs Chain of responsibility](#pipeline-vs-chain-of-responsibility)
- [Middleware](#middleware)
- [Pipelines](#pipelines)
- [Chains of responsibility](#chains-of-responsibility)
- [Middleware resolver](#middleware-resolver)
- [ServiceProvider implementation](#serviceprovider-implementation)
- [Unity implementation](#unity-implementation)
- [Migrate from PipelineNet 0.10 to 0.20](#migrate-from-pipelinenet-010-to-020)
- [License](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -65,7 +68,7 @@ public class ArgumentExceptionHandler : IMiddleware<Exception, bool>
Now we create a chain of responsibility with the middleware:
```C#
var exceptionHandlersChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryExceptionHandler>() // The order of middleware being chained matters
.Chain<OutOfMemoryExceptionHandler>()
.Chain<ArgumentExceptionHandler>();
```
Now your instance of `ResponsibilityChain` can be executed as many times as you want:
Expand All @@ -81,21 +84,43 @@ result = exceptionHandlersChain.Execute(new InvalidOperationException()); // Res
```
You can even define a fallback function that will be executed after your entire chain:
```C#
var exceptionHandlersChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryExceptionHandler>() // The order of middleware being chained matters
.Chain<ArgumentExceptionHandler>()
.Finally((parameter) =>
public class FinallyDoSomething : IFinally<Exception, bool>
{
public bool Finally(Exception parameter)
{
// Do something
return true;
});
}
}

var exceptionHandlersChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryExceptionHandler>()
.Chain<ArgumentExceptionHandler>()
.Finally<FinallyDoSomething>();
```
Now if the same line gets executed:
```C#
var result = exceptionHandlersChain.Execute(new InvalidOperationException()); // Result will be true
```
The result will be true because of the function defined in the `Finally` method.

You can also opt in to throw an exception in the `Finally` method instead of returning a value:
```C#
public class FinallyThrow : IFinally<Exception, bool>
{
public bool Finally(Exception parameter)
{
throw new InvalidOperationException("End of the chain of responsibility reached. No middleware matches returned a value.");
}
}

var exceptionHandlersChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryExceptionHandler>()
.Chain<ArgumentExceptionHandler>()
.Finally<FinallyThrow>();
```
Now if the end of the chain was reached and no middleware matches returned a value, the exception will be thrown.

## Pipeline vs Chain of responsibility
Here is the difference between those two in PipelineNet:
- Chain of responsibility:
Expand Down Expand Up @@ -173,14 +198,19 @@ will replace the previous function defined.
As we already have an example of a chain of responsibility, here is an example using the asynchronous implementation:
If you want to, you can use the asynchronous version, using asynchronous middleware. Changing the instantiation to:
```C#
var exceptionHandlersChain = new AsyncResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryAsyncExceptionHandler>() // The order of middleware being chained matters
.Chain<ArgumentAsyncExceptionHandler>()
.Finally((ex) =>
public class FinallySetExceptionSource : IAsyncFinally<Exception, bool>
{
public Task<bool> Finally(Exception parameter)
{
ex.Source = ExceptionSource;
return Task.FromResult(true);
});
}
}

var exceptionHandlersChain = new AsyncResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryAsyncExceptionHandler>()
.Chain<ArgumentAsyncExceptionHandler>()
.Finally<FinallySetExceptionSource>();
```
And here is the execution:
```C#
Expand Down Expand Up @@ -271,5 +301,36 @@ An implementation of the [middleware resolver for Unity](https://github.com/Shan
Install-Package PipelineNet.Unity
```

## Migrate from PipelineNet 0.10 to 0.20
In PipelineNet 0.20, `Finally` overloads that use `Func` have been made obsolete.

To migrate replace:
```C#
var exceptionHandlersChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryExceptionHandler>()
.Chain<ArgumentExceptionHandler>()
.Finally((parameter) =>
{
// Do something
return true;
});
```
With:
```C#
public class FinallyDoSomething : IFinally<Exception, bool>
{
public bool Finally(Exception parameter)
{
// Do something
return true;
}
}

var exceptionHandlersChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryExceptionHandler>()
.Chain<ArgumentExceptionHandler>()
.Finally<FinallyDoSomething>();
```

## License
This project is licensed under MIT. Please, feel free to contribute with code, issues or tips :)
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using PipelineNet.ChainsOfResponsibility;
using PipelineNet.Finally;
using PipelineNet.Middleware;
using PipelineNet.MiddlewareResolver;
using System;
using System.Threading.Tasks;
using Xunit;

namespace PipelineNet.Tests.ChainsOfResponsibility
Expand Down Expand Up @@ -88,6 +87,24 @@ public async Task<bool> Run(Exception exception, Func<Exception, Task<bool>> exe
return await executeNext(exception);
}
}

public class FinallyThrow : IAsyncFinally<Exception, bool>
{
public Task<bool> Finally(Exception exception)
{
throw new InvalidOperationException(
"End of the asynchronous chain of responsibility reached. No middleware matches returned a value.");
}
}

public class FinallyThrowIfCancellationRequested : ICancellableAsyncFinally<Exception, bool>
{
public Task<bool> Finally(Exception exception, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.FromResult(default(bool));
}
}
#endregion

[Fact]
Expand Down Expand Up @@ -127,6 +144,7 @@ public async Task Execute_ChainOfMiddlewareThatDoesNotHandleTheException_ChainRe
Assert.Equal(default(bool), result);
}

#pragma warning disable CS0618 // Type or member is obsolete
[Fact]
public async Task Execute_ChainOfMiddlewareWithFinallyFunc_FinallyFuncIsExecuted()
{
Expand All @@ -152,6 +170,7 @@ public async Task Execute_ChainOfMiddlewareWithFinallyFunc_FinallyFuncIsExecuted

Assert.Equal(ExceptionSource, exception.Source);
}
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// Tests the <see cref="ResponsibilityChain{TParameter, TReturn}.Chain(Type)"/> method.
Expand All @@ -168,6 +187,7 @@ public void Chain_AddTypeThatIsNotAMiddleware_ThrowsException()



#pragma warning disable CS0618 // Type or member is obsolete
/// <summary>
/// Try to generate a deadlock in synchronous middleware.
/// </summary>
Expand All @@ -184,5 +204,43 @@ public void Execute_SynchronousChainOfResponsibility_SuccessfullyExecute()

Assert.Equal("Test with spaces and new lines", result);
}
#pragma warning restore CS0618 // Type or member is obsolete

[Fact]
public async Task Execute_ChainOfMiddlewareWithFinally_FinallyIsExecuted()
{
var responsibilityChain = new AsyncResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<UnavailableResourcesExceptionHandler>()
.Chain(typeof(InvalidateDataExceptionHandler))
.Chain<MyExceptionHandler>()
.Finally<FinallyThrow>();

// Creates an ArgumentNullException. The 'MyExceptionHandler'
// middleware should be the last one to execute.
var exception = new ArgumentNullException();

// The 'FinallyThrow' should throw 'InvalidOperationException'.
await Assert.ThrowsAsync<InvalidOperationException>(() => responsibilityChain.Execute(exception));
}

[Fact]
public async Task Execute_ChainOfMiddlewareWithCancellableFinally_CancellableFinallyIsExecuted()
{
var responsibilityChain = new AsyncResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<UnavailableResourcesExceptionHandler>()
.Chain(typeof(InvalidateDataExceptionHandler))
.Chain<MyExceptionHandler>()
.CancellableFinally<FinallyThrowIfCancellationRequested>();

// Creates an ArgumentNullException. The 'MyExceptionHandler'
// middleware should be the last one to execute.
var exception = new ArgumentNullException();

// Create the cancellation token in the canceled state.
var cancellationToken = new CancellationToken(canceled: true);

// The 'FinallyThrowIfCancellationRequested' should throw 'OperationCanceledException'.
await Assert.ThrowsAsync<OperationCanceledException>(() => responsibilityChain.Execute(exception, cancellationToken));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using PipelineNet.ChainsOfResponsibility;
using PipelineNet.Finally;
using PipelineNet.Middleware;
using PipelineNet.MiddlewareResolver;
using System;
using Xunit;

namespace PipelineNet.Tests.ChainsOfResponsibility
Expand Down Expand Up @@ -63,6 +63,15 @@ public bool Run(Exception exception, Func<Exception, bool> executeNext)
return executeNext(exception);
}
}

public class FinallyThrow : IFinally<Exception, bool>
{
bool IFinally<Exception, bool>.Finally(Exception parameter)
{
throw new InvalidOperationException(
"End of the chain of responsibility reached. No middleware matches returned a value.");
}
}
#endregion

[Fact]
Expand Down Expand Up @@ -102,6 +111,7 @@ public void Execute_ChainOfMiddlewareThatDoesNotHandleTheException_ChainReturnsD
Assert.Equal(default(bool), result);
}

#pragma warning disable CS0618 // Type or member is obsolete
[Fact]
public void Execute_ChainOfMiddlewareWithFinallyFunc_FinallyFuncIsExecuted()
{
Expand All @@ -127,6 +137,7 @@ public void Execute_ChainOfMiddlewareWithFinallyFunc_FinallyFuncIsExecuted()

Assert.Equal(ExceptionSource, exception.Source);
}
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// Tests the <see cref="ResponsibilityChain{TParameter, TReturn}.Chain(Type)"/> method.
Expand All @@ -140,5 +151,23 @@ public void Chain_AddTypeThatIsNotAMiddleware_ThrowsException()
responsibilityChain.Chain(typeof(ResponsibilityChainTests));
});
}

[Fact]
public void Execute_ChainOfMiddlewareWithFinally_FinallyIsExecuted()
{
var responsibilityChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<UnavailableResourcesExceptionHandler>()
.Chain(typeof(InvalidateDataExceptionHandler))
.Chain<MyExceptionHandler>()
.Finally<FinallyThrow>();

// Creates an ArgumentNullException. The 'MyExceptionHandler'
// middleware should be the last one to execute.
var exception = new ArgumentNullException();

// The 'FinallyThrow' should throw 'InvalidOperationException'.
Assert.Throws<InvalidOperationException>(() => responsibilityChain.Execute(exception));
}

}
}
Loading

0 comments on commit 68107ef

Please sign in to comment.