AWS Developer Tools Blog
Working with dependency injection in .NET Standard: inject your AWS clients – part 2
In part 1 of this blog post, we explored using the lightweight dependency injection (DI) provided by Microsoft.Extensions.DependencyInjection. By itself, this is great for libraries and small programs, but if you’re building a nontrivial application, you have other problems to contend with:
- You might have complex configuration needs (development versus production, multiple sources, etc.)
- How do you implement logging cleanly?
- Lifetime management is hard…
Fortunately, there is a solution – hosts! A host centralizes and simplifies the setup of these application-wide concerns. In the .NET Core 1.0 release, Web Host was introduced to simplify the setup of web applications. In the release of .NET Core 2.1, Generic Host was added to support developers of any tech stack.
These hosts are very similar, and the Generic Host might eventually replace the Web Host (as mentioned in the Microsoft documentation at the time of this post). If you’re using the Web Host, the main distinction from the Generic Host is use of the Startup class. If you’re building a web application, you should use the Web Host. Otherwise, use the Generic Host. The example in this post uses the Generic Host.
Demo
Let’s take a look at the example from part 1, rewritten to use the Generic Host. To recap, this application simply lists out all Amazon S3 buckets and Amazon DynamoDB tables in the account.
Demo.cs
Instead of manually creating a ServiceCollection, the host gives us an instance. This collection already contains relevant configuration, so we don’t have to explicitly add it. The collection also has some new extension methods: AddLogging to inject the loggers provided by the host, and AddHostedService to specify which objects to manage. Besides setting up DI with configuration and logging, the host also manages the application lifetime and communicates this information between its hosted services. In this case, we only have one hosted service: Application.
Application.cs
Application now has a logger and a lifetime management object injected into its constructor. It also implements IHostedService, and has StartAsync and StopAsync hooks to enable the host to relay lifetime-related events. These methods take cancellation tokens, so we should always funnel those down to our async calls to ensure we can correctly respond to lifetime directives from the host in a timely way. We no longer need to call Run directly, so we have made it private.
We could have extended BackgroundService instead of implementing IHostedService directly. This example uses IHostedService to be explicit about lifetime and to show logging examples. If you’re building a continuously or long-running service, consider using BackgroundService; implement ExecAsync, and only implement StartAsync and StopAsync when needed.
Let’s run through an execution:
- “dotnet run” is executed on the compiled application.
- Program.Main is executed, which calls Program.WithGenericHost.
- The host is defined, using settings from the “appsettings.json” file, a ServiceCollection for DI (with the services it’s responsible for hosting), a logger, and what runtime to use (act like a console application).
- The host starts, and creates an Application hosted service, giving runtime dependencies, a logger instance, and a lifetime handle or delegate.
- Host calls StartAsync on the application.
- Application does work.
- Application signals to the host to close the application through applicationLifetime.
- Host calls StopAsync on the application.
- Host has stopped all hosted services, and exits.
Because we’re using ConsoleLifetime, the application responds to other signals, like Ctrl+C. If we enter that keyboard shortcut, the host will call StopAsync on all HostedServices (Application).
Conclusion
And there it is! By changing a few blocks of code, we significantly increase the quality of our application! We now have cleaner, simpler DI, logging, and lifetime management.
If you’re using a Web Host, this is still relevant, but you are probably using controllers, which differ from hosted services. You should inject loggers into your controllers, but controller lifetime is managed for you.
Consider using a host in your next application!