Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use dependency injection in .NET Standard project?

Is it possible to use dependency injection in a standalone .NET Standard project without an ASP.NET Core web application project?

Since there is no startup.cs file, I am curious to find out if this is possible, and if it is how it can be done?

like image 708
mko Avatar asked Jul 04 '20 19:07

mko


People also ask

Can we use dependency injection in .NET framework?

NET supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. Dependency injection in . NET is a built-in part of the framework, along with configuration, logging, and the options pattern.

How does .NET dependency injection work?

Dependency injection (also known as DI) is a design pattern in which a class or object has its dependent classes injected (passed to it by another class or object) rather than create them directly. Dependency injection facilitates loose coupling and promotes testability and maintenance.

How do you implement dependency injection in C#?

Dependency Injection is done by supplying the DEPENDENCY through the class's constructor when creating the instance of that class. The injected component can be used anywhere within the class. Recommended to use when the injected dependency, you are using across the class methods.

What is dependency injection C# with example?

Dependency Injection (DI) is a software design pattern. It allows us to develop loosely-coupled code. The intent of Dependency Injection is to make code maintainable. Dependency Injection helps to reduce the tight coupling among software components.


2 Answers

Yes, you absolutely can do this. In fact, even if your class libraries were in the same assembly as an ASP.NET Core implementation, it’s preferred to make your libraries entirely unaware of both your specific dependencies and any dependency injection container you’re using.

TL;DR: Both your dependencies and any dependency injection container you’re using should be configured in your application’s composition root (e.g., Startup), not in your library. Read on for details.

Core Concepts

First of all, it's useful to remember that, fundamentally, dependency injection is a set of patterns and practices for achieving loosely coupled code, and not a specific dependency injection container. That's important here as you don't want your libraries to become dependent upon whatever dependency injection container you may choose to use, such as the one built-in to ASP.NET Core. The container is simply a tool that helps automate the construction, management, and injection of dependencies into your libraries.

Given that, your .NET Standard Class Library should be written to allow dependencies to be injected from whatever application it is being implemented in, while being completely agnostic to what dependencies or even container is being used. Typically that is done through constructor injection, in which dependencies are exposed as parameters in a class’s constructor. In that case, you just want to expose dependencies in your constructors through their abstractions—namely through an interface that describes the implementation.

These concepts are easier to understand with an example.

Example

Let’s say you need access to some type of data persistence layer (such as a database) for users, but you don't want your class library to be tightly coupled to any single implementation (such as a SQL Server database).

Abstraction

In that case, you might create an IUserRepository abstraction, which your concrete implementations can implement. Here’s a barebones example, which exposes a single GetUser() method:

public interface IUserRepository 
{
    User GetUser(int userId);
}

Constructor Injection

Then, for any classes that depend on that service, you will implement a constructor which allows the IUserRepository abstraction to be injected into it:

public class MyClass 
{
    private readonly IUserRepository _userRepository;
    public MyClass(IUserRepository userRepository)
    {
        _userRepository = userRepository?? throw new ArgumentNullException(nameof(userRepository));
    }
    public string GetUserName(int userId) => _userRepository.GetUser(userId).Name;
}

Concrete Implementation

Now, you can create a concrete implementation of the IUserRepository—let us say a SqlUserRepository.

public class SqlUserRepository: IUserRepository
{
    private readonly string _connectionString;
    public SqlUserRepository(string connectionString) 
    {
        _connectionString = connectionString?? throw new ArgumentNullException(nameof(connectionString));
    }
    public GetUser(int userId) 
    {
        //Implementation
    }
}

Critically, this implementation could be in an entirely separate assembly; the assembly which contains IUserRepository and MyClass needn't be aware of it at all.

Dependency Injection

So where does the actual dependency injection happen? In whatever application that implements your .NET Standard Class Library. So, for instance, if you have an ASP.NET Core application, you might configure your dependency injection container via your Startup class to inject an instance of SqlUserRepository for any classes which depend upon an IUserRepository:

public void ConfigureServices(IServiceCollection services) 
{
    //Register dependencies
    services.AddScoped<IUserRepository>(c => new SqlRepository("my connection string"));
}

In that regard, your front-end application provides the glue between your .NET Standard Class Library and whatever services it depends upon.

Important: This could just as easily be a console application instead. It could use a third-party dependency injection container, such as Ninject. Or you could manually wire up your dependency graph in your composition root. What’s important here is that your .NET Standard class library doesn’t know or care, so long as something gives it its dependencies.

Assembly References

In the above example, your assembly structure might look something like this:

  • My.Library.dll
    • IUserRepository
    • MyClass
  • My.Library.Sql.dll (references My.Library.dll)
    • SqlUserRepository
  • My.Web.dll (references My.Library.dll,My.Library.Sql.dll)
  • My.Console.dll (references My.Library.dll,My.Library.Sql.dll)

Notice that My.Library is completely unaware of either the concrete implementations (e.g., My.Library.Sql.dll) or the applications which will implement it (e.g., My.Web.dll, My.Console.dll). All it aware of is that an IUserRepository will be injected into MyClass when it is constructed.

Important: These could all be in the same assembly. But even if they are, it’s useful to think of them as separate in order to maintain loose coupling between your dependency injection container, your core business logic, and your concrete implementations.

Best Practices

While not strictly required, a best practice is for your dependencies to be required parameters which are treated as read-only by your class. You can create a lot problems for yourself if you expose either optional dependencies, or dependencies which can be replaced during the lifetime of an object.

The above class structure demonstrates the ideal implementation by requiring the parameter:

public MyClass(IUserRepository userRepository) 

Adding a guard clause to prevent null values:

_userRepository = userRepository?? throw new ArgumentNullException(nameof(userRepository));

And, finally, assigning it to a readonly field so it can’t be replaced with a different implementation later:

private readonly IUserRepository _userRepository;
like image 144
Jeremy Caney Avatar answered Oct 17 '22 02:10

Jeremy Caney


Let's assume you are writing a log reader library that fetches data from several sources:

public interface IDataProvider
{
    Task<(IEnumerable<LogModel>, int)> FetchDataAsync(
        int page,
        int count,
        string level = null,
        string searchCriteria = null
    );
}

And you have multiple implementations for the above interface:

public class SqlServerDataProvider : IDataProvider
{
    private readonly RelationalDbOptions _options;

    public SqlServerDataProvider(RelationalDbOptions options)
    {
        _options = options;
    }

    public async Task<(IEnumerable<LogModel>, int)> FetchDataAsync(
        int page,
        int count,
        string logLevel = null,
        string searchCriteria = null
    )
    { ... }

And here is RelationalDbOptions class

public class RelationalDbOptions
{
    public string ConnectionString { get; set; }

    public string TableName { get; set; }

    public string Schema { get; set; }
}

Let's create a ServiceCollectionextension method to register dependencies:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddSqlServerLogReader(
        this IServiceCollection services,
        Action<RelationalDbOptions> options
    )
    {
        if (services == null)
            throw new ArgumentNullException(nameof(services));

        if (optionsBuilder == null)
            throw new ArgumentNullException(nameof(optionsBuilder));

        var relationalDbOptions = new RelationalDbOptions();
        options.Invoke(relationalDbOptions );
        services.AddSingleton(relationalDbOptions);

        Services.AddScoped<IDataProvider, SqlServerDataProvider>();

        return services;
    }
}

Now if you want to use your log reader library with ASP.NET Core or Console application, you can register log reader dependencies ba calling AddSqlServerLogReader in ConfigureServices method or anywhere that you are creating ServiceCollection:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSqlServerLogReader(options => { 
           options.ConnectionString = ""; 
           options.LogTableName = ""
    });
    ...
}

It's a common pattern to register library dependencies. Checkout real implementation here.

like image 40
Mohsen Esmailpour Avatar answered Oct 17 '22 03:10

Mohsen Esmailpour