Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Obsolete Finally #23

Merged
merged 3 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -79,28 +82,50 @@ result = exceptionHandlersChain.Execute(new ArgumentExceptionHandler()); // Resu
// If no middleware matches returns a value, the default of the return type is returned, which in the case of 'bool' is false.
result = exceptionHandlersChain.Execute(new InvalidOperationException()); // Result will be false
```
You can even define a fallback function that will be executed after your entire chain:
You can even define a fallback 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) =>
.Finally<FinallyDoSomething>();

public class FinallyDoSomething : IFinally<Exception, bool>
mariusz96 marked this conversation as resolved.
Show resolved Hide resolved
{
public bool Finally(Exception parameter)
{
// Do something
return true;
});
}
}
```
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.
The result will be true because of the type used in the `Finally` method.

You can also choose to throw an exception in the `Finally` method instead of returning a value:
```C#
var exceptionHandlersChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Chain<OutOfMemoryExceptionHandler>()
.Chain<ArgumentExceptionHandler>()
.Finally<ThrowInvalidOperationException>();

public class ThrowInvalidOperationException : IFinally<Exception, bool>
{
public bool Finally(Exception parameter)
{
throw new InvalidOperationException("End of the chain of responsibility reached. No middleware matches returned a value.");
}
}
```
Now if the end of the chain was reached and no middleware matches returned a value, the `InvalidOperationException` will be thrown.

## Pipeline vs Chain of responsibility
Here is the difference between those two in PipelineNet:
- Chain of responsibility:
- Returns a value;
- Have a fallback function to execute at the end of the chain;
- Have a fallback to execute at the end of the chain;
- Used when you want that only one middleware to get executed based on an input, like the exception handling example;
- Pipeline:
- Does not return a value;
Expand Down Expand Up @@ -166,21 +191,26 @@ Task.WaitAll(new Task[]{ task1, task2, task3 });
The chain of responsibility also has two implementations: `ResponsibilityChain<TParameter, TReturn>` and `AsyncResponsibilityChain<TParameter, TReturn>`.
Both have the same functionaly, aggregate and execute a series of middleware retrieving a return type.

One difference of chain responsibility when compared to pipeline is the fallback function that can be defined with
the `Finally` method. You can set one function for chain of responsibility, calling the method more than once
will replace the previous function defined.
One difference of chain responsibility when compared to pipeline is the fallback that can be defined with
the `Finally` method. You can set one finally for chain of responsibility, calling the method more than once
will replace the previous type used.

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) =>
.Finally<ExceptionHandlerFallback>();

public class ExceptionHandlerFallback : IAsyncFinally<Exception, bool>
{
public Task<bool> Finally(Exception parameter)
{
ex.Source = ExceptionSource;
parameter.Data.Add("MoreExtraInfo", "More information about the exception.");
return Task.FromResult(true);
});
}
}
```
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. This will be removed in the next major version.

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

public class FinallyDoSomething : IFinally<Exception, bool>
{
public bool Finally(Exception parameter)
{
// Do something
return true;
}
}
```

## 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 @@
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 @@
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 @@

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 @@



#pragma warning disable CS0618 // Type or member is obsolete
/// <summary>
/// Try to generate a deadlock in synchronous middleware.
/// </summary>
Expand All @@ -180,9 +200,47 @@
.Finally(input => Task.FromResult(input));

var resultTask = responsibilityChain.Execute(" Test\nwith spaces\n and new lines \n ");
var result = resultTask.Result;

Check warning on line 203 in src/PipelineNet.Tests/ChainsOfResponsibility/AsyncResponsibilityChainTests.cs

View workflow job for this annotation

GitHub Actions / build

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Check warning on line 203 in src/PipelineNet.Tests/ChainsOfResponsibility/AsyncResponsibilityChainTests.cs

View workflow job for this annotation

GitHub Actions / build

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

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>
{
public 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
Loading