Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using Dependency Injection in Multiple .NET Core Projects

DZone's Guide to

Using Dependency Injection in Multiple .NET Core Projects

In this post we take a look at answering the question of how to setup and use dependency injection in multiple projects within the same solution.

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

One of my last posts was about Dependency Injection (DI) in .NET Core Console Applications. Some days after that post was published, I got a question about how to use the IServiceCollection in multiple projects. In this post, I'm going to try to explain just that. 

Setup

To demonstrate that, I created solutions with two .NET Core Console apps and two .NET Standard libraries. One of the console apps uses two of the libraries, and the other one is just using one. Each library provides some services which need to be registered to the DI container. Also, the console apps provide some services to add.

We now have four projects like this:

  • DiDemo.SmallClient
    • .NET Core Console app.
    • Includes a WriteSimpleDataService.
    • References DiDemo.CsvFileConnector.
  • DiDemo.BigClient
    • .NET Core Console app.
    • Includes a WriteExtendedDataService.
    • Includes a NormalizedDataService.
    • References DiDemo.SqlDatabaseConnector.
    • References DiDemo.CsvFileConnector.
  • DiDemo.SqlDatabaseConnector
    • .NET Standard library.
    • Includes a SqlDataService.
    • Includes a SqlDataProvider used by the service.
  • DiDemo.CsvFileConnector
    • .NET Standard library.
    • Includes a CsvDataService.

BTW: Since one of the latest updates the "Class Libraries (.NET Standard)" project disappeared from the ".NET Core" node in the "Add New Project" dialogue and the "Class Library (.NET Core)" is back again. The "Class Libraries (.NET Standard)" is now in the ".NET Standard" node under the "Visual C#" node.

In the most cases it doesn't really makes sense to create a .NET Core class library. The difference here is that the Class Library (.NET Core) has some .NET Core related references. They target the netcoreapp1.x instead of the netstandard1.x. This means they have a lot of references, which are not needed in a class library in the most cases, e. g. the Libuv and the .NET Core runtime.

The WriteExtendedDataService uses an INormalizedDataService to get the data and writes it to the console. The NormalizedDataService fetches the data from the CsvDataService and from the SqlDataService and normalizes it, to make it usable in the WriteExtendedDataService.

The WriteSimpleDataService uses only the ICsvDataService and writes the data out to the console.

Setup the DI Container

Let's set up the DI container for the SmallClient app. Currently, it looks like this:

var services = new ServiceCollection();
services.AddTransient<IWriteSimpleDataService, WriteSimpleDataService>();
services.AddTransient<ICsvDataService, CsvDataService>();

var provider = services.BuildServiceProvider();

var writer = provider.GetService<IWriteSimpleDataService>();
writer.write();

That doesn't really look wrong, but what happens if the app grows and gets a lot more services to add to the DI container? The CsvDataService is not in the app directly, but it is in a separate library. Usually, I don't want to map all the services of the external library. I just want to use the library and I don't want to know anything about the internal stuff. This is why we should set up the mapping for the DI container in the external library as well.

Let's Plug Things Together

The .NET Standard libraries should reference the Microsoft.Extensions.DependencyInjection.Abstractions to get the IServiceCollection interface. Now we can create a public static class called IServiceCollectionExtensions to create an extension method to work in the IServiceCollection:

public static class IServiceCollectionExtension
{
  public static IServiceCollection AddCsvFileConnector(this IServiceCollection services)
  {
    services.AddTransient<ICsvDataService, CsvDataService>();
    return services;
  }
}

Inside this method, we do all the mappings from the interfaces to the concrete classes or all the other registrations to the DI container. Let's do the same to encapsulate all the services inside the SmallClient app and to keep the program.cs as small as possible:

public static class IServiceCollectionExtension
{
  public static IServiceCollection AddInternalServices(this IServiceCollection services)
  {
    services.AddTransient<IWriteSimpleDataService, WriteSimpleDataService>();
    return services;
  }
}

We can now use this method in the program.cs of the SmallClient app to plug all that stuff together:

var services = new ServiceCollection();
services.AddInternalServices();
services.AddCsvFileConnector();

var provider = services.BuildServiceProvider();

var writer = provider.GetService<IWriteSimpleDataService>();
writer.write();

It looks much cleaner now. Maybe you remember the AddSomething methods? Exactly, this is the same way it is done in ASP.NET Core with the services.AddMvc() method.

We now need to do the same thing for the BigClient app and the SqlDatabaseConnector library. First, let's create the mapping for the SqlDatbaseConnector:

public static class IServiceCollectionExtension
{
  public static IServiceCollection AddSqlDatabaseConnector(this IServiceCollection services)
  {
    services.AddTransient<ISqlDataService, SqlDataService>();
    services.AddTransient<ISqlDataProvider, SqlDataProvider>();
    return services;
  }
}

We also need to create an extension method for the internal services:

public static class IServiceCollectionExtension
{
  public static IServiceCollection AddInternalServices(this IServiceCollection services)
  {
    services.AddTransient<IWriteExtendedDataService, WriteExtendedDataService>();
    services.AddTransient<INormalizedDataService, NormalizedDataService>();
    return services;
  }
}

Now let's plug that stuff together in the BigClient App:

var services = new ServiceCollection();
services.AddInternalServices();
services.AddCsvFileConnector();
services.AddSqlDatabaseConnector();

var provider = services.BuildServiceProvider();

var writer = provider.GetService<IWriteExtendedDataService>();
writer.write();

As you can see, the BigClient app uses the already existing services.AddCsvFileConnector() method.

Does It Really Work?

It does. Start the BigClient app in Visual Studio to make sure that it will work as expected:

To see the full sources and to try it out by yourself, please visit my GitHub repository.

What do you think? Do you have questions or some ideas to add? Feel free to drop a comment.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
.net core ,dependency injection ,web dev

Published at DZone with permission of Juergen Gutsch, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}