如何注入配置、环境或上下文参数?
有时候,你需要解析一个依赖链中某个位置的 服务 ,而该服务消费了一个 组件 ,并且该组件需要从配置、环境或其他运行时上下文位置传递给它的参数。
对于这个问题,让我们想象一个简单的电子邮件通知系统:
// 这个接口让你可以向某人发送电子邮件通知。
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>();
如果可以修改代码,这是推荐的选项。