DI is great, but managing the registry of dependencies might be not. With project growing it becomes harder and harder to maintain DI registry.
Maintaining the registry manually is not just a pain in the ass:
- it’s a continuous time waste
- it’s a popular source of bugs
- it’s a great demotivator to create new small classes, but use the “I’ll just stick it here” approach instead
The module approach — in any form, whether it’s framework-specific modules like Autofac.Module, either using static methods like Microsoft’s extension approach (eg. AddLogging, AddMvc), doesn’t solve the problem. It’s an attempt to hide the problem, by giving an illusionary structure, at the same time all the problems remain, In addition to that we are getting logical conflicts with shared components. In the end, the only benefit of “modularity” — is that it helps to build a bigger heap of code that requires continuous manual care.
Don’t get me wrong here. There is nothing wrong with the modular approach for distributing cross-cutting components, like the same logging or MVC mentioned above. I’m talking specifically about services within a single application.
There is another popular approach — “conventional”. In case you are not familiar with it — it means scanning an assembly using reflection and programmatically registering services based on naming conventions.
That solves the manual part - that’s true, however:
it’s not flexible enough.
Yes, typically it does cover, a significant percent of registrations, but it leaves out lots of details, and as know “the devil hides in details”
the enforced naming pattern harms semantics, or will if it doesn’t seem like that in the beginning
the convention’s still something that must be continuously manually taken care of
This is an approach I’ve come up with several years ago and it has proven itself on a variety of projects.
Finally, I’ve packed it and published it on Github and NuGet.
It has quite a minimalistic API, but sufficient to solve all the problems mentioned above.
It is designed for
IServiceCollection so every modern IoC framework supports it.
It is based on assembly scanning but uses attributes not naming conventions and code to handle special cases.
In a typical project, the attribute handles most of the registrations. On a class level, you define how it should be registered. It is required to specify a lifetime. And optionally you can specify as what type the class should be registered, defaults to itself if not specified.
examples of Service attribute usage:
So far we have seen the static part, but there is always a set of services that require custom resolvers,
or even access to configuration or some context data.
Therefore there is a ConfigureServices attribute.
The attributes invoke a specified static method
IServiceCollection instance to the method as a parameter.
Looks for a method named “ConfigureServices” if not other name is specified.
an example of ConfigureServices attribute usage:
ConfigureServices attribute could as well be used without Service attribute. It can be applied to any class, the only requirements are:
- that method (referred by that attribute) must be static and without overloads.
- objects specified as parameters to be passed to a scanning context. (see Setup and configuration right below)
First, install NuGet package
Importing ServiceAnnotations namespace will add an extensions method
AddAnnotatedServices to a
IServiceCollection interface. Which has two parameters, both are optional.
An assembly to scan. Defaults to calling assembly, so typically don’t need to be specified.
Action to configure scan context, where we can pass objects that would be available as parameters for ConfigureServices attribute referred methods. Like in the example above where were using
IConfigurationas a parameter to get a
You don’t need to explicitly add
IServiceCollection, it will be available by default
I hope it will help you to keep your code cleaners.
Don’t hesitate to open issues and contribute on Github.