Skip to content

Instantly share code, notes, and snippets.

@Eibwen
Last active August 22, 2018 22:06
Show Gist options
  • Save Eibwen/1d9dc709f63249a16727c53ab57cbab4 to your computer and use it in GitHub Desktop.
Save Eibwen/1d9dc709f63249a16727c53ab57cbab4 to your computer and use it in GitHub Desktop.
Generic pieces to allow building a pipeline similar to HttpClientHandler or DelegatingHandlers in C#

Generic Handler Pipeline

This introduction is here in case you'd like to treat this as a programming excercise yourself. If doing so, feel free to implement it in any language you'd like.

Problem description

Many structures (HttpClient, Delegating handlers in MVC/WebAPI) in C# use a handler pattern. Which often have helper methods make it easy to use them but actually building one from scratch is not often done.

And those ones I've seen are always concrete types as input/outputs. So I'd like to make a generic one, so that those helper mehods do not need to be rewritten for each concrete input/output type that we need.

  • Requirement 1: Have a method that when passed IEnumerable<GenericPipelineHandler<TIn, TOut>> will result in an object that has a method similar to TOut Process(TIn input);, and if that method is called each handler is called in order. Each handler should be able to work with and modify both the input object and the output object before it is passed inward or outward respectively.
    • It can be up to you on if it needs further parameters, below HttpClientFactory has a different object for the innerHandler than the rest of the handlers, so perhaps that is a good design or even required?
    • You can also define the interface of GenericPipelineHandler<TIn, TOut>
  • Requirement 2: We do not want to use a loop to access each Handler, instead it should act like DelegatingHandlers in ASP.NET MVC or like Interceptors in OkHttp

We can maybe use HttpClientFactory and related objects as a basis of our interface? Or perhaps OkHttp interceptors?

My solution

The actual solution I came up with is below, for the exercise I'd suggest you attempting it first, then you can look at my solution and compare/contrast. I definitely do not know if I'm fully happy with my implementation, I'd love to hear other people's opinions/ideas.

using System;
using System.Collections.Generic;
using System.Linq;
namespace GenericPipelineHandler
{
public abstract class GenericPipelineHandler<TIn, TOut>
{
public abstract TIn ProcessInput(TIn input);
public abstract TOut ProcessOutput(TOut output);
}
public static class PipelineFactory<TIn, TOut>
{
public static GenericPipeline<TIn, TOut> Create(Func<TIn, TOut> innerMost, IEnumerable<GenericPipelineHandler<TIn, TOut>> handlers)
{
GenericPipeline<TIn, TOut> innerMore = new DefaultPipeline(innerMost);
foreach (var parentFunc in handlers.Reverse())
{
var parent = new PipelineWrapper(parentFunc)
{
InnerHandler = innerMore
};
innerMore = parent;
}
return innerMore;
}
public class PipelineWrapper : GenericPipeline<TIn, TOut>
{
private readonly GenericPipelineHandler<TIn, TOut> _thisFunc;
public PipelineWrapper(GenericPipelineHandler<TIn, TOut> thisFunc)
{
_thisFunc = thisFunc;
}
protected internal override TOut Process(TIn input)
{
var processedInput = _thisFunc.ProcessInput(input);
var innerOutput = InnerHandler.Process(processedInput);
return _thisFunc.ProcessOutput(innerOutput);
}
}
public class DefaultPipeline : GenericPipeline<TIn, TOut>
{
private readonly Func<TIn, TOut> _processor;
public DefaultPipeline(Func<TIn, TOut> processor)
{
_processor = processor;
}
protected internal override TOut Process(TIn input)
{
return _processor(input);
}
}
}
public abstract class GenericPipeline<TIn, TOut>
{
protected internal GenericPipeline<TIn, TOut> InnerHandler { set; get; }
protected GenericPipeline()
{
}
protected internal abstract TOut Process(TIn input);
}
}
void Main()
{
var handlers = new []
{
new MyStringAppender("a", "z"),
new MyStringAppender("b", "y"),
new MyStringAppender("c", "x"),
};
var myGodForsakenPipeline = PipelineFactory<IEnumerable<string>, IEnumerable<string>>
.Create(i => i.Append("- Response -"), handlers);
myGodForsakenPipeline.Dump();
myGodForsakenPipeline.Process(Enumerable.Repeat("- first -", 1)).Dump();
}
public class MyStringAppender : GenericPipelineHandler<IEnumerable<string>, IEnumerable<string>>
{
private readonly string _toPrepend;
private readonly string _toAppend;
public MyStringAppender(string toPrepend, string toAppend)
{
_toPrepend = toPrepend;
_toAppend = toAppend;
}
public override IEnumerable<string> ProcessInput(IEnumerable<string> input)
{
return input.Append(_toPrepend);
}
public override IEnumerable<string> ProcessOutput(IEnumerable<string> output)
{
return output.Append(_toAppend);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment