Full source code available here.
Over the past couple of years I wrote a few posts about Dependency Injection in .Net Core 2.1, and this week I received comments from a reader telling me that some of the changes in .Net Core 3.1 mean that some of the approaches no longer work. There have been breaking changes https://docs.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.1#hosting-generic-host-restricts-startup-constructor-injection
I wanted to see what would still work so I tried a few things.
You can no longer DI from Program
in Startup
.
But you can add a transient service and/or a singleton service to the ServiceCollection
and use it within Program, and the rest of the application. You can also add a scoped service to the ServiceCollection and use it within the Program, it’s a little different from using transient and singleton so I’ll cover it in the next post.
Here’s how to use transient and singletons inside Program.
Create two services, creatively named ServiceOne
and ServiceTwo
and have them implement interfaces.
public class ServiceOne : IServiceOne { public static int staticCounter; public ServiceOne() { staticCounter++; } //snip… public class ServiceTwo : IServiceTwo { public static int staticCounter; public ServiceTwo() { staticCounter++; } //snip…
I added a static counter to make it easy to see how many times the constructor is called.
For the transient one I expect it to increment every time the service is injected, for the singleton I expect it to remain at 1 for the lifetime of the application.
In Program.cs I split up CreateHostBuilder(args).Build call from the subsequent .Run().
public static void Main(string[] args) { IHost host = CreateHostBuilder(args).Build(); DoSomethingWithTheTransientService(host.Services); DoSomethingWithTheSingletonService(host.Services); host.Run(); }
The CreateHostBuilder()
method is not changed –
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
And I have two methods that use the ServiceCollection
to access the registered services –
private static void DoSomethingWithTheTransientService(IServiceProvider serviceProvider) { Console.WriteLine("Calling the transient service"); var serviceOne = serviceProvider.GetService<IServiceOne>(); Console.WriteLine(serviceOne.StaticCounter()); Console.WriteLine(serviceOne.GetHashCode()); } private static void DoSomethingWithTheSingletonService(IServiceProvider serviceProvider) { Console.WriteLine("Calling the singleton service"); var serviceTwo = serviceProvider.GetService<IServiceTwo>(); Console.WriteLine(serviceTwo.StaticCounter()); Console.WriteLine(serviceTwo.GetHashCode()); }
To make what’s happening more obvious I added the services to the constructor call of the WeatherForecastController()
and added the counter and hash codes to the data returned by the action method.
public class WeatherForecastController : ControllerBase { private IServiceOne _serviceOne; private IServiceTwo _serviceTwo; public WeatherForecastController(IServiceOne serviceOne, IServiceTwo serviceTwo) { _serviceOne = serviceOne; _serviceTwo = serviceTwo; }
For completeness, here is the ConfigureServices
method in Startup.cs
–
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IServiceOne, ServiceOne>(); services.AddSingleton<IServiceTwo, ServiceTwo>(); services.AddControllers(); }
Put some breakpoints in Program Main(), DoSomethingWithTheTransientService(), DoSomethingWithTheSingletonService()
and in the WeatherForecastController.Get()
.
Start the application and browse to http://localhost:5000/weatherforecast
to see what happens.
I’m going to follow up on this post with a version that shows how to use scoped dependencies in Startup .NET 5.
Full source code available here.