Simple Injector - Register foreach type with param

2019-02-15 16:31发布

I've been using Autofac as DI Container and it's great, but it's a bit too slow.

So I've decided to migrate my project to Simple Injector, because I've seen some benchmark tests and it's one of the fastest.

Its API however lack a bit in my opinion, but I'm sure there are some workaround to solve my problem.

I'm scanning two assemblies to get their types because I want to register them in an automated way:

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

So I've decided to save their types in a dictionary

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

to retrieve them in a single foreach loop

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

there is one problem tho: the d.Value types need a parameter.

I know I could register them by hand one by one like

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

but what's the point of using a DI Container if I have to do the same I would do with pure DI?

EDIT

for the reference, here's the Autofac way to do that

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

1条回答
对你真心纯属浪费
2楼-- · 2019-02-15 16:56

There are several ways to address this issue, but my preference is to go back to the source of your problems. The cause of your problems is a missing abstraction.

Although it is fine to inject configuration values such as connection strings in services (while on the other hand injecting runtime data is bad practice, the moment to start injecting the same configuration value into multiple services is the moment you need to do a step back and check the application design.

At first sight it might look obvious that a repository needs a connection string, because it connects to the database, but if you look closely at all your repository implementations, you will probably find lots of duplicate code in creating and opening the connections. This is a violation of DRY (and possibly OCP).

The solution would be to extract the logic for creating and opening connections into its own service and hide it behind an abstraction. A good example of this is having an IConnectionFactory abstraction with a CreateConnection() method.

This way you can hide the connectionString configuration value behind the SqlConnectionFactory implementation and the repositories will be oblivious to this connection string. This reduces code duplication and makes your code easier to read and your application easier to maintain.

As an extra benefit you get a composition root that is much easier to maintain and a DI container that is much easier to configure. This happens because your repository services now depend on a very clear IConnectionFactory instead of depending on the ambiguous String type.

Of course you now move the problem to your SqlConnectionFactory, but that will now be the only service that depends on that connection string and it can be registered as follows:

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

Do note that out-of-the-box, Simple Injector has no support for things as WithParameter. As you might already guessed, this lack of support is explicit, since we like to motivate developers to fix flaws in their design instead. Still, Simple Injector contains extension points that allow you to build this, such as using the IConstructorInjectionBehavior interface. This article gives a detailed example of what you can do with IConstructorInjectionBehavior.

Btw, your configuration seems very fragile. Seems to me that it's much easier and safer to just use the repository implementations for the registration:

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

Btw, another way to make batch registration easier is to define a single generic IRepository<T> abstraction for repositories. This way, registering them is a one-liner in Simple Injector:

/// v3 syntax
container.Register(typeof(IRepository<>), new [] {
typeof(SqlDataRepository).Assembly });
查看更多
登录 后发表回答