Skip to content

Instantly share code, notes, and snippets.

@dclucas
Created April 4, 2014 02:24
Show Gist options
  • Save dclucas/9966923 to your computer and use it in GitHub Desktop.
Save dclucas/9966923 to your computer and use it in GitHub Desktop.
IoC chain of responsibility
/*
An IoC-based Chain of Responsibility implementation, supporting async execution. This sample uses
SimpleInjector for IoC and Seterlund.CodeGuard for argument validation
The generic ChainOfResponsibility class provides base behavior that can be extended through inheritance
but should be functional on its own on most cases.
Chain resolution is determined by message types, so a given message type (which can be any class of
your choosing) may have at most one chain associated to it.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Seterlund.CodeGuard;
using SimpleInjector;
using SimpleInjector.Extensions;
namespace ChainOfIoC
{
#region Base classes and interfaces
public class ChainOfResponsibility<TMessage>
: IChainOfResponsibiity<TMessage>
where TMessage : class
{
public IEnumerable<IChainLink<TMessage>> Links { get; private set; }
public bool ThrowOnUnhandled { get; private set; }
public bool RunParallel { get; private set; }
// Just one public constructor, so we do not break SimpleInjector
public ChainOfResponsibility(IEnumerable<IChainLink<TMessage>> links)
: this(links, true, true)
{
}
// Protected constructor for behavior override on inheriting classes
protected ChainOfResponsibility(IEnumerable<IChainLink<TMessage>> links, bool throwOnUnhandled, bool runParallel)
{
Guard.That(() => links)
.IsNotNull()
.IsNotEmpty();
Links = links;
ThrowOnUnhandled = throwOnUnhandled;
RunParallel = runParallel;
}
public void Execute(TMessage message)
{
Guard.That(() => message).IsNotNull();
var handled = false;
if (RunParallel)
{
Parallel.ForEach(Links, link => { handled |= link.HandleMessage(message); });
}
else
{
handled = Links.Aggregate(false, (current, l) => current | l.HandleMessage(message));
}
if (!handled && ThrowOnUnhandled)
{
// todo: consider using a custom exception here
throw new InvalidOperationException(
String.Format("Failed to handle message {0}, of type {1}", message.ToString(), message.GetType().FullName));
}
}
public async Task ExecuteAsync(TMessage message)
{
await Task.Run(() => Execute(message));
}
}
public interface IChainLink<in TMessage>
{
bool HandleMessage(TMessage message);
}
public interface IChainOfResponsibiity<T>
{
void Execute(T message);
Task ExecuteAsync(T message);
}
#endregion
#region Sample
public class SampleMessage
{
public string Content { get; set; }
}
public class ChainLinkBase : IChainLink<SampleMessage>
{
public ChainLinkBase(TimeSpan sleepDelay)
{
SleepDelay = sleepDelay;
}
public bool HandleMessage(SampleMessage message)
{
Console.WriteLine("Starting handle method for class {0}, for message {1}", GetClassName(), message.Content);
Thread.Sleep(SleepDelay);
Console.WriteLine("Sleep now over for class {0}.", GetClassName());
return true;
}
private string GetClassName()
{
return GetType().FullName;
}
public TimeSpan SleepDelay { get; private set; }
}
public class ChainLink1 : ChainLinkBase
{
public ChainLink1()
: base(TimeSpan.FromSeconds(3))
{
}
}
public class ChainLink2 : ChainLinkBase
{
public ChainLink2()
: base(TimeSpan.FromSeconds(2))
{
}
}
#endregion
class Program
{
static readonly Container container = new Container();
static void InitializeContainer()
{
container.RegisterOpenGeneric(typeof(IChainOfResponsibiity<>), typeof(ChainOfResponsibility<>));
container.RegisterAll<IChainLink<SampleMessage>>(
typeof(ChainLink1),
typeof(ChainLink2));
}
static void Main(string[] args)
{
InitializeContainer();
var chain = container.GetInstance<IChainOfResponsibiity<SampleMessage>>();
chain.ExecuteAsync(new SampleMessage() {Content = "Hello World"}).Wait();
Console.ReadKey();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment