3
votes

I would like to simplify type registration with Injection constructor. IOC should usually resolve types by it self. Some types cannot be resolved by itself automatically ,because their constructor need some Named parameter. They need custom registation.

For example:

public DatabaseLayer(string connectionString, 
                     string userName,
                     ILogger loger, 
                     TypeMapper mapper,
                     ...some other dependencies)

Type registration:

container.RegisterType<DatabaseLayer>(
    new InjectionConstructor(
        new ResolvedParameter<string>("connectionString"),
        new ResolvedParameter<string>("userName"),
        new ResolvedParameter<ILoger>("file")
        typeof(TypeMapper),
        typeof(..),
        ...));

As you can see, if you need any Named resolves in your registration you need write down tens of unnamed parameter or Application crashes at run time. And even worse you need correct your registration every time you change your constructor parameter. You don't notice anything until Programm crashes at run time.

So I am looking for extension or a way to configure Unity which allows me skip default(nameless) parameter registration to reduce possible errors and keep boot strapper as small as possible.

Edit Well found what i am searching for. https://outlawtrail.wordpress.com/2012/08/02/fun-with-constructor-arguments-part-1-pick-choose/

smart constructor seems be the solution

2
"You don't notice anything until Programm crashes at run time" - the solution is simple, just write some unit tests to check if all registered types are resolved correctly :-) - Rui Jarimba

2 Answers

2
votes

One option is to use the InjectionFactory instead of InjectionConstructor.

Assuming this model:

public class DataBaseLayer
{
    private readonly string _connectionString;
    private readonly string _userName;
    private readonly ILogger _logger;
    private readonly ITypeMapper _mapper;

    public DataBaseLayer(
        string connectionString,
        string userName,
        ILogger logger,
        ITypeMapper mapper)
    {
        _connectionString = connectionString;
        _userName = userName;
        _logger = logger;
        _mapper = mapper;
    }
}

public interface ITypeMapper
{
}

public class TypeMapper : ITypeMapper
{
}

public interface ILogger
{

}

public class Logger : ILogger
{

}

And assuming that Username and ConnectionString are stored in the configuration file (you didn't specify where these parameters are coming from), I'd create a Bootstrapper class like the following:

public static class Bootstrapper
{
    // assuming Username is a configuration setting stored in the config file
    private static string Username => ConfigurationManager.AppSettings["username"];

    // assuming ConnectionString is a connection string stored in the config file
    private static string ConnectionString => ConfigurationManager.ConnectionStrings["myConnectionString"].ConnectionString;

    public static IUnityContainer Setup()
    {
        IUnityContainer container = new UnityContainer();
        container.RegisterType<ILogger, Logger>();
        container.RegisterType<ITypeMapper, TypeMapper>();
        container.RegisterType<DataBaseLayer>(new InjectionFactory(CreateDataBaseLayer));

        return container;
    }

    private static DataBaseLayer CreateDataBaseLayer(IUnityContainer container)
    {
        ILogger logger = container.Resolve<ILogger>();
        ITypeMapper mapper = container.Resolve<ITypeMapper>();

        return new DataBaseLayer(ConnectionString, Username, logger, mapper);
    }
}

Using the code:

IUnityContainer container = Bootstrapper.Setup();
DataBaseLayer dbLayer = container.Resolve<DataBaseLayer>(); 
1
votes

Ok, some more options to help you improve your code. Assuming the following model:

public class DataBaseLayer
{
    private readonly string _connectionString;
    private readonly string _userName;
    private readonly ILogger _logger;
    private readonly ITypeMapper _mapper;

    public DataBaseLayer(
        string connectionString,
        string userName,
        ILogger logger,
        ITypeMapper mapper)
    {
        _connectionString = connectionString;
        _userName = userName;
        _logger = logger;
        _mapper = mapper;
    }
}

public interface ITypeMapper
{
}

public class TypeMapper : ITypeMapper
{
}

public interface ILogger
{

}

public class ConsoleLogger : ILogger
{
}

public class FileLogger : ILogger
{

}

1) Reduce the number of magic strings in the code

First thing would be to try to reduce the number of magic strings in your code, such as "connectionString" and "userName":

container.RegisterInstance(typeof(string), "userName", "my username");
container.RegisterInstance(typeof(string), "connectionString", "my connection string");

So, instead of repeating things like this all over the code...

new InjectionConstructor(
    new ResolvedParameter<string>("connectionString"),
    new ResolvedParameter<string>("userName"),
    /* .... */
);

You can create your own parameter classes that derive from ResolveParameter:

public class UsernameParameter : ResolvedParameter<string>
{
    public static UsernameParameter Instance => new UsernameParameter();

    public static string ParameterName => "userName";

    private UsernameParameter() : base(ParameterName)
    {
    }
}

public class ConnectionStringParameter : ResolvedParameter<string>
{
    public static ConnectionStringParameter Instance => new ConnectionStringParameter();

    public static string ParameterName => "connectionString";

    private ConnectionStringParameter() : base(ParameterName)
    {
    }
}

Why bother? This way you don't have to repeat the strings "connectionString" and "userName", and if you need to change them all you have to do is to edit the ParameterName property in the corresponding class.

Registering the strings using ConnectionStringParameter and UsernameParameter:

container.RegisterInstance(typeof(string), UsernameParameter.ParameterName, "my username");
container.RegisterInstance(typeof(string), ConnectionStringParameter.ParameterName, "my connection string");

Using the parameters:

new InjectionConstructor(
    ConnectionStringParameter.Instance,
    UsernameParameter.Instance,
    /* .... */
);

2) Use extension methods or factories to remove code duplication

For example, let's suppose you want to register different instances of DataBaseLayer. The only difference between these instances is the type of logger used. Registering the instances of ILogger:

container.RegisterType<ILogger, FileLogger>("file");
container.RegisterType<ILogger, ConsoleLogger>("console");

Registering the instances of DataBaseLayer using an extension method:

container.RegisterDataBaseLayer(instanceName: "fileDatabaseLayer", loggerType: "file");
container.RegisterDataBaseLayer(instanceName: "consoleDatabaseLayer", loggerType: "console");

Implementation of the RegisterDataBaseLayer extension method:

public static class UnityContainerExtensions
{
    public static IUnityContainer RegisterDataBaseLayer(
        this IUnityContainer container, 
        string instanceName, 
        string loggerType)
    {
        container.RegisterType<DataBaseLayer>(
            instanceName,
            new InjectionConstructor(
                ConnectionStringParameter.Instance,
                UsernameParameter.Instance,
                new ResolvedParameter<ILogger>(loggerType),
                new ResolvedParameter<ITypeMapper>()
            )
        );

        return container;
    }
}

3) Group your parameters if possible

Instead of ....

public class FooService
{
    public FooService(string userName, string password)
    {
        // ...
    }
}

Do:

public class FooService
{
    public FooService(Credentials credentials)
    {
        // ...
    }
}

public class Credentials
{
    public string Username { get; set; }
    public string Password { get; set; }
}

This will make your life easier when registering types using an IoC container and reduce your Bootstrapper code.

4) Create unit tests for your Bootstrapper/IoC container

This is extremely important and can save you some headaches. If you feel your code is fragile and might crash if/when a class is changed (e.g. changing or adding a new constructor), then you should add some tests for every type and/or named instance you register.

You should at least check if the container is able to resolve a particular instance, without throwing exceptions.