Reusing Initialization Code in Startup.cs

...and using ILogger!

Today we are going to have some more DI fun and extend the ServiceCollection used in .NET to inject services.

Some back ground info.. I am working on a big project with lots of different projects that all are sharing some of the same code base. This is all good in regards to reduce code duplication, but since these projects also use DI it means that we had a lot of the same code in Startup.cs. For example all our services would required logging, they pretty much all need some database access and then they also used some libraries, and so on. So, to not duplicate the initialization code in Startup.cs we made some small extensions on ServiceCollection.

I will keep this example simple, so it's easier to understand the design behind it, so I hope you can see it's a thought example 😀

Solution

To start with we need 3 projects in a VS solution:

  • App.Shared - Class library
  • App1 - Console App
  • App2 - Console App

solution-setup.png

Implementation

Then we need to add a few Nugets to the App.Shared project:

install-package Microsoft.Extensions.DependencyInjection
install-package Microsoft.Extensions.Logging
install-package Microsoft.Extensions.Logging.Console

Extending ServiceCollection

Here's the code for that:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddRequiredService(this IServiceCollection services, bool isAwesome = true)
    {
        services.AddLogging(configure =>
            configure.AddConsole());

        services.AddSingleton(new AwesomeServiceOptions { IsAwesome = isAwesome });
        services.AddSingleton<AwesomeService>();
        return services;
    }
}

This code does:

  1. Configure logging
  2. Add options for our service
  3. Add our service

Configuring a Second Service

To see how this works we need a second service were we can inject the above service:

public class Service
{
    private readonly ILogger<Service> log;
    private readonly AwesomeService awesomeService;
    public Service(ILogger<Service> log, AwesomeService awesomeService)
    {
        log.LogDebug($"{nameof(Service)} initializing...");
        this.log = log;
        this.awesomeService = awesomeService;
        log.LogDebug($"{nameof(Service)} initialized.");
    }
    public void Run()
    {
        Console.WriteLine($"{nameof(AwesomeService.IsAwesome)}: {awesomeService.IsAwesome}");
    }
}

As you can see, this is simple, the constructor is just accepting an ILogger and our AwesomeService.

Finally, Our 2 Apps

So we have 2 different apps that we want to use DI in, here's the first one:

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection()
            .AddRequiredServices()
            .AddTransient<Service>();
        var serviceProvider = services.BuildServiceProvider();
        var service = serviceProvider.GetService<Service>();
        service?.Run();
        Console.ReadKey();
    }
}

And the second:

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection()
            .AddRequiredServices(isAwesome: false)
            .AddTransient<Service>();
        var serviceProvider = services.BuildServiceProvider();
        var service = serviceProvider.GetService<Service>();
        service?.Run();
        Console.ReadKey();
    }
}

Notice the difference? It's just the isAwesome: true/false.

If we run the first one, this is what it outputs: image.png

Then look what happens if we run the second one: image.png

Wrap Up

So, that's it for today's effort of making the world a little less duplicated place to live in 😁

As with all my other articles, you can find the code in this Github repo:

Did you find this article valuable?

Support Nicklas Møller Jepsen by becoming a sponsor. Any amount is appreciated!