Почему разрешение зависимостей разрешает параметры моей службы ПОСЛЕ самой службы? - PullRequest
1 голос
/ 24 марта 2020

У меня есть проект. NET Core 2.2 WebAPI, в котором я регистрирую три службы (назовем их MailerService, TicketService и AuditServce), а также промежуточное программное обеспечение (ExceptionMiddleware) это зависит от одного из этих сервисов (MailerService). MailerService и TicketService оба зависят от строго типизированных объектов параметров, которые я регистрирую с помощью service.Configure<TOption>(). Я убедился, что объекты опций регистрируются до сервисов, а сами зависимости опций связаны с конструкторами сервисов.

Проблема заключается в том, что TicketService разрешает объект параметров просто из DI, но по какой-то причине конфигурация для MailerService разрешает ПОСЛЕ самой службы. Грубый набросок соответствующего кода ниже.

Я установил точки останова для наблюдения за порядком разрешения, и делегат для установки MailerConfig последовательно запускает ПОСЛЕ конструктора MailerService. Поэтому каждый раз, когда я получаю экземпляр MailerSerivce, его опцией является 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;
}

Ответы [ 2 ]

0 голосов
/ 27 марта 2020

Хотя это не очень хороший ответ (я до сих пор понятия не имею , почему DI разрешал только параметры после службы), я нашел решение проблемы , Я просто делаю окончательный анализ шаблона Options и решаю все зависимости явно в делегате, где я регистрирую почтовую службу. Я также настроил ExceptionMiddleware, чтобы использовать почтовую службу в качестве аргумента метода в InvokeAsyn c, а не в качестве аргумента конструктора. Не очень важно, чтобы службы были временными или одиночными, но в настоящее время я просто предпочитаю временные.

Заметным недостатком этого подхода является то, что я больше не могу использовать встроенные в параметры механизмы оперативного обновления. система - если я на лету изменю значение в моих настройках приложения, приложение должно быть переработано, чтобы забрать его. Это не является реальной потребностью в моем приложении, поэтому я могу с этим смириться, но другие должны быть внимательны, прежде чем следовать моему подходу.

Новый делегат регистрации MailerService:

  services.AddTransient<IMailerService>(provider =>
  {
    var cfg = Configuration.GetSection("MailerSettings").Get<MailerConfig>();
    cfg.SecretKey = _GetApiKey(Configuration.GetValue<string>("MailerApiKeyFile"));

    var auditor = provider.GetService<AuditService>();

    return new AuditedMailerService(auditor, Options.Create(cfg));
  });
0 голосов
/ 24 марта 2020

Потому что то, что вы делаете, не имеет смысла.

Вы регистрируете промежуточное ПО с зависимостью от сервиса, который вы пометили как временный, то есть create-on-demand.

Но промежуточное ПО всегда запускается при запуске приложения (singleton) . Поэтому любые зависимости также создаются при запуске приложения. Следовательно, экземпляр вашей «временной» службы, созданной вашим промежуточным программным обеспечением, также является одноэлементным!

Кроме того, если ваше промежуточное программное обеспечение является единственной вещью, которая зависит от этой временной службы, то зарегистрируйте службу как что-либо, кроме одиночной. бессмысленно!

То, что у вас есть, это несоответствие образа жизни иждивенцев, , что, как правило, плохая идея по многим причинам . Чтобы избежать этого, как указано выше, убедитесь, что все сервисы в вашей цепочке зависимостей зарегистрированы в одной и той же области видимости, то есть все, от чего зависит ваш ExceptionMiddleware - в данном случае AuditedMailerService - должно быть одноэлементным .

Если - , если - вы неявно намереваетесь или хотите, чтобы AuditedMailerService был переходным процессом, тогда вместо внедрения его в конструктор промежуточного программного обеспечения вставьте его через Invoke method :

public ExceptionMiddleware(RequestDelegate next, IOptions<ExceptionMiddlewareConfig> config)
{
  _mailer = mailer;
  _anonymousUserName = config.Value.AnonymousUserName;
  _sendToEmailAddress = config.Value.SendToEmailAddress;
}

public async Task Invoke(HttpContext httpContext, IMailerService mailer)
{
  ...
}

Но вот еще один интересный вопрос, вытекающий из симптомов этого несоответствия образа жизни: почему экземпляр IOptions<MailerConfig> в итоге становится null ?

Мое предположение - и это только предположение - заключается в том, что вы попали в тупик из-за того, что ASP. NET Core 2.x's WebHost (компонент, который запускает ваш веб-приложение) фактически создает два IServiceProvider экземпляров . Существует первоначальный «фиктивный», который создается для внедрения сервисов на самых ранних этапах запуска приложения, а затем «реальный», который используется до конца жизни приложения. Связанная проблема обсуждает, почему это проблематично c: короче говоря, было возможно получить экземпляры службы, зарегистрированные в фиктивном контейнере, тогда реальный контейнер создал бы второй экземпляр той же службы, вызывая проблемы. Я полагаю, что поскольку промежуточное программное обеспечение запускается так рано в конвейере, контейнер Io C, который он использует, является фиктивным без знания IOptions<MailerConfig> и , поскольку расположение службы по умолчанию в ASP. NET Core. возвращает null, когда запрашиваемая служба не найдена, вместо выдачи исключения , возвращается null.

...