Adding Dependency Injection to ASPNET 5 Console Applications
As it stands in the RC1-Update1 release of ASPNET 5 and DNX, (soon™ to become AspNetCore and the dotnet CLI respectively), you are not limited to only creating Web Applications; you can also create Class Libraries and Console Applications. Having been on the proverbial bandwagon for quite some time now, I have assisted in getting several ASPNET5 web applications into production, and dozens of class libraries. I recently came across the requirement to introduce a command line interface (CLI) to run some background jobs for one of our sites. In this post I’ll explain what I did to not only produce a professional looking CLI, but also to utilise our existing class libraries through dependency injection.
Brief Background
For those of you that have been following vNext progress for some time, you may remember a time when you could create a constructor in your console app’s Program.cs
, and the DNX bootstrapper would perform some amount of dependency injection for you. This was later removed in favour of some static properties, such as PlatformServices.Default.Runtime
.
All of the existing commands available for ASPNET 5 (dnu
, dnx
, dnvm
) are of course CLI’s created by the ASPNET team. My goal here is to provide a mechanism to achieve a dependency injected CLI with as little code as possible, so you’ll find I’ve stolen as much of the Microsoft practices (and code…) as possible to make life easier.
Class Libraries
Since discovering the Microsoft Dependency Injection packages, I have followed the practice of defining my class libraries with Service Extensions. For anyone that has worked with ASPNET5, you’ll recognise the following from your Startup
class:
1
2
3
4
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
That AddMvc
method simply adds dependency injection bindings to the IServiceCollection
, and is a really useful pattern to follow for your class libraries. To create such a method, in your class library, add Microsoft.Extensions.DependencyInjection.Abstractions
to your project.json
, and then in a class called ServiceExtensions
, add something like the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public static class ServiceExtensions
{
public static IServiceCollection AddMyServices(this IServiceCollection services)
{
services.TryAddTransient<MySelfBoundClass>();
services.TryAddTransient<IAnimal, Dog>();
// etc
return services;
}
}
Class Libraries written this way can not only be easily incorporated into ASPNET5 websites, but if you follow the advice below, into Console Applications also.
Whether you use
TryAdd*
orAdd*
is up to you, and I’ll assume you know (or can google) the difference betweenTransient
,Singleton
, etc.
Getting Started
If you take a look at the DNX CLI, specifically the Program
class, you’ll find (in my opinion) very easy to understand and follow code. As will become increasingly common with AspNetCore, your Program.cs
is responsible for setting up a bootstrapper, which in turn executes your application. In this case, the bootstrapper is the CommandLineApplication
class.
There are some very obvious methods available on this class (such as Options
, Command
, Argument
, etc), which combined with the example of the DNX application itself, makes it very easy to reuse. The CommandLineApplication
class is defined in a NuGet package, which scanning the project.json
is called Microsoft.Extensions.CommandLineUtils.Sources
and is a build dependency. However, a quick scan of NuGet.org reveals that the package has not been published to the official NuGet feeds.
No problem though, the packages for RC1U1 can all be found on one of the ASPNET Team’s MyGet Feeds. You could add this feed to your global NuGet sources using the command line but I don’t recommend it. Instead, I opted to add a NuGet.config
file to my project, and appended the feed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageRestore>
<add key="enabled" value="True" />
<add key="automatic" value="True" />
</packageRestore>
<activePackageSource>
<add key="aspnetrc1" value="https://www.myget.org/F/aspnetmaster/api/v3/index.json" />
</activePackageSource>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="nuget.org" value="https://www.nuget.org/api/v2/" />
<add key="aspnetrc1" value="https://www.myget.org/F/aspnetmaster/api/v3/index.json" />
</packageSources>
</configuration>
Once the source has been added (and in most cases, close/open the solution too), you will be able to add Microsoft.Extensions.CommandLineUtils.Sources
to the project.json
for your Console Application. Don’t forget to specify it as a build dependency!
1
2
3
4
5
6
"dependencies": {
"Microsoft.Extensions.CommandLineUtils.Sources": {
"version": "1.0.0-rc1-final",
"type": "build"
}
}
Dependency Injection
So we’ve stolen got a nice Command Line parser from Microsoft, but where’s the DI? I expect there are some really good reasons for there not being DI out the box, or in fact used at all in the Microsoft console applications. However, whilst I understand why you might use statics in this case, I couldn’t bring myself to do it. Especially given that all of our internal class libraries are typically an internal sealed class
implementing a public
interface. I could of course have picked an easy alternative such as code sharing or using InternalsVisibleToAttribute
, but where’s the fun in that!
The first question to answer is, “Where to inject?”. This will help us later when we ask “Where to configure services?”.
Where to inject?
Looking through the existing CommandLineApplication
code there are a few candidates. We could set up our bootstrapper to inject at Command
level, which currently takes an Action<CommandLineApplication>
parameter. However, doing so would require a lot more than extending the code we’ve imported.
Therefore, the logical choice becomes the OnExecute
method. It’s worth pointing out that the structure nests CommandLineApplication
s when you add a command, whereby the top level bootstrapper contains a CommandLineApplication
for each command.
Hopefully this quick diagram gives you an idea of how the pieces fit together:
1
2
3
4
5
6
7
├── CommandLineApplication (Top level bootstrapper)
│ ├── OnExecute (this is the delegate to execute if no command is passed; typically set up to Show Help)
│ ├── Commands
│ │ ├── CommandLineApplication (Command A)
│ │ │ ├── OnExecute (this is the delegate to execute if a matching command is passed)
│ │ ├── CommandLineApplication (Command B)
│ │ │ ├── OnExecute (etc..)
NB: Each CommandLineApplication
can have it’s own options, too.
Now we know at what level we want to inject a service, we can look at where would be a logical place to configure services.
Where to configure services?
Again, there are a couple candidates to pick from here. The pattern we’ve grown used to from using ASPNET5 Web Applications, is to add our services into the bootstrapper, which is achieved in web applications by said bootstrapper calling the ConfigureServices
method we mentioned above.
It’s time to start extending the imported CommandLineApplication
. The class is internal
, but because it’s packaged as a build time source package, that’s fine; it’s considered internal
to our application too. We can’t edit the imported code directly, but we can inherit from the class. I chose to create a class that is also called CommandLineApplication
which may add to confusion, so you can call it something else if you’re struggling with the ambiguous naming.
In order to add services to our bootstrapper, we can add a couple simple members to our inherited CommandLineApplication
(note, you’ll need to add Microsoft.Extensions.DependencyInjection
to your project.json
dependencies):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Microsoft.Extensions.DependencyInjection;
namespace Devbot
{
internal class CommandLineApplication : Microsoft.Dnx.Runtime.Common.CommandLine.CommandLineApplication
{
private static IServiceCollection ApplicationServices { get; set; }
public void UseServices(Action<IServiceCollection> configureServices)
{
var services = new ServiceCollection();
configureServices(services);
ApplicationServices = services;
}
}
}
Nice and simple, we allow application services to be added to the bootstrapper. We do jump through a bit of a hoop here with regards to accepting an Action<IServiceCollection>
; whilst there are easier ways to do this, I wanted the consuming code to remain consistent with code you’d find in a ConfigureServices
method.
Assuming you follow the aforementioned advice regarding Service Extensions in your class libraries, you can now add your class library services to your console application in the Program
class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Program
{
public static int Main(string[] args)
{
// grab environment data so we can output env info later
var env = PlatformServices.Default.Runtime;
// create the bootstrapper
var app = new CommandLineApplication(); // note, this is our inherited class
app.Name = "example";
app.FullName = "Example Application demonstrating Dependency Injection";
// add some options to make your CLI look good!
var optionVerbose = app.Option("-v|--verbose", "Show verbose output", CommandOptionType.NoValue);
app.HelpOption("-?|-h|--help");
app.VersionOption("--version", () => env.GetShortVersion(), () => env.GetFullVersion());
// configure services
app.UseServices(services => services
.AddMyServices() // <-- add your services here
);
}
}
Great, so now we can configure our services. Next, we need to inject them.
How to inject services?
This is only slightly more difficult, and only made difficult because I decided to inject at Option
level. The first step is to provide a new overload of the OnExecute
method:
1
2
3
4
5
6
7
8
9
10
public void OnExecute<TService>(Func<TService, Task<int>> invoke)
{
Invoke = () =>
{
if (ApplicationServices == null)
throw new InvalidOperationException("No services have been configured.");
var provider = ApplicationServices.BuildServiceProvider();
return invoke(provider.GetRequiredService<Service>()).Result;
};
}
NB: The imported code provides overloads for both int
, and Task<int>
. Feel free to add the int
variant of the above as a reader’s exercise.
With this last piece of the puzzle in place, you’re all set. To utilise the dependency injection, you can create your command:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
internal static class NoiseCommand
{
public static void UseNoiseCommand(this CommandLineApplication app)
{
app.Command("speak",
c =>
{
c.Description = "Makes your IAnimal make a noise (different noise for different animal?)";
c.OnExecute(async (IAnimal animal) => // <- this is where your service gets injected
{
await animal.SpeakAsync();
return 0;
});
},
throwOnUnexpectedArg: true
);
}
}
… and the add it to your Program
class …
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Program
{
public static int Main(string[] args)
{
// grab environment data so we can output env info later
var env = PlatformServices.Default.Runtime;
// create the bootstrapper
var app = new CommandLineApplication(); // note, this is our inherited class
app.Name = "example";
app.FullName = "Example Application demonstrating Dependency Injection";
// add some options to make your CLI look good!
var optionVerbose = app.Option("-v|--verbose", "Show verbose output", CommandOptionType.NoValue);
app.HelpOption("-?|-h|--help");
app.VersionOption("--version", () => env.GetShortVersion(), () => env.GetFullVersion());
// setup a default execution for when no commands passed
app.OnExecute(() =>
{
app.ShowHelp();
return 2;
});
// configure services
app.UseServices(services => services
.AddMyServices() // <-- add your services here
);
app.UseNoiseCommand(); // <-- register your command here
app.Execute();
}
}
Summary
Looking back at this post, it’s awfully long-winded! Though, coming up with a means to inject services and implementing the above only actually took 20-30 minutes. I’m keen to get some feedback on:
- Why you shouldn’t use DI in your console app?
- What would be a better/easier/cleaner way to achieve DI in your console app?
- Would it be easier to provide a sample repository / gist containing all of the code used?
It’s possibly worth noting that I took matters a step further and use the tertiary provider concept from Compose so that the application services shown above, are augmented/overridden by services registered at command level. Whilst there were reasons I chose to do this, it’s not necessarily I practice I’d recommend as it probably implies some deeper problems. Let me know if you want to see the code for the tertiary provider mechanism.