In my development team, we're using the CQRS pattern as described in this article and using the recommended DI Container: Simple Injector (pun intended).
This is our current project structure:
references => [02] [03]
Project 01 is the client (an ASP.NET MVC 4 application) where we register our app's services with Simple Injector (Bootstrapper
). Project 03 is where all the Command and Query handlers are defined as described in the article. Project 02 is where the Commands and Queries are defined. In order for Simple Injector to register the handlers, the client has a direct reference to the business layer assembly.
The container registers the commandhandlers like this
container.Register(typeof(ICommandHandler<>), assemblies);
Now the problem is, is that one of our developers accidentally forgot to declare one of his handler classes as public
. So instead of:
public class AddCustomerCommandHandler : ICommandHandler<AddCustomerCommand> { ... }
he wrote:
class AddCustomerCommandHandler : ICommandHandler<AddCustomerCommand> { ... }
Now according to msdn, if the access modifier is omitted, internal is used:
Classes and structs that are declared directly within a namespace (in other words, that are not nested within other classes or structs) can be either public or internal. Internal is the default if no access modifier is specified.
Now internal is defined as:
The type or member can be accessed by any code in the same assembly, but not from another assembly.
After trying to execute this command, the runtime doesn't throw any error and the command is happily executed. I'm surprised by this, because I would expect a verification error from Simple Injector, which it doesn't. Also when trying to directly instantiate the handler object from within the client, the compiler is giving me an error that it cannot access an internal class here
, as intended! So why is Simple Injector able to register this commandhandler while it's access modifier is internal
?
In previous versions, Simple Injector's batch registration skipped internal types by default, and a flag allowed you to register internal types as well.
This method has proven to be problematic, because in some applications, the missing types allowed the application to keep running, while showing incorrect behavior (thus failing silently).
To prevent this, we changed this behavior and now you'll always see such type be registered as well. The idea is that silently skipping an expected type is much worse than anything else. Since most applications run in full trust, internal types can be constructed and resolved, so from Simple Injector's perspective, it's fine for a type to be internal. For other application types, a call to Verify will quickly detect an unconstructable type.
Also from an application perspective, it should be no problem for the type to be internal, since the consumers talk to the public interface that this type implements.
The reason this fails in your case is most likely because you dispatch your handlers and use dynamic
typing during dispatching. What you are seeing is IMO a quirk in the C# dynamic infrastructure. C# tries to find your Handle
method using reflection, but the Handle method is internal, because its surrounding type is internal. It can't find this method even though the type implements a public interface that contains that method. This is a quirk and I believe C# should still find that method; but it doesn't. That's why your code is failing.
You can do several things to prevent having such problem in the future. You can for instance define a unit test that checks whether all handlers are public. Or you can define a special generic -and public- wrapper class with a handler as constructor argument and resolve that wrapper instead of the handler. You can then use dynamic typing on that wrapper. Or you register an outermost decorator that you make sure is public. C# reflects the outer most type so this will work.
You can also integrate the check in Simple Injector where you register the outermost decorator conditionally, and let the predicate throw an exception in case the handler is internal.