Dependency injection is one of the most prevalent patterns in .NET. Prior to .NET Core, the community developed several frameworks with varying features and coding patterns. Since then, Microsoft.Extensions.DependencyInjection
has emerged as the default framework.
Unlike its predecessors, Microsoft.Extensions.DependencyInjection
does not rely on custom attributes on fields and properties. Instead, it requires you to add the dependencies to the class constructor and store them as fields. This requirement can lead to some boilerplate code, especially if there are numerous dependencies in a complex class hierarchy. Moreover, it can be tedious to migrate your code from an attribute-based framework to a constructor-based one.
To alleviate these minor inconveniences, you can employ the [Dependency] aspect of Metalama.Extensions.DependencyInjection
.
The advantages of this aspect include:
- Reduction of boilerplate code,
- Simplified migration from attribute-based frameworks to constructor-based ones,
- Compatibility with multiple dependency injection frameworks (see Injecting dependencies into aspects).
The [Dependency] aspect provides two properties:
- IsLazy generates code that resolves the dependency lazily, upon the first access.
- IsRequired determines whether the code can execute if the property is missing. If you are using nullable reference types, the
IsRequired
parameter is inferred from the nullability of the field or property.
Example: Injecting Dependencies
The following example demonstrates the code generation pattern for three types of dependency: required, optional, and lazy.
1using Metalama.Extensions.DependencyInjection;
2using Microsoft.Extensions.Hosting;
3using Microsoft.Extensions.Logging;
4
5namespace Doc.DependencyInjectionAspect;
6
7public class DependencyInjectionAspect
8{
9 // This dependency will be optional because the field is nullable.
10 [Dependency]
11 private ILogger? _logger;
12
13 // This dependency will be required because the field is non-nullable.
14 [Dependency]
15 private IHostEnvironment _environment;
16
17 // This dependency will be retrieved on demand.
18 [Dependency( IsLazy = true )]
19 private IHostApplicationLifetime _lifetime;
20
21 public void DoWork()
22 {
23 this._logger?.LogDebug( "Doing some work." );
24
25 if ( !this._environment.IsProduction() )
26 {
27 this._lifetime.StopApplication();
28 }
29 }
30}
1using System;
2using Metalama.Extensions.DependencyInjection;
3using Microsoft.Extensions.Hosting;
4using Microsoft.Extensions.Logging;
5
6namespace Doc.DependencyInjectionAspect;
7
8public class DependencyInjectionAspect
9{
10 // This dependency will be optional because the field is nullable.
11 [Dependency]
12 private ILogger? _logger;
13
14 // This dependency will be required because the field is non-nullable.
15 [Dependency]
16 private IHostEnvironment _environment;
17
18 [Dependency(IsLazy = true)]
19 private IHostApplicationLifetime _lifetime
20 {
21 get
22 {
23 return _lifetimeCache ??= _lifetimeFunc.Invoke();
24 }
25
26 set
27 {
28 throw new NotSupportedException("Cannot set '_lifetime' because of the dependency aspect.");
29 }
30 }
31
32 public void DoWork()
33 {
34 this._logger?.LogDebug("Doing some work.");
35
36 if (!this._environment.IsProduction())
37 {
38 this._lifetime.StopApplication();
39 }
40 }
41
42 private IHostApplicationLifetime? _lifetimeCache;
43 private Func<IHostApplicationLifetime> _lifetimeFunc;
44
45 public DependencyInjectionAspect(ILogger<DependencyInjectionAspect> logger = null, IHostEnvironment? environment = null, Func<IHostApplicationLifetime>? lifetime = null)
46 {
47 this._logger = logger; this._environment = environment ?? throw new System.ArgumentNullException(nameof(environment)); this._lifetimeFunc = lifetime ?? throw new System.ArgumentNullException(nameof(lifetime));
48 }
49}