Unity IOC使用InjectionConstructor

时间:2018-09-27 08:48:16

标签: c# .net unity-container ioc-container

我想使用Injection构造函数简化类型注册。国际奥委会通常应自行解决类型。有些类型无法自动自行解析,因为它们的构造函数需要一些Named参数。他们需要自定义注册。

例如:

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

类型注册:

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

如您所见,如果您在注册中需要任何命名解析,则需要写下数十个未命名参数或应用程序在运行时崩溃。更糟糕的是,每次更改构造函数参数时,都需要更正注册。在Programm在运行时崩溃之前,您什么都不会注意到。

因此,我正在寻找扩展或一种配置Unity的方法,该方法使我可以跳过默认(无名)参数注册,以减少可能的错误并保持引导程序尽可能小。

修改 很好找到我在寻找什么。 https://outlawtrail.wordpress.com/2012/08/02/fun-with-constructor-arguments-part-1-pick-choose/

智能构造函数似乎是解决方案

2 个答案:

答案 0 :(得分:1)

一种选择是使用InjectionFactory而不是InjectionConstructor

假设此模型:

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
{

}

并假设UsernameConnectionString存储在配置文件中(您未指定这些参数的来源),我将创建一个Bootstrapper类,例如以下:

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);
    }
}

使用代码:

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

答案 1 :(得分:0)

好的,还有更多选项可帮助您改善代码。假设使用以下模型:

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)减少代码中的魔术字符串数量

第一件事是尝试减少代码中魔术字符串的数量,例如"connectionString""userName"

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

因此,与其在整个代码中重复这样的事情……

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

您可以创建自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)
    {
    }
}

为什么要打扰?这样,您不必重复字符串"connectionString""userName",并且如果需要更改它们,您要做的就是编辑相应类中的ParameterName属性。

使用ConnectionStringParameterUsernameParameter注册字符串:

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

使用参数:

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

2)使用扩展方法或工厂删除重复的代码

例如,假设您要注册DataBaseLayer的不同实例。这些实例之间的唯一区别是使用的记录器类型。注册ILogger的实例:

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

使用扩展方法注册DataBaseLayer的实例:

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

扩展RegisterDataBaseLayer的方法:

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)尽可能对参数分组

代替....

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

要做:

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

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

使用IoC容器注册类型时,这将使您的生活更轻松,并减少Bootstrapper代码。

4)为Bootstrapper / IoC容器创建单元测试

这非常重要,可以为您省去一些麻烦。如果您认为代码易碎,并且在/更改类时(例如,更改或添加新的构造函数)可能会崩溃,则应为注册的每种类型和/或命名实例添加一些测试。

您至少应该检查容器是否能够解析特定实例而不会引发异常。