Created
April 4, 2014 02:24
-
-
Save dclucas/9966923 to your computer and use it in GitHub Desktop.
IoC chain of responsibility
This file contains 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
/* | |
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