项目
版本

如何注入配置、环境或上下文参数?

有时候,你需要解析一个依赖链中某个位置的 服务 ,而该服务消费了一个 组件 ,并且该组件需要从配置、环境或其他运行时上下文位置传递给它的参数。

对于这个问题,让我们想象一个简单的电子邮件通知系统:

// 这个接口让你可以向某人发送电子邮件通知。
public interface INotifier
{
    void Send(string address, string message);
}

// 该通知器实现使用后端电子邮件仓库来执行繁重的工作。
public class Notifier : INotifier
{
    private IEmailServer _server;
    public Notifier(IEmailServer server)
    {
        this._server = server;
    }

    public void Send(string address, string message)
    {
        this._server.SendMessage(address, "from@domain.com", message);
    }
}

// 这个电子邮件服务器接口是通知器将使用的发送电子邮件的方式。
public interface IEmailServer
{
    void SendMessage(string toAddress, string fromAddress, message);
}

// 注意这个实现接受一个服务器地址字符串参数 - 这是我们直到运行时才知道的值,因此不能显式注册参数值。
public class EmailServer : IEmailServer
{
    private string _serverAddress;
    public EmailServer(string serverAddress)
    {
        this._serverAddress = serverAddress;
    }

    public void SendMessage(string toAddress, string fromAddress, message)
    {
        // …通过指定的服务器地址发送消息。
    }
}

在 Autofac 中注册内容时,您可能会有如下这样的注册:

var builder = new ContainerBuilder();
builder.RegisterType<Notifier>().As<INotifier>();
builder.RegisterType<EmailServer>().As<IEmailServer>();
var container = builder.Build();

你只知道电子邮件服务器地址是在运行时 - 可能是通过上下文或环境参数,也可能是通过配置。

如何在解析通知器时将配置/环境/上下文参数传递给电子邮件服务器?

选项 1:使用 Lambda 注册

在这个选项中,而不是直接注册电子邮件服务器类型,使用 Lambda 表达式 注册。这允许您从容器中解析内容,或者使用环境获取值。

var builder = new ContainerBuilder();
builder.Register(ctx =>
{
    var address = Environment.GetEnvironmentVariable("SERVER_ADDRESS");
    return new EmailServer(address);
}).As<IEmailServer>();

作为这一过程的一部分,你可能希望围绕如何获取服务器地址创建某种抽象。例如,它可能是作为 HTTP 请求的一部分获取的,并且你将其存储在 HttpContext 中。你可以创建一个地址提供者,如下所示:

public interface IServerAddressProvider
{
    string GetServerAddress();
}

public class ContextServerAddressProvider : IServerAddressProvider
{
    private HttpContextBase _context;
    public ContextServerAddressProvider(HttpContextBase context)
    {
        this._context = context;
    }

    public string GetServerAddress()
    {
        return (string)this._context.Items["EMAIL_SERVER_ADDRESS"];
    }
}

一旦有了提供者,你可以在容器中注册它,并在与 Lambda 结合使用时使用它。

var builder = new ContainerBuilder();
builder.RegisterType<ContextServerAddressProvider>()
       .As<IServerAddressProvider>()
       .InstancePerRequest();
builder.Register(ctx =>
{
    var address = ctx.Resolve<IServerAddressProvider>().GetServerAddress();
    return new EmailServer(address);
}).As<IEmailServer>();

如果需要传递一个字符串参数或无法修改代码,这是推荐的选项。

选项 2:使用提供者

扩展第 1 选项中描述的提供者机制:通常最大的问题是,需要传递的参数是一个基本类型,如整数或字符串。如果你可以将其切换为使用提供者和强类型接口参数,那么注册会变得更加容易。

例如,你可能可以从像这样 的 Web 请求上下文中获取参数。

public interface IServerAddressProvider
{
    string GetServerAddress();
}

public class ContextServerAddressProvider : IServerAddressProvider
{
    private HttpContextBase _context;
    public ContextServerAddressProvider(HttpContextBase context)
    {
        this._context = context;
    }

    public string GetServerAddress()
    {
        return (string)this._context.Items["EMAIL_SERVER_ADDRESS"];
    }
}

然后,你可以重构电子邮件服务器代码,使其接受提供者而不是地址字符串:

public class EmailServer : IEmailServer
{
    private IServerAddressProvider _serverAddressProvider;
    public EmailServer(IServerAddressProvider serverAddressProvider)
    {
        this._serverAddressProvider = serverAddressProvider;
    }

    public void SendMessage(string toAddress, string fromAddress, message)
    {
        var address = this._serverAddressProvider.GetServerAddress();
        // …通过指定的服务器地址发送消息。
    }
}

现在,你可以简单地注册类型:

var builder = new ContainerBuilder();
builder.RegisterType<ContextServerAddressProvider>()
       .As<IServerAddressProvider>()
       .InstancePerRequest();
builder.RegisterType<EmailServer>().As<IEmailServer>();

如果可以修改代码,这是推荐的选项。

在本文档中