✅ How to Get DI Services in a Console Application
After some reading of various sources, mainly the official MS docs, I have my console app set up like this and it all appears to be working fine:
My big question is how do I access a service provider to get a service in other classes in the app? Do I have to have a "root service" singleton that all the other services are injected into, then access that from everywhere else, or, as I hope, is the there a better way to get a service required for a task from other classes in the app?
24 Replies
@VoidPointer you really should be starting everything from the DI root.
Do you mean something like this?
then provide access to the other services via
RootService
?No.
That’s service locator, don’t do that.
I think there might be a misunderstanding in the fundamentals of DI here; you define dependencies of classes in the class itself, usually via constructor arguments. The composition root, aka the app, then constructs your classes with their dependencies injected. Crucially, this is not something you usually do manually.
Read more here:
https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
Dependency injection - .NET
Learn how to use dependency injection within your .NET apps. Discover how to registration services, define service lifetimes, and express dependencies in C#.
Specifically, the mechanism you should use here is auto wiring, where the DI container wires up all the dependencies to create your service.
In a console application, that object graph root (so called as it is the top level object created by the DI container) is usually some implementation of
IHostedService
and family.
A use pattern can be found by creating a new project with the dotnet Worker Service
template.
Note specifically how the user code,
Worker.cs
does not access IServiceProvider
directly, instead it relies on auto wiring to inject its dependencies.
Also note how the Worker
service is not explicitly instantiated by user code; instead the app itself manages its lifetime.Thank you, but I fully understand this, and have been using DI in ASP.NET for ages. I've even played with some quite advanced stuff there, but I've always just been used to getting a service injected into a controller when ASP.NET instantiates the controller, or using
Now in a console app I don't have either of these, so it seems to me I either have to register every class with a dependency on a service, even if I don't see it as a service, as a service as well, also with
[FromServices]
on a request parameter in minimal APIs.Now in a console app I don't have either of these, so it seems to me I either have to register every class with a dependency on a service, even if I don't see it as a service, as a service as well, also with
builder.Services.AddTransient
, and only ever get instances of these from a service provider, only in Main
. Is that what @jcotton42 means in his answer above by "doing everything from the DI root"?I'm not sure I understand the question then.
Yes, you have to register all dependencies of your object graph root to the service collection if you want to resolve that root from it.
The term 'Service' does not restrict the kinds of dependencies you may register. Any reference type can be registered to the container.
You only need to access the service provider directly to instantiate the root object, not its dependencies.
I think I understand you, yet I asked about that above (here
RootService
and RootObject
are the same thing), with:
Do you mean something like this?
then provide access to the other services via RootService
and @jcotton42 said not to do that, that's the service locator anti-pattern. Maybe I had the bit about providing access to other services via RootService
phrased wrong, but what I meant was use the other services somewhere in an object graph with RootService
as its root. I did not mean create RootService
in order to provide access to other services. That would clearly be a service locator.
So we end up with a sort of layered structure, with objects further from the Main
method depending on those closer to it, which eventually depend on services/objects instantiated by the service provider inside the Main
method.
There is thus no way to instantiate an object that depends on injected services without doing it through a service provider inside the Main
method. Unless we do something daft like expose a ServiceProvider
property on the Program
class, but that would also just be another service locator.Ah I think I get your meaning now; are you trying to do something like
maybe?
In that case, injecting
Foo
and Bar
via constructor injection into RootService
and then accessing them would be fine imo.
Are we converging on your intended meaning and question?
Sorry if I'm being dense.We are definitely converging, by accessing
Foo
and Bar
from Main
, because that is the only place I can get a reference to them, through RootService
. My original question started out being, how to I get a reference to RootService
, or any other service outside of Main
?
Although now I'm starting to see a design where I don't have to. I can parse args
and determine which command to execute, and get the required service and pass it to the command handler, or even register one big umbrella command handler as transient and for each command invocation, i.e. each time the console app is run, I use something like this in Main
:
Is this code snippet the whole purpose of the application? Or is there more code to execute after determining the result?
That is basically the whole purpose of the application. The
ExecuteCommand
method will do all the work, such as call a method on an injected DbService
and process the result of that. The result in the var result = svc.ExecuteCommand(args)
call in Main
may just be written to console to report the result of the command.I see. In that case, I think the worker template may yet be the kind of application template you could use. With it, you can at least avoid using the service provider directly:
That's looking good, thanks. I'm currently busy looking at how Spectre.Cli, System.CommandLine, and Cocona integrate with .NET DI, because I'm going to probably end up using one of them instead of manual command parsing.
Configuration providers - .NET
Discover how to configure .NET apps using the configuration provider API and the available configuration providers.
You can parse commandline args via configuration providers and then bind an options object against them
Options pattern - .NET
Learn the options pattern to represent groups of related settings in .NET apps. The options pattern uses classes to provide strongly-typed access to settings.
Thanks man. I've explored configuration providers in some depth, but also only in ASP.NET projects. I've just recently been wondering how to bind command line args to an IOptions class for my CLI app.
By default in some host builders, they are included in its configuration.
I've decided I'm going to use Cocona for this app. It handles it's own IServiceCollection, but it has the neatest out of the three console libraries I've tried. Thanks for all the input.
You're welcome, I hope some of it helped.
$close
If you have no further questions, please use /close to mark the forum thread as answered