【发布时间】:2020-07-05 07:35:31
【问题描述】:
我有一个 .NET Core 2.2 WebAPI 项目,我在其中注册了三个服务(我们将它们称为 MailerService、TicketService 和 AuditServce),以及一个依赖于的中间件 (ExceptionMiddleware)在其中一项服务上 (MailerService)。 MailerService 和 TicketService 都依赖于强类型选项对象,我在 service.Configure<TOption>() 注册。我已确保选项对象在服务之前注册,并且选项依赖项本身已连接到服务的构造函数中。
问题是TicketService 可以很好地从 DI 解析其选项对象,但由于某种原因,MailerService 的配置在服务本身之后解析。下面是相关代码的粗略草图。
我设置了断点来观察解析顺序,并且设置 MailerConfig 的委托在 MailerService 构造函数之后始终触发。所以每次我得到一个 MailerSerivce 的实例时,它的 options 参数都是 NULL。然而,观察 TicketService 的相同解析,TicketConfig 在 TicketService 构造函数触发之前解析,并且 TicketService 得到一个正确配置的选项对象。除了 MailerService 是中间件的依赖项之外,我无法弄清楚它们之间可能有什么不同。
我已经为此苦苦思索了好几个小时,但找不到任何合适的文档来解释为什么 DI 解析顺序可能会失控,或者我在这里可能做错了什么。有人猜我可能做错了什么吗?异常中间件是否也需要注册为服务?
启动
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
services.Configure<MailerConfig>(myOpts =>
{
// this always resolves AFTER MailerService's constructor
myOpts = Configuration.GetSection("MailerSettings").Get<MailerConfig>();
myOpts.SecretKey = _GetApiKey(Configuration.GetValue<string>("MailerApiKeyFile"));
});
services.Configure<ExceptionMiddlewareConfig>(myOpts =>
{
myOpts.AnonymousUserName = Configuration.GetValue<string>("AnonymousUserName");
myOpts.SendToEmailAddress = Configuration.GetValue<string>("ErrorEmailAddress");
});
services.Configure<TicketConfig>(myOpts =>
{
// this always resovles BEFORE TicketService's constructor
myOpts.ApiRoot = Configuration.GetValue<string>("TicketApiRoot");
myOpts.SecretKey = _GetApiKey(Configuration.GetValue<string>("TicketApiKeyFile"));
});
services.AddTransient(provider =>
{
return new AuditService
{
ConnectionString = Configuration.GetValue<string>("Auditing:ConnectionString")
};
});
services.AddTransient<ITicketService, TicketService>();
services.AddTransient<IMailerService, AuditedMailerService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<ExceptionMiddleware>();
//app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
}
MailerService 构造函数
public AuditedMailerService(AuditService auditRepo, IOptions<MailerConfig> opts)
{
// always gets a NULL opts object??????
_secretKey = opts.Value.SecretKey;
_defaultFromAddr = opts.Value.DefaultFromAddress;
_defaultFromName = opts.Value.DefaultFromName;
_repo = auditRepo;
}
TicketService 构造函数
public TicketService(IOptions<TicketConfig> opts)
{
// always gets an initialized opts object with proper values assigned
ApiRoot = opts.Value.ApiRoot;
SecretKey = opts.Value.SecretKey;
}
中间件构造函数
public ExceptionMiddleware(RequestDelegate next, IMailerService mailer, IOptions<ExceptionMiddlewareConfig> config)
{
_mailer = mailer;
_next = next;
_anonymousUserName = config.Value.AnonymousUserName;
_sendToEmailAddress = config.Value.SendToEmailAddress;
}
【问题讨论】:
-
不确定,但如果您想“共享”相同的选项,为什么不将它们注册为单例...。这样可以省去很多麻烦
-
确认
Configuration.GetSection("MailerSettings").Get<MailerConfig>();返回一个实际值。您似乎覆盖了提供给代表的选项。默认情况下,它会消耗任何错误,因此不会抛出异常。 -
@NKosi:
GetSection()调用确实有效。获取 api 密钥的下一行也可以正常工作。委托在触发时会设置一个适当的选项对象。令人困惑的是,委托仅在 AuditedMailerService 的构造函数之后才被触发 - 因此服务总是获得一个空选项对象。我很困惑。 -
@NateKennedy 检查我的provided answer
标签: c# .net-core dependency-injection asp.net-core-webapi .net-core-2.2