Last active
July 7, 2022 16:33
-
-
Save seclerp/4a3dbd789170b84583a35f26bcfce052 to your computer and use it in GitHub Desktop.
Custom middleware-like Chain Of Responsibility implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Interceptors; | |
public class FirstInterceptor : IInterceptor | |
{ | |
public async Task InvokeAsync(PipelineStep next, EventContext ctx) | |
{ | |
Console.WriteLine($"Hello from interceptor {nameof(FirstInterceptor)}"); | |
await next.Invoke(ctx); | |
} | |
} | |
public class SecondInterceptor : IInterceptor | |
{ | |
public async Task InvokeAsync(PipelineStep next, EventContext ctx) | |
{ | |
await next.Invoke(ctx); | |
Console.WriteLine($"Hello from interceptor {nameof(SecondInterceptor)}"); | |
} | |
} | |
public class ThirdInterceptor : IInterceptor | |
{ | |
public async Task InvokeAsync(PipelineStep next, EventContext ctx) | |
{ | |
Console.WriteLine($"Data: {string.Join(", ", ctx.SomeContextData)}"); | |
await next.Invoke(ctx); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Extensions.DependencyInjection; | |
namespace Interceptors; | |
// Func<EventContext, Task> | |
public delegate Task PipelineStep(EventContext ctx); | |
public interface IInterceptor | |
{ | |
Task InvokeAsync(PipelineStep next, EventContext ctx); | |
} | |
public class EventContext | |
{ | |
public IEnumerable<string> SomeContextData { get; set; } | |
} | |
// Null-object pattern | |
public class DeadEndInterceptor : IInterceptor | |
{ | |
public Task InvokeAsync(PipelineStep next, EventContext _) => Task.CompletedTask; | |
} | |
/// <summary> | |
/// Instance-based interceptor pipeline. | |
/// </summary> | |
public class InterceptorPipelineBuilder | |
{ | |
private readonly LinkedList<IInterceptor> _interceptorsList; | |
public InterceptorPipelineBuilder() | |
{ | |
_interceptorsList = new LinkedList<IInterceptor>(); | |
} | |
public InterceptorPipelineBuilder AddInterceptor(IInterceptor interceptor) | |
{ | |
_interceptorsList.AddFirst(interceptor); | |
return this; | |
} | |
public PipelineStep Build() | |
{ | |
var currentStep = CreateStep(new DeadEndInterceptor(), _ => Task.CompletedTask); | |
foreach (var interceptor in _interceptorsList) | |
{ | |
var nextStep = currentStep; | |
currentStep = CreateStep(interceptor, nextStep); | |
} | |
return currentStep; | |
} | |
private PipelineStep CreateStep(IInterceptor interceptor, PipelineStep next) => | |
async ctx => | |
{ | |
await interceptor.InvokeAsync(next, ctx); | |
}; | |
} | |
/// <summary> | |
/// Dependency injection container based interceptor pipeline. | |
/// </summary> | |
public class InterceptorTypePipelineBuilder | |
{ | |
private readonly IServiceProvider _serviceProvider; | |
private readonly LinkedList<Type> _interceptorsList; | |
public InterceptorTypePipelineBuilder(IServiceProvider serviceProvider) | |
{ | |
_serviceProvider = serviceProvider; | |
_interceptorsList = new LinkedList<Type>(); | |
} | |
public InterceptorTypePipelineBuilder AddInterceptor<TInterceptor>() | |
where TInterceptor : IInterceptor | |
{ | |
_interceptorsList.AddFirst(typeof(TInterceptor)); | |
return this; | |
} | |
public PipelineStep Build() | |
{ | |
var currentStep = CreateStep(typeof(DeadEndInterceptor), _ => Task.CompletedTask); | |
foreach (var interceptorType in _interceptorsList) | |
{ | |
var nextStep = currentStep; | |
currentStep = CreateStep(interceptorType, nextStep); | |
} | |
return currentStep; | |
} | |
private PipelineStep CreateStep(Type interceptorType, PipelineStep next) => | |
async ctx => | |
{ | |
var interceptorInstance = (IInterceptor)_serviceProvider.GetRequiredService(interceptorType); | |
await interceptorInstance.InvokeAsync(next, ctx); | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Interceptors; | |
using Microsoft.Extensions.DependencyInjection; | |
async Task WithoutDependencyInjection(EventContext context) | |
{ | |
var pipeline = new InterceptorPipelineBuilder() | |
.AddInterceptor(new FirstInterceptor()) | |
.AddInterceptor(new SecondInterceptor()) | |
.AddInterceptor(new ThirdInterceptor()) | |
.Build(); | |
await pipeline.Invoke(context); | |
} | |
async Task WithDependencyInjection(EventContext context) | |
{ | |
var serviceProvider = new ServiceCollection() | |
.AddSingleton(sp => new InterceptorTypePipelineBuilder(sp) | |
.AddInterceptor<FirstInterceptor>() | |
.AddInterceptor<SecondInterceptor>() | |
.AddInterceptor<ThirdInterceptor>()) | |
.AddTransient<DeadEndInterceptor>() | |
.AddTransient<FirstInterceptor>() | |
.AddTransient<SecondInterceptor>() | |
.AddTransient<ThirdInterceptor>() | |
.BuildServiceProvider(); | |
var pipeline = serviceProvider.GetRequiredService<InterceptorTypePipelineBuilder>().Build(); | |
await pipeline.Invoke(context); | |
} | |
var context = new EventContext | |
{ | |
SomeContextData = new[] | |
{ | |
"Hello", "World" | |
} | |
}; | |
await WithoutDependencyInjection(context); | |
await WithDependencyInjection(context); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
👍🏼