As you can see from my previous post on handler filters, an IHandlersFilter will have a chance to do stuff to the array of handlers that would otherwise be used untouched during a call to ResolveAll ( ResolveAll is also what happens behind the scenes when a component relies on CollectionResolver to supply a collection of components for it to use).
Now, what is left is to implement some kind of logic that does something to the handlers array. First thing that comes to my mind is ordering the handlers!
Ordering the handlers is very relevant e.g. in scenarios where tasks are modeled as a series of steps that should be executed in a consistent fashion. An example could be a component ( TaskProcessor) that gets a collection of implementations of some task-like interface injected ( IEnumerable<ITask>).
Now, to ensure that this list of tasks is properly ordered, I’d like to specify a few hints on how they should be ordered. Not too little, because otherwise stuff would not work, but I also don’t want to be overly explicit on how the tasks should be ordered.
So I came up with a solution based on two attributes: ExecutesBeforeAttribute and ExecutesAfterAttribute, allowing types to position themselves in relation to other types they know about, and all other types to stay oblivious of what is going on.
One possible usage scenario could look like this, assuming we have modeled some kind of money transfer as a series of steps, each implementing ITask:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public interface ITask { void Execute(); } public class ExecutePayment : ITask { ... } public class ValidateCreditCards : ITask { ... } public class ValidateDebitAccountBalance : ITask { ... } public class ReportWarnings : ITask { ... } public class GenerateReceipt : ITask { ... } |
and then a simplistic executor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class TaskExecutor { readonly IEnumerable<ITask> tasks; public TaskExecutor(IEnumerable<ITask> tasks) { this.tasks = tasks; } public void ExecuteTasks() { foreach(var task in tasks) { task.Execute(); } } } |
Now, carrying out a money transfer would consist of having the executor run through this list of discrete tasks, but it’s probably important that the validation occurs before the actual payment, and it probably doesn’t make sense to generate reports before the payment. Therefore, let’s decorate some of the steps:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class ExecutePayment : ITask { ... } [ExecutesBefore(typeof(ExecutePayment))] public class ValidateCreditCards : ITask { ... } [ExecutesBefore(typeof(ExecutePayment))] public class ValidateDebitAccountBalance : ITask { ... } [ExecutesAfter(typeof(ExecutePayment))] public class ReportWarnings : ITask { ... } [ExecutesAfter(typeof(ExecutePayment))] public class GenerateReceipt : ITask { ... } |
and then make sure we respect them:
1 2 3 4 5 6 7 8 9 10 |
var container = new WindsorContainer(); // allow collections to be resolved container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel)); // this is the new thing! container.Kernel.AddHandlersFilter(new RespectOrderDirectivesHandlersFilter(typeof(ITask))); container.Register(AllTypes.FromThisAssembly().BasedOn<ITask>().WithService.Base(), Component.For<TaskExecutor>()); |
Now, when the container builds a TaskExecutor, it will inject the ITask implementations in the order specified by the attributes. Inside RespectOrderDirectivesHandlersFilter, there’s a HandlerSorter, which is the implementation of the actual sorting algorithm. It builds a directed graph out of components and their dependents, and then it orders the components so that they will be traversed in a way that respects the order specified by the attributes. I’m not an expert on graph algorithms, so I don’t know if my implementation is really really stupid and slow – but I know that a) it works, and b) the ordering is cached, so the algorithm will only run once per set of handlers.
If you want to know more, or perhaps put RespectOrderDirectivesHandlersFilter to use, you can visit the RespectOrderDirectivesHandlersFilter page on GitHub.