Creating a console app with Dependency Injection in .NET Core
Intro
Dependency injection is baked in the ASP.Net Core projects (yes, I still call it Core), but it's missing from console app templates. And while it is easy to add, it's not that clear cut on how to do it. I present here three ways to do it:
- The fast one: use the Worker Service template and tweak it to act like a console application
- The simple one: use the Console Application template and add dependency injection to it
- The hybrid: use the Console Application template and use the same system as in the Worker Service template
Tweak the Worker Service template
It makes sense that if you want a console application you would select the Console Application template when creating a new project, but as mentioned above, it's just the default template, as old as console apps are. Yet there is another default template, called Worker Service, which almost does the same thing, only it has all the dependency injection goodness baked in, just like an ASP.Net Core Web App template.
So start your Visual Studio, add a new project and choose Worker Service:
It will create a project containing a Program.cs, a Worker.cs and an appsettings.json file. Program.cs holds the setup and Worker.cs holds the code to be executed.
Worker.cs has an ExecuteAsync method that logs some stuff every second, but even if we remove the while loop and add our own code, the application doesn't stop. This might be a good thing, as sometimes we just want stuff to work until we press Ctrl-C, but it's not a console app per se.
In order to transform it into something that works just like a console application you need to follow these steps:
- inject an IHost instance into your worker
- specifically instruct the host to stop whenever your code has finished
So, you go from:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
to:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IHost _host;
public Worker(ILogger<Worker> logger, IHost host)
{
_logger = logger;
_host = host;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Console.WriteLine("Hello world!");
_host.StopAsync();
}
}
Note that I did not "await" the StopAsync method because I don't actually need to. You are telling the host to stop and it will do it whenever it will see fit.
If we look into the Program.cs code we will see this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
I don't know why they bothered with creating a new method and then writing it as an expression body, but that's the template. You see that there is a lambda adding dependencies (by default just the Worker class), but everything starts with Host.CreateDefaultBuilder. In .NET source code, this leads to HostingHostBuilderExtensions.ConfigureDefaults, which adds a lot of stuff:
- environment variables to config
- command line arguments to config (via CommandLineConfigurationSource)
- support for appsettings.json configuration
- logging based on operating system
That is why, if you want these things by default, your best bet is to tweak the Worker Service template
Add Dependency Injection to an existing console application
Some people want to have total control over what their code is doing. Or maybe you already have a console application doing stuff and you just want to add Dependency Injection. In that case, these are the steps you want to follow:
- create a ServiceCollection instance (needs a dependency to Microsoft.Extensions.DependencyInjection)
- register all dependencies you want to use to it
- create a starting class that will execute stuff (just like Worker above)
- register starting class in the service collection
- build a service provider from the collection
- get an instance of the starting class and execute its one method
Here is an example:
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
ConfigureServices(services);
services
.AddSingleton<Executor,Executor>()
.BuildServiceProvider()
.GetService<Executor>()
.Execute();
}
private static void ConfigureServices(IServiceCollection services)
{
services
.AddSingleton<ITest, Test>();
}
}
public class Executor
{
private readonly ITest _test;
public Executor(ITest test)
{
_test = test;
}
public void Execute()
{
_test.DoSomething();
}
}
The only reason we register the Executor class is in order to get an instance of it later, complete with constructor injection support. You can even make Execute an async method, so you can get full async/await support. Of course, for this example appsettings.json configuration or logging won't work until you add them.
Mix them up
Of course, one could try to get the best of both worlds. This would work kind of like this:
- use Host.CreateDefaultBuilder() anyway (needs a dependency to Microsoft.Extensions.Hosting), but in a normal console app
- use the resulting service provider to instantiate a starting class
- start it
Here is an example:
class Program
{
static void Main(string[] args)
{
Host.CreateDefaultBuilder()
.ConfigureServices(ConfigureServices)
.ConfigureServices(services => services.AddSingleton<Executor>())
.Build()
.Services
.GetService<Executor>()
.Execute();
}
private static void ConfigureServices(HostBuilderContext hostContext, IServiceCollection services)
{
services.AddSingleton<ITest,Test>();
}
}
The Executor class would be just like in the section above, but now you get all the default logging and configuration options from the Worker Service section.
Conclusion
What the quickest and best solution is depends on your situation. One could just as well start with a Worker Service template, then tweak it to never Run the builder and instead configure it as above. One can create a Startup class, complete with Configure and ConfigureServices as in an ASP.Net Core Web App template or even start with such a template, then tweak it to work as a console app/web hybrid. In .NET Core everything is a console app anyway, it's just depends on which packages you load and how you configure them.
Comments
Why not just wrap Environment.GetCommanLineArgs() in something that implements an interface? Anyway, there are a lot of third party packages to parse command line and provide it to the user.
SideriteI love it :-) Just one addition: in a command line app, you typically need access to command line arguments, here's one possible implementation: 1.) Add this class: using System.Collections; using System.Collections.Generic; namespace MyNamespace { // Provides access to command line arguments in a dependency-injected environment. public class CommandlineArguments : IEnumerable<string>, IEnumerable { public string[] Args { get; set; } public CommandlineArguments(string[] args) { Args = args; } // Implement behavior typically used on the args array: public string this[int i] => Args[i]; public int Length => Args.Length; public IEnumerator<string> GetEnumerator() { foreach(string arg in Args) yield return arg; } IEnumerator IEnumerable.GetEnumerator() { foreach (string arg in Args) yield return arg; } } } 2.) Extend the startup code like this: Host.CreateDefaultBuilder() .ConfigureServices(ConfigureServices) .ConfigureServices(services => services // Create an register the object representing args .AddSingleton(new CommandlineArguments(args)) .AddSingleton<Executor>()) .Build() .Services .GetService<Executor>() .Execute(); 3.) Implement the Executor.Exect() method something like this: using System; namespace MyNamespace { public class Executor { private readonly CommandlineArguments Args; // Use DI to inject the object with command line args. public ProgramWithDI(CommandlineArguments args) { Args = args; } public void Execute() { Write("You provided these arguments:"); foreach (string arg in Args) Write(arg); } private static void Write(string text) => Console.WriteLine(text); } }
balintnThanks Siderite - I had actually done just that since posting the 2nd message, but didn't want to just spam your comments. :)
Nick ScotneyYou need to understand how dependency injection is designed to work. You first create a list of service registrations (interface to object), then you build the service provider (which, er... provides services), then you instantiate one class (and just one) using it, from which all the other classes will be instantiated. In the blog post example, that's the Executor. You don't need to see or use the service provider or the services collection in your code for anything else. In the example, ITest is given as a parameter of the constructor of Executor, therefore it is automatically resolved by the provider along with any dependencies Test has and its constructor parameters and so on. It's a dependency tree. Now, there are cases where you would want to get the provider for something custom, although it's best to avoid this if possible, for readability reasons as well as for avoiding the "service locator antipattern", which is a pointy subject people still fight over (google it). But if you want an instance of the service provider all you have to do is add a constructor parameter to your class of the type IServiceProvider and you will have access to it. Another scenario, even rarer, is you want to add more dependencies after you've created the service provider. There are several solutions for this. The sloppy one is just keep an instance of the service collection (or inject it, why not? if you register is as a singleton instance for a specific interface) and then add more services to it and generate a new service provider. The problem there is that you already have a service provider and it will remain in memory and classes who have already been instantiated will not use the new one. Another one, slightly better but still not something I would recommend, is what I wrote here: https://siderite.dev/blog/a-net-core-serviceprovider-that-allows.html I hope this helps you understand how to use dependency injection.
SideriteYour example requires minimal changes. Just assign the service provider to a variable and with it get the service.
SideriteOk, literally as soon as I'd typed that message, I tried something else and it works. Would you be able to let me know if this is good or bad practise at all? First I changed my service declaration to this: services .AddSingleton<ITestClass, TestClass>(); And then where I want to use the method, I use the following: var result = services .BuildServiceProvider() .GetService<ITestClass>() .OutputValues(); Now, as a follow up question, am I likely to run into issues if I use "BuildServiceProvider" multiple times through the application? Thanks in advance for any help you're able to provide
Nick ScotneyHey Siderite, So I just wanted to say that your example makes sense, in that I have an existing console application, and following the method laid out, I was able to successfully pass options loaded from my appsettings.json, to the receiving class via DI. However, I do have a question about how you could use this in the console app, but outside of where we build services. The reason I'm looking to add DI to a console app is for the purpose of testing a class library, with multiple controller type classes. So once I've added my options and Singleton's to my service collection, how would I call the method from somewhere else in the main routine, as if I try to use "services.GetService" outside of the initially build, the method isn't accessible (I'm guessing because GetService is a method of the result type of BuildServiceProvider? And if I try to create new instances of my class and do it that way (which I believe completely goes against the DI pattern [using new() I mean]), then it moans that I need to pass in my config model, which I've already added to the service So if that doesn't make sense, I want to achieve this (if possible): // Add Class to DI services .AddSingleton<ITestClass, TestClass>() .BuildServiceProvider(); // Now call a method from the Class services .GetService<ITestClass>() .OutputValues(); Now it's 100% possible that because I'm new to DI, I may mis-understand how it's meant to work, but any help on this would be gratefully appreciated.
Nick ScotneyThe whole point of the post is to enable dependency injection, so that would be the mechanism to instantiate a class like Executor. You can see it done in the Main method of the console application: 1. create a ServicesCollection 2. configure whatever dependencies we have (like Test being the implementation of ITest) 3. add to the collection the Executor class (as its own implementation) 3. create the ServiceProvider from the collection (that's the function of the collection) 4. instantiate Executor (using dependency injection, so no constructors) 5. execute the Executor
SideriteHow would you run Execute() method of Executer class in your program? Don&#39;t we need to first instantiate Executer? By doing that, we would need to pass it a constructor argument, don&#39;t we? What constructor argument can we actually pass it? If someone could kindly provide example with code, would be much appreciated. Many thanks! Astig
AstigI&#39;ve been working on the idea of console application startup with DI, logging etc without need to be dependent on Asp.Net package. I came up with this https://github.com/Jandini/Janda.Go The template is available only for latest .NET6 and new top-level templates console projects but I will add VS2019 switch soon. Without extras the Main code can look like ``` new ServiceCollection() .AddTransient&lt;IMain, Main&gt;() .AddLogging(builder =&gt; builder.AddConsole()) .BuildServiceProvider(); .GetRequiredService&lt;IMain&gt;() .Run(); ```
Matt JandaI don&#39;t think I understand. The last part of the post, where you get an Executor via DI, is working as with a controller. You just have the tree of dependencies coming up to your Executor class. Or do you want to encapsulate dependency injection and setup in a component to be used by everything? I am afraid you can&#39;t do that, because it would have to have dependencies on all the interfaces and implementations. What am I missing?
SideriteMost examples I find about DI do dependency injection at the root level. That is (in the example of a console application) in the main.cs where a ServiceProvider is set up and built and used to get started doing the work. I have tried to make a similar app of my own and it works. So far so good. But what I&amp;#39;m looking for is not to do DI at root level but somewhere deeper in one or the other component. And of course, this component used the DI as setup in my main.cs. What I basically want to do is the same as mvc where a controller takes benefit of injected services, but translated to a console application.
Pascal Declercq