HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

IoC Registration: Convention over Configuration

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
iocconventionregistrationconfigurationover

Problem

The rubberduck project has reached a turning point. The core features are implemented (except the SmartIndenter embedding - we're keeping that for 2.0), the next few releases will probably just build on the architecture in place.

One of the biggest architectural issues, is tight coupling, with dependencies being new'd up just about anywhere they're needed. Sometimes dependencies of dependencies are being constructor-injected... it's time for a major cleanup/refactoring.

The solution is proper Inversion of Control and Dependency Injection - and Rubberduck's dependency graph is getting quite extensive, so we're going to be using an IoC container. I picked ninject because I love its neat API, and how its lets you register the types by convention rather than by configuration.

Here's Rubberduck's entry point class, where the composition root lives. There used to be a private field there, private App _app; - it was removed because, well, everything works without it. On the plus side, I like that all the class cares about is the IKernel. On the other hand, I can't help thinking something isn't right - if all I need to do for everything to work, is to instantiate the App class, doesn't it mean I have a constructor doing way too much work?

```
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Extensibility;
using Microsoft.Vbe.Interop;
using Ninject;
using Ninject.Extensions.Factory;
using Rubberduck.Root;
using Rubberduck.UI;

namespace Rubberduck
{
[ComVisible(true)]
[Guid(ClassId)]
[ProgId(ProgId)]
[EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once InconsistentNaming // note: underscore prefix hides class from COM API
public class _Extension : IDTExtensibility2
{
private const string ClassId = "8D052AD8-BBD2-4C59-8DEC-F697CA1F8A66";
private const string ProgId = "Rubberduck.Extension";

private readonly IKernel _kernel = new

Solution

When you have very "generic" convention, such as these, you need to make sure there's is a way to create a non-convention binding in case you need one.

There are several ways to achieve this:

  • provide a way to exclude a type from the convention (for example, you could add a [DoNotBindByConvention] attribute to these types. Not very neat but works.)



  • you could include the type in the convention, create a binding, and then replace it by using Rebind.



  • requires a strict sequence of creating the bindings. First the convention, then all other bindings.



  • if you have a "multi binding", i.e. something you want to inject as T[], List,..., or by calling IResolutionRoot.GetAll(), rebind doesn't work.



Generally, such generic conventions are somewhat brittle / dangerous. As your example shows you already have to exclude some types (factories...) from some convention. There's potential that the number of these special cases increases and thus conventions become very complicated. Maybe not for you, in case you're writing these, but how will go you about communicating these to your fellow developers? That's usually an important reason not to use such broad conventions.

Hints

Some containers - like Ninject and AutoFac - also provide a "Module" concept. A "Module" can be used to group contain bindings /-conventions specific to an area. One can load specific modules or modules of a whole assembly.
More on Ninject Modules

Context

StackExchange Code Review Q#98146, answer score: 6

Revisions (0)

No revisions yet.