One of the coolest new features of ASP.NET 5 is dependency injection baked right into the framework as a first class citizen. Dependency injection allows us to create reusable components and services that we register during application startup.
Later when other components (like Controllers, View Components, and even our own classes) are created by ASP.NET 5, they will have access to our custom dependencies – the reusable components and services we registered earlier.
Registering Dependencies
The default ASP.NET 5 web application project already makes use of this mechanism. The ConfigureServices
method of Startup.cs
registers Entity Framework, Identity Services, and MVC components (using custom extension methods), as well as a couple of custom components – IEmailSender
and ISmsSender
.
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddEntityFramework() .AddSqlServer() .AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); // Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>(); }
The call to services.AddTransient
might not be super clear if you haven’t played with ASP.NET 5 dependency injection yet. Here’s a quick description of the various dependency injection flavors that are available out-of-the-box:
- Transient – a new instance of this dependency is created each and every time it is requested.
- Singleton – a single instance of this dependency is created and reused for every request.
- Instance – like singleton, but we have to create the first instance ourselves, and ASP.NET 5 will reuse that particular instance.
- Scoped – hybrid between transient and singleton: a single instance is created and reused for the lifetime of an individual request. Each request has their own instance. For a bit of a deeper dive into scoped dependencies, take a look at my post on that here.
Consuming Dependencies
Now, we can access these components from elsewhere in the application. Lets look at the AccountController
constructor from the default application template to see this in action:
public AccountController( UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IEmailSender emailSender, ISmsSender smsSender, ILoggerFactory loggerFactory) { // ... }
That’s all there is to it – ASP.NET 5 will automatically resolve and inject the appropriate dependencies when constructing the AccountController
via this constructor.
The only part that we have to remember to do is register each dependency into the IServiceCollection
during the ConfigureServices
call in Startup.cs
. If we forget to register the dependency, ASP.NET 5 will fail when trying to create a component that requires the missing dependency.
Dependency Scanning
At my current work we are building web services using Java + Spring on AWS. One feature of the Spring Framework that I’ve grown quite fond of is their annotation-based (C#: attribute-based) dependency injection. In essence, I’m able to apply an annotation (attribute) to a class, and the Spring framework will register it into the dependency container for me automatically.
This can be quite handy – I get to declare that a given class should be available as a dependency right on the class, rather than separately in some configuration method. Another reason why I like this is I can pull in a new library into my project, and I can gain access to all its components with one line (or no lines) of code.
Custom Solution for ASP.NET 5
As far as I can tell, ASP.NET 5 doesn’t provide this functionality. So I figured why not build something myself for fun. This post will go over the main parts of that implementation, and I’ll go into a bit more detail in follow-up posts.
I should note, I’m not entirely sure I’ve done this the best way possible. It seems to work well, but if you’ve got ideas on how to make it better, I gladly welcome it. Once its a bit more polished, I’ll make it available as a NuGet package if there’s interest. Without further delay, lets dig into the code.
Dependency Attributes
First, I needed to create some attributes that I could decorate classes with. Here’s the base attribute they’ll all inherit from:
using System; using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace DotNetLiberty.DependencyInjection { [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public abstract class DependencyAttribute : Attribute { public ServiceLifetime DependencyType { get; set; } public Type ServiceType { get; set; } protected DependencyAttribute(ServiceLifetime dependencyType) { DependencyType = dependencyType; } public ServiceDescriptor BuildServiceDescriptor(TypeInfo type) { var serviceType = ServiceType ?? type.AsType(); return new ServiceDescriptor(serviceType, type.AsType(), DependencyType); } } }
The DependencyType
property reuses the ServiceLifetime enumeration from the .NET dependency injection framework – it allows me to define whether a dependency is Singleton
, Transient
, or Scoped
.
Next, I want to be able to optionally define the type to register the dependency as. For example, if I had a class Foo : IFoo
, I could choose to register it only as an IFoo
. A component requesting justFoo
in this case would fail.
Furthermore, I could decorate a single class with two or more interfaces – in this case I may want to register both interfaces into the service container (which is why the AttributeUsage
attribute is setting the AllowMultiple
property to true
).
Finally, I’ve added a small helper method BuildServiceDescriptor
that will generate a ServiceDescriptor – the ASP.NET 5 representation of a dependency that can be registered into the IServiceCollection
(rather than using the AddTransient
, AddSingleton
, and AddScoped
extension methods).
Here’s what the rest of the attributes look like:
public class ScopedDependencyAttribute : DependencyAttribute { public ScopedDependencyAttribute() : base(ServiceLifetime.Scoped) { } } public class SingletonDependencyAttribute : DependencyAttribute { public SingletonDependencyAttribute() : base(ServiceLifetime.Singleton) { } } public class TransientDependencyAttribute : DependencyAttribute { public TransientDependencyAttribute() : base(ServiceLifetime.Transient) { } }
At this point, I should be able to decorate my various components with these attributes. For example:
[SingletonDependency] public class Foo { } [TransientDependency(ServiceType = typeof(IBar))] public class Bar : IBar { } [ScopedDependency] public class Baz { }
Dependency Scanner
Now I need to write a bit of code to scan through an assembly and detect classes that are decorated with these attributes. For that I created a new class called Scanner
:
using System; using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace DotNetLiberty.DependencyInjection { public class Scanner { internal Func<AssemblyName, IAssemblyTypeAccessor> AssemblyLoader = (name) => new AssemblyTypeAccessor(Assembly.Load(name)); public void RegisterAssembly(IServiceCollection services, AssemblyName assemblyName) { var assembly = AssemblyLoader(assemblyName); foreach (var type in assembly.DefinedTypes)) { var dependencyAttributes = type.GetCustomAttributes<DependencyAttribute>(); // Each dependency can be registered as various types foreach (var dependencyAttribute in dependencyAttributes) { var serviceDescriptor = dependencyAttribute.BuildServiceDescriptor(type); services.Add(serviceDescriptor); } } } } }
You can probably tell whats happening here, but I’ll explain it anyway.
First, I’m iterating through all the types defined in the assembly. It might look a bit weird to be using a Func
that returns an IAssemblyTypeAccessor
to load the assembly.
The reason I’m doing this is that it makes unit testing this a whole lot easier (in my unit tests, I swap out AssemblyLoader
with a fake that just returns a custom list of types – thereby avoiding actually having to load an assembly).
Next, I’m just checking to see if there are any attributes on the type that inherit from DependencyAttribute
. If so, I use the attribute’s BuildServiceDescriptor
method to generate a service descriptor that I pass into the IServiceCollection
add method.
That’s all the Scanner
needs to do (for now – I’ll be expanding upon it in the next post).
Extension Methods
All that is left is to use this scanner from my ASP.NET 5 application. For that, I added some extension methods:
using System; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.PlatformAbstractions; namespace DotNetLiberty.DependencyInjection { public static class IServiceCollectionExtensions { public static IServiceCollection AddDependencyScanning(this IServiceCollection services) { services.AddSingleton<Scanner>(); return services; } public static IServiceCollection ScanFromSelf(this IServiceCollection services) { var appEnv = services.BuildServiceProvider().GetService<IApplicationEnvironment>(); services.ScanFromAssembly(new AssemblyName(appEnv.ApplicationName)); return services; } public static IServiceCollection ScanFromAssembly(this IServiceCollection services, AssemblyName assemblyName) { var scanner = services.GetScanner(); scanner.RegisterAssembly(services, assemblyName); return services; } private static Scanner GetScanner(this IServiceCollection services) { var scanner = services.BuildServiceProvider().GetService<Scanner>(); if (null == scanner) { throw new InvalidOperationException( "Unable to resolve scanner. Did you forget to call services.AddDependencyScanning?"); } return scanner; } } }
The first method – AddDependencyScanning
doesn’t do much. It simply adds the Scanner
to the service collection. Below, rather than creating a new Scanner
when I need one, I’ll resolve it directly from the service collection. Right now Scanner
doesn’t require any dependencies, but when it does in the future, all I’ll have to do is declare the dependency as a constructor parameter.
The next method – ScanFromSelf
grabs the application name from the IApplicationEnvironment
service (via the service container), and calls into the ScanFromAssembly
method. Then, the ScanFromAssembly
method just has to resolve the Scanner
and call RegisterAssembly
.
Decorate Dependencies with Attributes
Now that I’ve got the necessary extension methods in place, I can update first update the AuthMessageSender
class (located in Services/MessageServices.cs
..?) with my new dependency attributes:
[TransientDependency(ServiceType = typeof(IEmailSender))] [TransientDependency(ServiceType = typeof(ISmsSender))] public class AuthMessageSender : IEmailSender, ISmsSender { public Task SendEmailAsync(string email, string subject, string message) { // Plug in your email service here to send an email. return Task.FromResult(0); } public Task SendSmsAsync(string number, string message) { // Plug in your SMS service here to send a text message. return Task.FromResult(0); } }
Configure Dependency Scanning
And make the corresponding update to ConfigureServices
:
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddEntityFramework() .AddSqlServer() .AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); // Add application services. //services.AddTransient<IEmailSender, AuthMessageSender>(); //services.AddTransient<ISmsSender, AuthMessageSender>(); services.AddDependencyScanning() .ScanFromSelf(); }
Now, when my application starts up, it will automatically find the AuthMessageSender
class and register it as both an IEmailSender
and ISmsSender
in the IServiceCollection
. If I add more services later on, as long as I’ve decorated them with the appropriate dependency attributes, they’ll automatically be added as well.
I can even use the ScanFromAssembly
extension method to add dependencies from other assemblies referenced by my project (provided I know the assembly name ahead of time).
In the next post, I’ll show you how I expanded upon the scanner to be able to scan for dependencies from all assemblies that are referenced in the project – I can simply add a reference in project.json
and I’m off to the races.