У меня есть проект .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;
}
Конструктор службы билетов
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>();
возвращает фактическое значение. Похоже, вы переопределяете параметры, предоставленные делегату. По умолчанию он будет потреблять любые ошибки, поэтому никаких исключений не будет. - person Nkosi   schedule 24.03.2020GetSection()
действительно работает. И следующая строка, где извлекается ключ API, тоже работает. Делегат, когда он срабатывает, устанавливает правильный объект параметров. Загадка заключается в том, что делегат запускается только ПОСЛЕ конструктора для AuditedMailerService, поэтому служба всегда получает нулевой объект параметров. Я совершенно озадачен. - person Nate Kennedy   schedule 27.03.2020