.NET Core DI,注册程序包的默认实现

时间:2019-05-22 11:05:44

标签: c# asp.net-core dependency-injection .net-core inversion-of-control

如何使用.NET Core的IoC容器注册默认实现,并提供一种覆盖现有实现的方法?

例如,我可能想创建一个为某些服务提供默认实现的软件包。

namesapce Package 
{
    public interface ISomeService { }

    public class Default : ISomeService { }
}

然后在同一软件包中使用此服务。

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

如何注册ISomeService默认实现?

稍后在某些项目中使用此程序包并希望用另一个方法覆盖现有实现时,应将Default替换为Override。

namespace Project 
{
    public class Override : ISomeService { }
}

3 个答案:

答案 0 :(得分:2)

内置的.NET Core DI容器允许应用程序开发人员通过简单地将相同的服务附加到ServiceCollection来覆盖您添加到ServiceCollection的程序包的注册。如果为单个服务类型进行了多次注册,则将使用最后一次注册。例如:

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

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

请考虑,以不需要使用DI容器的方式构建软件包,如Mark Seemann在其DI-Friendly Library文章中所述。

答案 1 :(得分:2)

如果您的软件包中包含配置IServiceCollection的类,例如:

public class MyPackageInstaller
{
    public void Install(IServiceCollection services)
    {
        // Your package registers its 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;
    }
}

现在,您的安装程序可以使用这些选项,并注册使用者指定的实现或其默认设置。

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>();
    }
}

用法:

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

乍看之下,获得相同结果的方法似乎更长。这样做的好处是,它使您可以更清楚地指出哪些服务应该被覆盖或不应被覆盖。这样一来,感觉就像您正在使用故意公开的配置选项,而不会像戳软件包的内部一样。

您可以允许他们仅指定一种服务类型,而不是允许使用者添加ServiceDescriptor,并且您的配置确定如何注册它(单例,瞬态等)。

当库依赖于配置值(例如必须由使用者提供的连接字符串)时,这也是一种有用的模式。您可以使它们成为构造选项的必需参数,然后要求这些选项来构造安装程序,或者只是在安装程序中使它们成为必需参数。现在,没有所需的配置值就无法安装软件包。

答案 2 :(得分:0)

您可以在包中注册所需的任何服务,并通过接口公开它们。然后,当您将其用于某个项目中时,要覆盖默认的包实现,要做的就是覆盖暴露的接口之一,那就是。