Quantcast
Channel: asp.net 5 rc1 – .NET Liberty
Viewing all articles
Browse latest Browse all 13

Dependency Scanning in ASP.NET 5

$
0
0

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.

armen-shimoon-dep-scan-1-ma

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.

 

 


Viewing all articles
Browse latest Browse all 13

Latest Images

Trending Articles





Latest Images