Skip to content

Instantly share code, notes, and snippets.

@seclerp
Last active July 7, 2022 16:33
Show Gist options
  • Save seclerp/4a3dbd789170b84583a35f26bcfce052 to your computer and use it in GitHub Desktop.
Save seclerp/4a3dbd789170b84583a35f26bcfce052 to your computer and use it in GitHub Desktop.
Custom middleware-like Chain Of Responsibility implementation
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);
}
}
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);
};
}
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);
@ferryferry
Copy link

👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment