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);
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 aCreateConnection()
method.This way you can hide the
connectionString
configuration value behind theSqlConnectionFactory
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 ambiguousString
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: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 theIConstructorInjectionBehavior
interface. This article gives a detailed example of what you can do withIConstructorInjectionBehavior
.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:
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: