Search code examples
c#dependency-injectionsimple-injector

Remove registered decorator in Simple Injector


Okay, I know this sounds like a weird request but here is my problem. I want to write some integration tests for my WCF service; I have a few key paths that I want to ensure behave properly. One test is to make sure that the correct exceptions are thrown in key places and that they propagate up the pipeline correctly without being intercepted in the wrong place.

So to do this I am overriding an existing registration with a mock object that will throw the exception I want to test for at the location I want it thrown. That part works fine.

Next, I want to resolve my command handler (the system under test), invoke the handle method, and assert that the correct exception happens.

The issue is that when I resolve my command handler I actually get back a loooong chain of decorators with my command handler all the way at the bottom. At the very top of this chain sits a decorator that is my global exception handler. It is this exception handling decorator at the top that I need to unregister because it prevents me from being able to assert that the exception was thrown. My container bootstrapper is quite complex so I have absolutely no desire to recreate a copy of it in my test project minus this one decorator.

If it were just a standard registration I could simply override the registration with a mock exception handler that rethrows the exception. As far as I can tell, though, it does not seem to be possible to override a decorator's registration. I would prefer not to go that route, anyway. It just over complicates the test with an additional mock. It would be much better if I could just unregister the decorator.

If it is not possible to unregister a decorator what would be my next best solution? Add option flags to my bootstrapper to enable/disable certain registrations?

Thanks.


Solution

  • It's impossible to remove a registration in Simple Injector. You can replace an existing registration, but that method does not work when dealing with decorators. Decorators are added in Simple Injector internally by adding a delegate to the ExpressionBuilt event. Since the registered delegate is stored nowhere, it is currently technically impossible to 'deregister' a decorator registration.

    The way around this is to simply not register that decorator at all. This might sound silly, but this is a practice I use all the time, even with other containers.

    What you can do for instance is to extract the common part of your registrations to a separate method, let's call it BuildUp. This method lacks the registrations that differ from the different applications that use it. In your case you have at least 2 'applications'; the real application and the integration test project. Both projects can call the BuildUp and add extra registrations before or after calling BuildUp. For instance:

    var container = new Container();
    container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
    
    container.RegisterDecorator(typeof(ICommandHandler<>),
        typeof(InnerMostCommandHandlerDecorator<>));
    
    CompositionRoot.BuildUp(container);
    
    container.RegisterDecorator(typeof(ICommandHandler<>),
        typeof(OuterMostCommandHandlerDecorator<>));
    

    This method seems to work very well in your case, since you want to add an 'outer most' decorator. Besides, letting the BuildUp leave 'holes' in your registration makes it often very easy to see when some application forgets to fill in the blanks, since your can let Simple Injector fail fast by calling container.Verify().

    Another common way us to pass a configuration object to the BuildUp method. This configuration object can contain the necessary information for making the right set of registrations as the caller requires. For instance, such configuration object can have a simple boolean flag:

    public static void Build(Container container, ApplicationConfig config) {
    
        // Lot's of registrations
    
        if (config.AddGlobalExceptionHandler) {
            container.RegisterDecorator(typeof(ICommandHandler<>),
                typeof(GlobalExceptionHandlerCommandHandlerDecorator<>));
        }
    }
    

    Passing on a configuration object onto the BuildUp method is also a great way to decouple your BuildUp method from the configuration system. This allows you to more easily call BuildUp during integration testing, without being forced to have a copy of the complete configuration file in the test project.

    Instead of using a flag property, you can also have the complete list of decorators inside the configuration object and let the BuildUp method iterate over it and register them. This allows the caller to remove or add decorators from the list before they are registered:

    var config = new ApplicationConfig();
    
    // Remove decorator
    config.CommandHandlerDecorators.Remove(
        typeof(AuthorizationCommandHandlerDecorator<>));
    
    // Add decorator after another decorator
    config.CommandHandlerDecorators.Insert(
        index: 1 + config.CommandHandlerDecorators.IndexOf(
            typeof(TransactionCommandHandlerDecorator<>)),
        item: typeof(DeadlockRetryCommandHandlerDecorator<>));
    
    // Add an outer most decorator
    config.CommandHandlerDecorators.Add(
        typeof(TestPerformanceProfilingCommandHandlerDecorator<>));
    
    CompositionRoot.BuildUp(container, config);
    
    
    public static void BuildUp(Container container, ApplicationConfig config) {
    
        // Lot's of registrations here.
    
        config.CommandHandlerDecorators.ForEach(type =>
            container.RegisterDecorator(typeof(ICommandHandler<>), type));
    }
    

    I've used all three methods in the past very successfully. Which option to choice depends on your needs.