Dependency Injection in .NET Core Console Applications
We take a look at how to implement dependency injection in smaller .NET Core apps, while also addressing the challenges of doing this in larger, real-world applications.
Join the DZone community and get the full member experience.
Join For FreeASP.NET Core uses a built-in dependency injection mechanism provided by Microsoft. This blog post introduces how to use the same mechanism in .NET Core console applications. For those who like other DI/IoC frameworks, this article provides a demo about how to use Autofac with .NET Core framework-level dependency injection.
Framework-Level Dependency Injection in ASP.NET Core
I don't describe here details of dependency injection in ASP.NET Core. Those who want to find out more about it can skim through these writings:
- Dependency injection in ASP.NET Core
- Using dependency injection with view components
- Injecting services to ASP.NET Core controller actions
- ASP.NET Core: Using view injection
- ASP.NET Core: Using third-party DI/IoC containers
Dependency Injection in .NET Core Console Applications
We can also use the same dependency injection mechanism in .NET Core command line applications. Although we don't have anything prepared for us, it's very easy to put up something similar to web applications. Actually, I prefer to use a more lightweight approach for console applications.
Before writing any code we need a NuGet package for dependency injection components:
Microsoft.Extensions.DependencyInjection
At a very basic level, we can create a dependency injections service provider using the following code:
static void Main(string[] args)
{
var collection = new ServiceCollection();
collection.AddScoped<IDemoService, DemoService>();
// ...
// Add other services
// ...
var serviceProvider = collection.BuildServiceProvider();
var service = serviceProvider.GetService<IDemoService>();
service.DoSomething();
serviceProvider.Dispose();
}
Calling Dispose()
on the service provider is mandatory as, otherwise, the registered instances will not get disposed. Keep this in mind.
Preparing for Disposables and Third-Party Dependency Injection Frameworks
The solution above works but it has one problem - it doesn't consider the possible use of third-party service providers. As the ServiceProvider
class is sealed and third-parties cannot extend it they must use IServiceProvider interface.
public interface IServiceProvider
{
object GetService(Type serviceType);
}
Convenience methods like GetService<T>() are defined in the ServiceProviderServiceExtensions
class.
public static class ServiceProviderServiceExtensions
{
public static IServiceScope CreateScope(this IServiceProvider provider);
public static object GetRequiredService(this IServiceProvider provider, Type serviceType);
public static T GetRequiredService<T>(this IServiceProvider provider);
public static T GetService<T>(this IServiceProvider provider);
public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
}
IServiceProvider doesn't use IDisposable and therefore it doesn't have the Dispose()
method. Still, the classes that extend this interface may also extend IDisposable but we don't know it. To consider this possibility, we have to implement the disposing of the service provider in a little different way.
static void Main(string[] args)
{
var collection = new ServiceCollection();
collection.AddScoped<IDemoService, DemoService>();
// ...
// Add other services
// ...
IServiceProvider serviceProvider = collection.BuildServiceProvider();
var service = serviceProvider.GetService<IDemoService>();
service.DoSomething();
if (serviceProvider is IDisposable)
{
((IDisposable)serviceProvider).Dispose();
}
}
Getting Code Cleaner
The Main()
method is currently messed up with all the details of dependency injection. In real projects, we would probably have as many services in service the collection and filling this collection is worth a separate method. Also, I don’t like to have the service provider disposing the logic in the Main()
method.
class Program
{
private static IServiceProvider _serviceProvider;
static void Main(string[] args)
{
RegisterServices();
var service = _serviceProvider.GetService<IDemoService>();
service.DoSomething();
DisposeServices();
}
private static void RegisterServices()
{
var collection = new ServiceCollection();
collection.AddScoped<IDemoService, DemoService>();
// ...
// Add other services
// ...
_serviceProvider = collection.BuildServiceProvider();
}
private static void DisposeServices()
{
if(_serviceProvider == null)
{
return;
}
if (_serviceProvider is IDisposable)
{
((IDisposable)_serviceProvider).Dispose();
}
}
}
Things are better now as the Main()
method is cleaner.
Using Autofac
I once wrote about how to use Structuremap and Autofac with ASP.NET Core. Using Autofac is simple and we need only small changes in our code to make it work.
The first thing is to add Autofac NuGet packages to the .NET Core console application project.
- Autofac
- Autofac.Extensions.DependencyInjection
With the Autofac packages in place, we can change the RegisterServices
method to use Autofac.
private static void RegisterServices()
{
var collection = new ServiceCollection();
var builder = new ContainerBuilder();
builder.RegisterType<DemoService>().As<IDemoService>();
//
// Add other services ...
//
builder.Populate(collection);
var appContainer = builder.Build();
_serviceProvider = new AutofacServiceProvider(appContainer);
}
We don’t need modifications to any other part of code. With these changes done and compiled our application uses Autofac now.
Wrapping Up
The dependency injection mechanism provided by Microsoft with .NET Core is popular in ASP.NET, but it can be used also in other types of .NET Core projects. It's actually very easy to get dependency injection to work as we need with small amounts of code. In real applications, the biggest part of the related code is about registering types. We were also able to use Autofac as a third-party dependency injection framework. It was easy and we made only minor changes to our existing code.
Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments