5

I've done this so many times but I never end up liking how my code turns out. I'm asking if anyone else has the same problems and can offer solutions or conventions that they've used to make cleaner code.

When I build a C# service, I get TopShelf, Log4Net, and Ninject all installed and set up. Inside of my service project, I end up with a class like this. The part I'm most interested in is SuperLargeStaticClassThing.RegisterDependencies(_kernel);

sealed class ServiceMain
{
    [NotNull]
    private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    private IKernel _kernel;

    /// <summary>   Starts the service.</summary>
    public void Start()
    {
        Log.Info("Service Start");

        // Step 1: Register Ninject Dependencies
        _kernel = new StandardKernel();
        SuperLargeStaticClassThing.RegisterDependencies(_kernel);

        // Step 2: Start Service Endpoints
        _kernel.Get<IEndpoint1>().Start();
        // ...
    }

    /// <summary>   Stops the service.</summary>
    public void Stop()
    {
        Log.Info("Service Stop");

        // Stop Service Endpoints
        _kernel.Get<IEndpoint1>().Stop();
        // ...
    }
}

And my SuperLargeStaticClassThing.RegisterDependencies(_kernel); method ends up being huge, ugly, monolithic, you name it. It's hundreds of lines of just binding interfaces to other types. Sometimes you can get away with convention based registration to make it shorter, but I feel like this isn't always a solution either. Not only is the method huge, it seems unwieldy. Sometimes certain dependencies need to be swapped out in chunks, so I'd enjoy seeing the different categories of dependencies registered together. I feel like there has to be a better way, but I'm stuck in finding one that I like.

Does anyone have any suggestions on a pattern to solve the problem of a huge and unwieldy registration method?

8
  • Are you open to using something else for your DI? I can write up a nice answer detailing why MEF, which is already part of .NET, would solve this particular problem.
    – Magus
    May 18, 2016 at 15:11
  • Do you have to have a DI framework? what about regular DI of just passing in dependencies when constructing your components ? May 18, 2016 at 15:19
  • 1
    This is something that has bothered me for some time about DI, and I'm glad you asked a question about it. May 18, 2016 at 15:26
  • @JBRWilkinson To build my components, I almost always use either DI (dependencies are automatically passed into the constructor of the object that depends on them) or in rare cases, _kernel.Get<>() (like in my post). How I build my components in a custom way now is by registering them with Ninject, and using methods like WithConstructorArgument(), OnActivation() and OnDeactivation(). Ninject also handles object lifetimes, which is a huge plus for long running services. The big problem is just that this extra "stuff" is in one hard-to-manage place. May 18, 2016 at 15:26
  • 1
    Well, I've checked with the boss, and the way that we solve this problem here is by binding interfaces through reflection. Each class is decorated with an Attribute that specifies which interface it implements. When the application loads, the DI container reflects over the entire assembly and stores all of the interface/type pairs in a dictionary. When Creator.Make<Iinterface>() is called, it retrieves the first type from the dictionary, walks the constructor tree looking for all the necessary types, instantiates them in order, and automagically wires everything up. May 18, 2016 at 16:10

4 Answers 4

5

so I'd enjoy seeing the different categories of dependencies registered together.

Ninject provides the concept of a module to allow you to group related bindings into a single class.

https://github.com/ninject/Ninject/wiki/Modules-and-the-Kernel

class WeaponsModule : NinjectModule
{
    private readonly bool useMeleeWeapons;
    public WeaponsModule(bool useMeleeWeapons) {
        this.useMeleeWeapons = useMeleeWeapons;
    }

    public void Load()
    {
        if (this.useMeleeWeapons)
            Bind<IWeapon>().To<Sword>();
        else
            Bind<IWeapon>().To<Shuriken>();
    }
}

class Program
{
    public static void Main()
    {
        bool useMeleeWeapons = false;
        IKernel kernel = new StandardKernel(new WeaponsModule(useMeleeWeapons));
        Samurai warrior = kernel.Get<Samurai>();
        warrior.Attack("the evildoers");
    }
}
3
  • That. Exactly that. And you can have as many modules as you want, each responsible for the bindings of a specific area of the code base. May 23, 2016 at 17:37
  • 1
    I'll mark this as the answer, because it's exactly how Ninject was designed to handle this scenario. I would only point out that adding more modules requires a code change in the "bootstrapping" process, where binding which is driven by attributes does not. I think I may create my own attributes for Ninject modules so it automatically scans for modules to get the best of both worlds. May 23, 2016 at 17:53
  • 1
    That's actually a slick idea @JohnCarpenter. Shouldn't take much to write it. I bet you don't even need attributes for the simple case of just loading all the modules. You could just use reflection to find classes that inherit from NinjectModule.
    – RubberDuck
    May 23, 2016 at 18:37
2

The way that we solve this problem where I work is by binding interfaces through reflection.

Each class is decorated with an attribute that specifies which interface contract the class fulfills. When the application loads, our DI container (called a "Creator") reflects over the entire assembly and stores all of these interface/type pairs in a dictionary.

When Creator.Make<Iinterface>() is called, it retrieves the first type from the dictionary, walks the constructor tree looking for all the necessary types, instantiates them in order, and automagically wires everything up. There is a provision for specifying concrete parameters, where they are needed.

All of the implementing classes are marked internal, which "encourages" everyone to use the Make method instead of instantiating the classes directly.

4
  • does the internal access modifier hurt when you're searching for types with Reflection from another assembly? I'm imagining the attributes are searched for and found from the registration method, which may not be in the same project as the types which are being registered. May 18, 2016 at 17:00
  • Not really... Reflection can be used to get around the access modifiers. See stackoverflow.com/a/12317078 May 18, 2016 at 17:04
  • 1
    Why do you need attributes to do that? You can already find out what interfaces a class implements through reflection May 19, 2016 at 9:10
  • @BenAaronson: You can. But some interfaces, like IDisposable, will never be instantiated directly. Also, these particular attributes sometimes include configuration information, like whether you want to run the type as a singleton or transient object. May 19, 2016 at 15:35
2

MEF it.

Built into the .NET framework is an often looked over namespace, created primarily for handling plugin support: System.ComponentModel.Composition - also known as MEF.

MEF can be applied very effectively for DI, possibly providing exactly what you're looking for.

First, you decorate a class with the needed attributes:

[Export] // This attribute specifies that a given class should be created and used in composition.
[PartCreationPolicy(CreationPolicy.NonShared)] // By default, one instance of the class will be shared with all importing classes. This is how you tel it you want something else.
public class Thing
{
  private readonly IDependency yourDependency;

  [ImportingConstructor] // This must be placed on the non-primary constructor you want to use, or the primary constructor will be used.
  public Thing(IDependency theThingYouWantToImport)
  {
    yourDependency = theThingYouWantToImport;
  }
}

[Export(typeof(IDependency))] // This tells MEF that you want your export to fill your chosen interface, rather than its own type.
public class Dependency { }

Somewhere else, at your composition root, you have to build up your container:

// You may also have other catalogs, like DirectoryCatalog, if you want to support plugins. If you only have one, you don't need the aggregate at all.
var assemblyCatalog = new AssemblyCatalog(typeof(App).Assembly);
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(assemblyCatalog);
var container = new CompositionContainer(catalog);
container.ComposeParts();

You may at this point request one of the classes from your fully constructed object graph.

There is a lot more available than just this: MEF supports ExportFactory<T>, as well as calling methods when all imports are fulfilled, and more complicated things like exporting with extra metadata.

Hopefully this is what you need and enough to get started.

1

This is what I've come up with based on the response already from @Robert Harvey which will work for at least 90% of the use cases that I will need. This will declutter the Registration process for sure. The configuration of whether to register a type, and the lifetime of the objects created are controlled from the class/interface files and not the monolithic registration method now.

Idea

Before I show the implementation that I came up with, know that it's small, and there is definitely room for improvement. This is sort of a baseline that I'll be modifying as I go along. The basic idea is that I'll create custom attributes to put on the classes that I want to register with DI. Then through reflection, the registration process searches for these attributes and registers the type based on the information it gets from the attributes on the classes and/or interfaces themselves. This has two big pluses and one downside.

Pluses:

  1. Simple registrations happen in a distributed way. There is no longer any one place where all registration happens so it's easier to manage and easier find what you're looking for
  2. Every time a new dependency is added, you don't have to keep the registration method open. Just add the attribute on the class that you're adding anyways and it's auto"magic"ally registered.

Downside: For more complicated registrations, which may be configuration based, are not handled with the solution that I've given. For these solutions I may be adding more as time goes on, but in the meantime I think this is a good starting point.

Code

I changed the name of SuperLargeStaticClassThing to SmallerStaticClassThing from the OP. Even though I'm using Ninject here, the features I'm using are available in all of the DI packages that I've used.

static class SmallerStaticClassThing
{
    internal static void RegisterDependencies(IKernel kernel)
    {
        var concreteTypesToRegister = Assembly.GetExecutingAssembly().GetTypes()
            .Where(x => x.IsClass && 
                       !x.IsAbstract &&  
                        x.GetCustomAttributes(typeof (RegisterAttribute)).Any());
        foreach (var concrete in concreteTypesToRegister)
        {
            var interfacesToRegister = concrete.GetInterfaces()
                .Where(x => !x.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
            foreach (var @interface in interfacesToRegister)
            {
                var binding = kernel.Bind(@interface).To(concrete);
                if (concrete.GetCustomAttributes(
                    typeof (SingletonLifetimeAttribute)).Any())
                {
                    binding.InSingletonScope();
                }
                else if (concrete.GetCustomAttributes(
                    typeof (TransientLifetimeAttribute)).Any())
                {
                    binding.InTransientScope();
                }
                else if (concrete.GetCustomAttributes(
                    typeof(ThreadLifetimeAttribute)).Any())
                {
                    binding.InThreadScope();
                }
                else
                {
                    // default scope... nothing needed for Ninject
                }
            }
        }

        // ... other stuff if you need
    }
}

This code requires some custom attributes that I made quickly. They all basically look like this, with the summary/class name different.

/// <summary>
/// Put this Attrubute on your class if you want the type to be instantiated
/// with a singleton lifetime management strategy
/// </summary>
[AttributeUsage(validOn: AttributeTargets.Class | AttributeTargets.Interface)]
public class SingletonLifetimeAttribute : Attribute
{
}

A quick example of a class which I'd like to register with whatever service it happens to be included in is as follows.

[Register, SingletonLifetime]
class HelpfulDependencyImplementation : IHelpfulDependeny
{
    // ... insert implementation here
}

This is going to work for me, I think, but time will tell. I'm sure small tweaks are going to happen over time to allow for more extensive DI configuration using attributes.

Feedback is appreciated.

2
  • I wish I had caught you earlier. I've posted my answer, but what you've implemented is essentially exactly what MEF offers.
    – Magus
    May 20, 2016 at 19:24
  • I personally have register every single type to its implementations + itself, and then only reregister the types which I know are multiple(e.g. TestSomething and RealSomething) But wouldn't recommend this with Ninject, it's just too slow! Seen it take 20 seconds at start time! May 20, 2016 at 20:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.