简单注入器-用参数注册每个类型

本文关键字:类型 注册 参数 注入器 简单 | 更新日期: 2023-09-27 18:12:04

我一直在使用Autofac作为DI容器,它很好,但是它有点太慢了。

所以我决定把我的项目迁移到Simple Injector,因为我看过一些基准测试,它是最快的一个。

它的API在我看来缺乏一点,但我相信有一些变通办法可以解决我的问题。

我正在扫描两个程序集以获得它们的类型,因为我想以自动的方式注册它们:

var repositories = (from type in typeof(DataRepository).Assembly.GetExportedTypes()
                    where type.Name.EndsWith("Repository")
                    select type).OrderBy(t => t.Name).ToList();
var repositioriesImp = (from type in typeof(SqlDataRepository).Assembly.GetExportedTypes()
                    where type.Name.EndsWith("Repository")
                    select type).OrderBy(t => t.Name).ToList();

所以我决定把它们的类型保存在字典中

var dictionary = repositories.Zip(repositioriesImp, (r, rImp) => new { r, rImp })
            .ToDictionary(x => x.r, x => x.rImp);

在单个foreach循环中检索它们

foreach(var d in dictionary)
{
    container.register(d.Key,d.Value,Lifestyle.Transient);
}

有一个问题: d值类型需要一个形参

我知道我可以手工注册一个接一个,就像

container.Register<TService>(() => new TImplementation(connString));

但是使用DI容器的意义是什么,如果我必须做同样的事情,我将做与纯DI?

编辑

作为参考,这是autoface的方法

container.RegisterAssemblyTypes(typeof(SqlDataRepository).Assembly)
    .Where(t => t.Name.StartsWith("Sql"))
    .As(t => t.BaseType)
    .InstancePerLifetimeScope()
    .WithParameter("connectionString", connectionString);

简单注入器-用参数注册每个类型

有几种方法可以解决这个问题,但我更倾向于回到问题的根源。你的问题的原因是缺少抽象。

虽然在服务中注入配置值(如连接字符串)是可以的(而另一方面注入运行时数据是不好的做法),但当你开始向多个服务注入相同的配置值时,你需要退后一步,检查应用程序的设计。

乍一看,一个存储库需要一个连接字符串,因为它连接到数据库,但是如果你仔细观察所有的存储库实现,你可能会发现在创建和打开连接时有很多重复的代码。这违反了DRY(可能还违反了OCP)。

解决方案是将创建和打开连接的逻辑提取到自己的服务中,并将其隐藏在抽象之后。一个很好的例子是使用CreateConnection()方法来实现IConnectionFactory抽象。

这样,您可以将connectionString配置值隐藏在SqlConnectionFactory实现后面,并且存储库将忽略此连接字符串。这减少了代码重复,使你的代码更容易阅读,你的应用程序更容易维护。

作为一个额外的好处,您获得了一个更容易维护的组合根和一个更容易配置的DI容器。这是因为您的存储库服务现在依赖于非常明确的IConnectionFactory,而不是依赖于模糊的String类型。

当然,你现在把问题转移到你的SqlConnectionFactory,但现在这将是唯一的服务,依赖于该连接字符串,它可以注册如下:

container.RegisterSingleton<IConnectionFactory>(
   new SqlConnectionFactory(connectionString));

请注意,开箱即用的简单注入器不支持WithParameter之类的东西。正如您可能已经猜到的那样,这种支持的缺乏是明确的,因为我们喜欢激励开发人员来修复他们设计中的缺陷。不过,Simple Injector包含了允许您构建它的扩展点,比如使用IConstructorInjectionBehavior接口。本文给出了使用IConstructorInjectionBehavior可以做什么的详细示例。

顺便说一句,你的配置看起来很脆弱。在我看来,仅仅使用存储库实现进行注册要容易得多,也安全得多:

foreach (Type impl in repositioriesImp) {
    container.Register(impl.GetInterfaces().Single(), impl);
}

顺便说一句,另一种使批处理注册更容易的方法是为存储库定义一个通用的IRepository<T>抽象。这样,在Simple Injector中注册它们只需一行代码:

/// v3 syntax
container.Register(typeof(IRepository<>), new [] {
typeof(SqlDataRepository).Assembly });