-
Notifications
You must be signed in to change notification settings - Fork 26
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
Add cancellation tokens #21
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,6 +71,15 @@ | |
await executeNext(context); | ||
} | ||
} | ||
|
||
public class ThrowIfCancellationRequestedMiddleware : ICancellableAsyncMiddleware<PersonModel> | ||
{ | ||
public async Task Run(PersonModel context, Func<PersonModel, Task> executeNext, CancellationToken cancellationToken) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
await executeNext(context); | ||
} | ||
} | ||
#endregion | ||
|
||
[Fact] | ||
|
@@ -123,7 +132,7 @@ | |
Gender = Gender.Other | ||
}; | ||
|
||
pipeline.Execute(personModel); | ||
Check warning on line 135 in src/PipelineNet.Tests/Pipelines/AsyncPipelineTests.cs
|
||
|
||
// Check if the level of 'personModel' is 4, which is configured by 'PersonWithGenderProperty' middleware. | ||
Assert.Equal(4, personModel.Level); | ||
|
@@ -162,5 +171,33 @@ | |
pipeline.Add(typeof(AsyncPipelineTests)); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public async Task Execute_RunPipelineWithCancellableMiddleware_CancellableMiddlewareIsExecuted() | ||
{ | ||
var pipeline = new AsyncPipeline<PersonModel>(new ActivatorMiddlewareResolver()) | ||
.Add<PersonWithEvenId>() | ||
.Add<PersonWithOddId>() | ||
.Add<PersonWithEmailName>() | ||
.Add<PersonWithGenderProperty>() | ||
.AddCancellable<ThrowIfCancellationRequestedMiddleware>(); | ||
|
||
// Create a new instance with a 'Gender' property. The 'ThrowIfCancellationRequestedMiddleware' | ||
// middleware should be the last one to execute. | ||
var personModel = new PersonModel | ||
{ | ||
Name = "[email protected]", | ||
Gender = Gender.Other | ||
}; | ||
|
||
// Create the cancellation token in the canceled state. | ||
var cancellationToken = new CancellationToken(canceled: true); | ||
|
||
// Check if 'ThrowIfCancellationRequestedMiddleware' threw 'OperationCanceledException'. | ||
await Assert.ThrowsAsync<OperationCanceledException>(() => pipeline.Execute(personModel, cancellationToken)); | ||
|
||
// Check if the level of 'personModel' is 4, which is configured by 'PersonWithGenderProperty' middleware. | ||
Assert.Equal(4, personModel.Level); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using PipelineNet.MiddlewareResolver; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
|
||
namespace PipelineNet | ||
{ | ||
/// <summary> | ||
/// Defines the base class for asynchronous middleware flows. | ||
/// </summary> | ||
/// <typeparam name="TMiddleware">The middleware type.</typeparam> | ||
/// <typeparam name="TCancellableMiddleware">The cancellable middleware type.</typeparam> | ||
public abstract class AsyncBaseMiddlewareFlow<TMiddleware, TCancellableMiddleware> | ||
{ | ||
/// <summary> | ||
/// The list of middleware types. | ||
/// </summary> | ||
protected IList<Type> MiddlewareTypes { get; private set; } | ||
|
||
/// <summary> | ||
/// The resolver used to create the middleware types. | ||
/// </summary> | ||
protected IMiddlewareResolver MiddlewareResolver { get; private set; } | ||
|
||
internal AsyncBaseMiddlewareFlow(IMiddlewareResolver middlewareResolver) | ||
{ | ||
MiddlewareResolver = middlewareResolver ?? throw new ArgumentNullException("middlewareResolver", | ||
"An instance of IMiddlewareResolver must be provided. You can use ActivatorMiddlewareResolver."); | ||
MiddlewareTypes = new List<Type>(); | ||
} | ||
|
||
/// <summary> | ||
/// Stores the <see cref="TypeInfo"/> of the middleware type. | ||
/// </summary> | ||
private static readonly TypeInfo MiddlewareTypeInfo = typeof(TMiddleware).GetTypeInfo(); | ||
|
||
/// <summary> | ||
/// Stores the <see cref="TypeInfo"/> of the cancellable middleware type. | ||
/// </summary> | ||
private static readonly TypeInfo CancellableMiddlewareTypeInfo = typeof(TCancellableMiddleware).GetTypeInfo(); | ||
|
||
|
||
/// <summary> | ||
/// Adds a new middleware type to the internal list of types. | ||
/// Middleware will be executed in the same order they are added. | ||
/// </summary> | ||
/// <param name="middlewareType">The middleware type to be executed.</param> | ||
/// <exception cref="ArgumentException">Thrown if the <paramref name="middlewareType"/> is | ||
/// not an implementation of <typeparamref name="TMiddleware"/> or <see cref="TCancellableMiddleware"/>.</exception> | ||
Check warning on line 49 in src/PipelineNet/AsyncBaseMiddlewareFlow.cs
|
||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="middlewareType"/> is null.</exception> | ||
protected void AddMiddleware(Type middlewareType) | ||
{ | ||
if (middlewareType == null) throw new ArgumentNullException("middlewareType"); | ||
|
||
bool isAssignableFromMiddleware = MiddlewareTypeInfo.IsAssignableFrom(middlewareType.GetTypeInfo()) | ||
|| CancellableMiddlewareTypeInfo.IsAssignableFrom(middlewareType.GetTypeInfo()); | ||
if (!isAssignableFromMiddleware) | ||
throw new ArgumentException( | ||
$"The middleware type must implement \"{typeof(TMiddleware)}\" or \"{typeof(TCancellableMiddleware)}\"."); | ||
|
||
this.MiddlewareTypes.Add(middlewareType); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ namespace PipelineNet.ChainsOfResponsibility | |
/// </summary> | ||
/// <typeparam name="TParameter">The input type for the chain.</typeparam> | ||
/// <typeparam name="TReturn">The return type of the chain.</typeparam> | ||
public class AsyncResponsibilityChain<TParameter, TReturn> : BaseMiddlewareFlow<IAsyncMiddleware<TParameter, TReturn>>, | ||
public class AsyncResponsibilityChain<TParameter, TReturn> : AsyncBaseMiddlewareFlow<IAsyncMiddleware<TParameter, TReturn>, ICancellableAsyncMiddleware<TParameter, TReturn>>, | ||
IAsyncResponsibilityChain<TParameter, TReturn> | ||
{ | ||
/// <summary> | ||
|
@@ -50,13 +50,25 @@ public IAsyncResponsibilityChain<TParameter, TReturn> Chain<TMiddleware>() where | |
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Chains a new cancellable middleware to the chain of responsibility. | ||
/// Middleware will be executed in the same order they are added. | ||
/// </summary> | ||
/// <typeparam name="TCancellableMiddleware">The new middleware being added.</typeparam> | ||
/// <returns>The current instance of <see cref="IAsyncResponsibilityChain{TParameter, TReturn}"/>.</returns> | ||
public IAsyncResponsibilityChain<TParameter, TReturn> ChainCancellable<TCancellableMiddleware>() where TCancellableMiddleware : ICancellableAsyncMiddleware<TParameter, TReturn> | ||
{ | ||
MiddlewareTypes.Add(typeof(TCancellableMiddleware)); | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Chains a new middleware type to the chain of responsibility. | ||
/// Middleware will be executed in the same order they are added. | ||
/// </summary> | ||
/// <param name="middlewareType">The middleware type to be executed.</param> | ||
/// <exception cref="ArgumentException">Thrown if the <paramref name="middlewareType"/> is | ||
/// not an implementation of <see cref="IAsyncMiddleware{TParameter, TReturn}"/>.</exception> | ||
/// not an implementation of <see cref="IAsyncMiddleware{TParameter, TReturn}"/> or <see cref="ICancellableAsyncMiddleware{TParameter, TReturn}"/>.</exception> | ||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="middlewareType"/> is null.</exception> | ||
/// <returns>The current instance of <see cref="IAsyncResponsibilityChain{TParameter, TReturn}"/>.</returns> | ||
public IAsyncResponsibilityChain<TParameter, TReturn> Chain(Type middlewareType) | ||
|
@@ -92,7 +104,6 @@ public async Task<TReturn> Execute(TParameter parameter, CancellationToken cance | |
{ | ||
var type = MiddlewareTypes[index]; | ||
resolverResult = MiddlewareResolver.Resolve(type); | ||
var middleware = (IAsyncMiddleware<TParameter, TReturn>)resolverResult.Middleware; | ||
|
||
index++; | ||
// If the current instance of middleware is the last one in the list, | ||
|
@@ -142,20 +153,33 @@ public async Task<TReturn> Execute(TParameter parameter, CancellationToken cance | |
} | ||
} | ||
|
||
if (resolverResult.IsDisposable && !(middleware is IDisposable | ||
if (resolverResult == null || resolverResult.Middleware == null) | ||
{ | ||
throw new InvalidOperationException($"'{MiddlewareResolver.GetType()}' failed to resolve middleware of type '{type}'."); | ||
} | ||
|
||
if (resolverResult.IsDisposable && !(resolverResult.Middleware is IDisposable | ||
#if NETSTANDARD2_1_OR_GREATER | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll create a PR to drop support for net standard < 2.1 for the version 1.0.0 of the package. This will help to simplify the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't do that please. 2.1 is kind of uncommon. And it doesn't work on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I thought the 2.1 was the one that was also used by .net framework, just saw it is the 2.0 😞 Ok, we can at least bump it to 2.0, although that wouldn't change anything in terms of code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would you do that then? It would only break libraries that target 1.1-1.6.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The recommendation from Microsoft is to have the netstandard 2.0 as the lowest version for packages unless there's a good use case to have for supporting old ones. Do you need it for older Net Framework versions? |
||
|| middleware is IAsyncDisposable | ||
|| resolverResult.Middleware is IAsyncDisposable | ||
#endif | ||
)) | ||
{ | ||
throw new InvalidOperationException($"'{middleware.GetType().FullName}' type does not implement IDisposable" + | ||
throw new InvalidOperationException($"'{resolverResult.Middleware.GetType()}' type does not implement IDisposable" + | ||
#if NETSTANDARD2_1_OR_GREATER | ||
" or IAsyncDisposable" + | ||
#endif | ||
"."); | ||
} | ||
|
||
return await middleware.Run(param, func).ConfigureAwait(false); | ||
if (resolverResult.Middleware is ICancellableAsyncMiddleware<TParameter, TReturn> cancellableMiddleware) | ||
{ | ||
return await cancellableMiddleware.Run(param, func, cancellationToken).ConfigureAwait(false); | ||
} | ||
else | ||
{ | ||
var middleware = (IAsyncMiddleware<TParameter, TReturn>)resolverResult.Middleware; | ||
return await middleware.Run(param, func).ConfigureAwait(false); | ||
} | ||
} | ||
finally | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace PipelineNet.Middleware | ||
{ | ||
/// <summary> | ||
/// Defines the asynchronous chain of responsibility middleware with cancellation token. | ||
/// </summary> | ||
/// <typeparam name="TParameter">The input type for the middleware.</typeparam> | ||
/// <typeparam name="TReturn">The return type of the middleware.</typeparam> | ||
public interface ICancellableAsyncMiddleware<TParameter, TReturn> | ||
{ | ||
/// <summary> | ||
/// Runs the middleware. | ||
/// </summary> | ||
/// <param name="parameter">The input parameter.</param> | ||
/// <param name="next">The next middleware in the flow.</param> | ||
/// <param name="cancellationToken">The cancellation token.</param> | ||
/// <returns>The return value.</returns> | ||
Task<TReturn> Run(TParameter parameter, Func<TParameter, Task<TReturn>> next, CancellationToken cancellationToken); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this approach!