Register boolean in Container

1.1k Views Asked by At

I'm in the process of rewriting my company's DAO library. Because of our solution's characteristic, we need to switch ADO libraries (Oracle/SQL Server) based on customer. Since static references are the main source of our issues (f.e.Oracle package taken from CI instead of SQL Server) I decided to go with plugin architecture, and I try to dynamically load needed dlls.

I'm new to Simple Injector (I was using Ninject, but in this case we need something that is really fast). I used the https://simpleinjector.readthedocs.org/en/latest/advanced.html#registering-plugins-dynamically article and managed to load correct dlls into Domain.

I'm currently facing a weird (in my opinion) error during Container Verification (container.Verify()):

The constructor of type GenericDAO contains the parameter of type Boolean with name 'isInUserContext' that is not registered. Please ensure Boolean is registered in the container, or change the constructor of GenericDAO.

My constructor looks like that:

public class GenericDAO : DBHelper
{

    public GenericDAO(Boolean isInUserContext, string connectionString, string providerName, UniversalDataAccess universalDataAccess)
        : base(isInUserContext, connectionString, providerName, universalDataAccess)
    {
    }

My registration:

var pluginDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");

var pluginAssemblies =
    from file in new DirectoryInfo(pluginDirectory).GetFiles()
    where file.Extension.ToLower() == ".dll"
    select Assembly.LoadFile(file.FullName);

var pluginTypes =
    from assembly in pluginAssemblies
    from type in assembly.GetExportedTypes()
    where typeof (IDBHelper).IsAssignableFrom(type)
    where !type.IsAbstract
    where !type.IsGenericTypeDefinition
    select type;

_kernel.RegisterAll<IDBHelper>(pluginTypes); 

Do you know how to successfully initialize all needed types? I would like to expose the public property of type GenericDAO.

1

There are 1 best solutions below

1
On BEST ANSWER

The exception message is a bit misleading, since Simple Injector disallows you from registering primitive types (such as Boolean) in the container, so in fact the container is advising you something that is impossible to do.

The reason that Simple Injector disallows you from registering primitives such as int, bool, string is that those types are ambiguous. DI containers in .NET inject services based on type information, but it is very unlikely that all components in your code base that need a string in their constructor, actually need the exact same value. Some expect a connection string, others expect some file path. But if you would be able to register string in the container, you would only be able to specify one value (the connection string for instance) making it impossible to inject another value (the file path for instance).

This problem however isn't specific to Simple Injector, this is a generic problems. Other DI containers however, might allow you to resolve components that take primitive values in the constructor, and might fill in this value with some default value. This would hardly be useful, because if you only need the default value of that primitive type, why would you expose this type through the constructor anyway?

It's difficult to say what you should do to solve this. Generally spoken, if a type needs some configuration values, you need to tell your container explicitly how this type should be resolved. With Simple Injector this would look as follows:

container.Register<GenericDAO>(() => new GenericDAO(
    isInUserContext: true, 
    connectionString: "con str", 
    providerName: "System.Sql.Client",
    universalDataAccess: container.GetInstance<UniversalDataAccess>()));

However, if you have multiple (or many) components that require those same configuration values, this usually means that you are missing an abstraction. For instance, the isInUserContext, is probably a value that should be hidden behind an IUserContext abstraction and the connectionString and providerName smell like an IDbConnectionFactory abstraction or something similar. In that latter case the GenericDAO class will move the responsibility in creating connections into a different class allowing it to be focussed on what ever responsibility it has.

In that case the GenericDAO's constructor will look as follows:

public GenericDAO(IUserContext userContext, IDbConnectionFactory connectionFactory,
    UniversalDataAccess universalDataAccess)

Now because the constructor has no primitive arguments anymore, the class can now be auto-wired by your container. In Simple Injector you can now register it as follows:

container.Register<GenericDAO>();

The advantage becomes especially clear in your case, since you are batch-registering the GenericDAO as plugin. All batch-registered types must be auto-wireable to be able to succeed.

Of course you'll have to register the IUserContext and IDbConnectionFactory as well, and they might still depend on some primitive values:

container.RegisterSingle<IUserContext>(new AspNetUserContext());
container.RegisterSingle<IDbConnectionFactory>(
    new SqlServerDbConnectionFactory("con str"));

The IUserContext and IDbConnectionFactory abstractions need to be defined in some central assembly that both the bootstrapper and the plugin assemblies refer to.

Another option is to let the plugin assemblies do the registration of the plugins themselves. Downside of this that those plugin assemblies need to take a dependency on your DI container. Simple Injector contains a SimpleInjector.Packaging NuGet project that allows you do create a 'package' in your plugin assembly. For instance:

public class Plugin1Bootstrapper : IPackage
{
    public void RegisterServices(Container container) {
        container.Register<GenericDAO>(() => new GenericDAO(
            isInUserContext: true, 
            connectionString: "con str", 
            providerName: "System.Sql.Client",
            universalDataAccess: container.GetInstance<UniversalDataAccess>()));

        // other registrations here.
    }
}

In the startup path of your application, you can call:

container.RegisterPackages(pluginAssemblies);

This will load all IPackage implementations for the supplied assemblies and will call RegisterServices on each of them.

Note that we register the GenericDAO here again by its concrete type. You can mix this with the RegisterAll<IDBHelper>(...) that you are already using. A RegisterAll registration will call back into the container for each element in the collection, so by registering the concrete type with Register<GenericDAO> allows to specify explicitly how Simple Injector must resolve that concrete type. Otherwise Simple Injector will try to auto-wire that concrete type and resolve it as transient.