简体   繁体   中英

.NET Core DI, register a default implementation for a package

How can one register a default implementation using the IoC container for .NET Core and also provide a way to override the existing implementation ?

For example, I might want to create a package which provide a default implementation for some service.

namesapce Package 
{
    public interface ISomeService { }

    public class Default : ISomeService { }
}

This service is then used inside the same package.

namesapce Package 
{
    public class Service 
    {
        Service(ISomeService service) { }
    }
}

How to register the ISomeService default implementation?

Later when using this package in some project and want to override the existing implementation with another the Default should be replaced with Override.

namespace Project 
{
    public class Override : ISomeService { }
}

If your package contains a class that configures an IServiceCollection , such as this:

public class MyPackageInstaller
{
    public void Install(IServiceCollection services)
    {
        // Your package registers its services
    }
}

Then this can also be a point where you allow the consumer to make optional changes. For example, you could define a class like this which allows the consumer to specify implementations for certain services:

public class MyPackageRegistrationOptions
{
    public ServiceDescriptor FooServiceDescriptor { get; private set; }

    public void AddFooService(ServiceDescriptor fooDescriptor)
    {
        if (fooDescriptor.ServiceType != typeof(IFooService))
        {
            throw new ArgumentException("fooDescriptor must register type IFooService.");
        }
        FooServiceDescriptor = fooDescriptor;
    }
}

Now your installer can take those options, and register either the implementation specified by the consumer or its own default.

public class MyPackageInstaller
{
    private readonly MyPackageRegistrationOptions _options;

    public MyPackageInstaller(MyPackageRegistrationOptions options = null)
    {
        _options = options;
    }
    public void Install(IServiceCollection services)
    {
        if (_options?.FooServiceDescriptor != null)
            services.Add(_options.FooServiceDescriptor);
        else 
             // here's your default implementation
            services.AddSingleton<FooService>();
    }
}

Usage:

var services = new ServiceCollection();
var options = new MyPackageRegistrationOptions();
options.AddFooService(ServiceDescriptor.Singleton<IFooService, AlternateFooService>());
var installer = new MyPackageInstaller(options);
installer.Install(services);

At first glance it looks like a longer way to get the same result. The benefit is that it allows you to make it clearer which services should or should not be overridden. That way it feels more like you're working with deliberately exposed configuration options and less like poking at the internals of the package.

Instead of allowing the consumer to add a ServiceDescriptor you could allow them to specify only a service type, and your configuration determines how it gets registered (singleton, transient, etc.)

This is also a helpful pattern when the library depends on configuration values like connection strings which must be supplied by the consumer. You can make them required arguments to construct options and then require the options to construct the installer, or just make them required arguments in the installer. Now it's impossible to install the package without the needed configuration values.

The built-in .NET Core DI Container allows an application developer to override your package's registrations to the ServiceCollection by simply appending the same service to the ServiceCollection . If multiple registrations are made for a single service type, the last registration will be used. For instance:

// Package registrations (part of your Package)
services.AddTransient<ISomeService, Default>();

// Override by application developer (part of his Startup.cs)
services.AddTransient<ISomeService, Override>();

DO CONSIDER to build your package in a way that it doesn't require the use of a DI Container, as described by Mark Seemann in his DI-Friendly Library article.

You can register whatever service you want inside your package and expose them via interfaces. Then when you will use it into some project, all you have to do to override the default package implementation is to override one of the exposed interfaces and that is that .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM