One of the features I’m looking forward to in the upcoming Castle Windsor 3.0 (“Wawel”) release is handler filters! I’m especially looking forward to this feature because it solves a problem I have had quite a few times now, and also because it’s a feature that I’ve contributed to the Windsor project.
Please note that handler filter are NOT available before Windsor 3.0, which will probably be out sometime around middle August 2011.
As you can see, handler selectors is a hook in Resolve that sorts out the situation by choosing one handler among all the available handlers. Pretty much related to this, are handler filters, which is a hook in ResolveAll, that sorts out the situation by choosing and ordering several handlers from all the available handlers. Let’s take a look at a tiny example….
Example: Task processing pipeline
A golden use case for this feature is when you rely on CollectionResolver to inject a collection for you, e.g. in chain of responsibility-like scenarios like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class TaskProcessor { readonly IEnumerable<ITask> tasks; readonly IErrorReporter errorReporter; public TaskProcessor(IEnumerable<ITask> tasks, IErrorReporter errorReporter) { this.tasks = tasks; this.errorReporter = errorReporter; } public void ProcessTasks() { foreach(var task in tasks) { try { task.DoIt(); } catch(SomeDomainException ex) { errorReporter.ReportErrorProcessingTask(task, ex); break; } } } } |
Usually, when doing this kind of task processing, you care about the order in which the tasks are processed. Let’s pretend we have some tasks:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class FinishTheJob : ITask { public void DoIt() { Console.WriteLine("Finishing stuff"); } } public class PrepareSomething : ITask { public void DoIt() { Console.WriteLine("Preparing stuff"); } } public class CarryItOut : ITask { public void DoIt() { Console.WriteLine("Carrying out some important logic"); } } |
and the tasks are registered “dynamically” (allowing us to add new tasks just by dropping new implementations of ITask into the project), like so:
1 |
container.Register(AllTypes.FromThisAssembly().BasedOn<ITask>().WithService.Base()); |
Now, how do we make sure that the PrepareSomething and FinishTheJob tasks are run at the right time, i.e. before and after CarryItOut? Handler filters to the rescue!
Let’s register a handler filter like so:
1 |
container.Kernel.AddHandlersFilter(new TaskHandlersFilter()); |
and the filter is implemented like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class TaskHandlersFilter : IHandlersFilter { readonly Dictionary<Type, int> sortOrder = new Dictionary<Type, int> { {typeof (PrepareSomething), 1}, {typeof (CarryItOut), 2}, {typeof (FinishTheJob), 3}, }; public bool HasOpinionAbout(Type service) { return service == typeof(ITask); } public IHandler[] SelectHandlers(Type service, IHandler[] handlers) { // come up with some way of ordering implementations here // (cool solution coming up in the next post... ;)) return handlers .OrderBy(h => sortOrder[h.ComponentModel.Implementation]) .ToArray(); } } |
Now, when my program does this:
1 2 3 4 |
foreach(var task in container.ResolveAll<ITask>()) { task.DoIt(); } |
I get this output:
1 2 3 |
Preparing stuff Carrying out some important logic Finishing stuff |
just as expected, which in turn means that CollectionResolver’s call to ResolveAll will also yield an ordered list of tasks.
One could also imagine that only certain handlers were returned, thus allowing tenants in multi-tenant scenarios to have different task processing pipelines.
Nifty, huh? In the next post, I will show a simple yet sophisticated way of ordering the handlers in the SelectHandlers method of a handler filter.