Разрешение зависимостей в ConfigureMultitenantContainer - PullRequest
1 голос
/ 07 октября 2019

Я пытаюсь разрешить ITenantIdentificationStrategy в ConfigureMultitenantContainer, но у меня An unhandled exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll.

Я зарегистрировал TenantResolverStrategy в ConfigureContainer:

public void ConfigureContainer(ContainerBuilder builder)
{
  builder.RegisterType<TenantResolverStrategy>().As<ITenantIdentificationStrategy>();
}

Я хочу разрешить ITenantIdentificationStrategy в ConfigureMultitenantContainer:

public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
  var strategy = container.Resolve<ITenantIdentificationStrategy>();
  var mtc = new MultitenantContainer(strategy, container);
  // mtc.ConfigureTenant("a", cb => cb.RegisterType<TenantACustom>().As<ITenantCustom>());
  return mtc;
}

Однако он выбрасывает An unhandled exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll.

Мой ITenantIdentificationStrategy реализован следующим образом:

public class TenantResolverStrategy : ITenantIdentificationStrategy
{
  public TenantResolverStrategy(
    IHttpContextAccessor httpContextAccessor,
    IMemoryCache memoryCache,
    TenantEntity tenantEntity
  )
  {
    this.httpContextAccessor = httpContextAccessor;
    this.memoryCache = memoryCache;
    this.tenantEntity = tenantEntity;
  }

  public bool TryIdentifyTenant(out object tenantId)
  {
    tenantId = null;

    var context = httpContextAccessor.HttpContext;
    var hostName = context?.Request?.Host.Value;

    tenantEntity = GetTenant(hostName);
    if (tenantEntity != null)
    {
      tenantId = tenantEntity.TenantCode;
    }

    return (tenantId != null || tenantId == (object)"");
  }
}

И я регистрирую ConfigureMultitenantContainer в Program.cs следующим образом:

var host = Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))

Я не могу разрешить другие зависимости, которые я регистрирую также в ConfigureContainer. Что-то не так с моей реализацией?

1 Ответ

1 голос
/ 07 октября 2019

Есть несколько вещей, которые могут вызывать у вас проблемы здесь.

Во-первых, я вижу, что ваша стратегия идентификации арендатора: , а не одиночный .

builder.RegisterType<TenantResolverStrategy>().As<ITenantIdentificationStrategy>();

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

Учтите:

var strategy = container.Resolve<ITenantIdentificationStrategy>();
// The multitenant container is CACHING THIS.
var mtc = new MultitenantContainer(container, strategy);
// Now, later on you maybe resolve another instance of the strategy:
var anotherInstance = mtc.Resolve<ITenantIdentificationStrategy>();
// Or from the root:
var thirdInstance = container.Resolve<ITenantIdentificationStrategy>();

// OH NO! strategy != anotherInstance != thirdInstance
// These ARE NOT THE SAME INSTANCE. Tenant determination may CHANGE
// based on which one of these is used.

Сделайте свою стратегию идентификации арендатора одноэлементной.

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

public class TenantResolverStrategy : ITenantIdentificationStrategy
{
  public TenantResolverStrategy(
    IHttpContextAccessor httpContextAccessor,
    IMemoryCache memoryCache,
    TenantEntity tenantEntity
  )
  {
    this.httpContextAccessor = httpContextAccessor;
    this.memoryCache = memoryCache;

    // PROBLEM! Where is TenantEntity coming from?
    this.tenantEntity = tenantEntity;
  }

  public bool TryIdentifyTenant(out object tenantId)
  {
    tenantId = null;

    var context = httpContextAccessor.HttpContext;
    var hostName = context?.Request?.Host.Value;

    // PROBLEM: Incorrectly storing state in the strategy
    // when this is used across threads. (There's also no
    // explanation of what's in GetTenant, so it's hard to
    // help with that.) 
    tenantEntity = GetTenant(hostName);
    if (tenantEntity != null)
    {
      tenantId = tenantEntity.TenantCode;
    }

    return (tenantId != null || tenantId == (object)"");
  }
}

Вы можете поддерживать кэш, но не поддерживать его состояние. Например, может быть, вам нужен Dictionary<string, object> для кэширования имени хоста в сопоставления идентификаторов арендаторов, и это нормально (если вы делаете это вокруг или используете поточно-безопасный словарь). Но у вас есть отдельный объект , который может быть перезаписан между потоками, и это плохие новости.

Далее, я вижу, что ваша стратегия ID арендатора принимает зависимости. Как правило, я бы избегал этого и строил бы это напрямую. Я знаю, что это не очень хорошо для некоторых людей, но есть тенденция к "сверх-DI" вещам, которые не должны быть вовлечены в DI. Построение вручную базовых объектов, таких как ContainerBuilder или ваша стратегия идентификатора арендатора, гарантирует, что вы смотрите только на те вещи, которыми можете управлять (и избегаете таких исключений, как вы видели).

Вам следуетбыть в состоянии вручную разрешить любые зависимости, которые входят в стратегию идентификатора клиента. Например, это должно работать:

public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
  // These are the dependencies of the strategy. You don't NEED TO DO THIS
  // but if you put these in here, it SHOULD NOT BLOW UP. If it does, you know
  // where to start tracing things down.
  var accessor = container.Resolve<IHttpContextAccessor>();
  var cache = container.Resolve<IMemoryCache>();
  var entity = container.Resolve<TenantEntity>();

  // Here's the strategy - again, make sure it's a SINGLETON!
  var strategy = container.Resolve<ITenantIdentificationStrategy>();
  var mtc = new MultitenantContainer(strategy, container);
  // mtc.ConfigureTenant("a", cb => cb.RegisterType<TenantACustom>().As<ITenantCustom>());
  return mtc;
}

Тем не менее, я признаю, что такие вещи, как соединения с базой данных, возможно, потребуется ввести,в этом случае, опять же, убедитесь, что все помечено как синглетоны . Ваша стратегия ID арендатора контейнера с несколькими арендаторами будет действовать в течение всего жизненного цикла приложения. Кроме того, ничто, что зависит от стратегии ID арендатора (например, контейнер для нескольких арендаторов), не должно быть специфичным для арендатора или основанным на запросе, потому что ... вы не можете определить арендатора без действительной стратегии ID арендатора. Круговая зависимость!

Итак, все сводится к следующему:

  • Зарегистрируйте свою стратегию идентификатора клиента как одиночную.
  • Удалите все зависимости в стратегии идентификатора арендатора, которые не являются't singletons (например, TenantEntity).
  • Используйте поточно-ориентированное кэширование (например, кэш-память) для сопоставлений идентификатора хоста-арендатора, но не сохраняйте состояние (не сохраняйте этоTenantEntity переменная экземпляра; при необходимости сделайте ее локальной переменной уровня метода.
  • Убедитесь, что все, что нужно разрешить, зарегистрировано. Если вашей стратегии ID арендатора требуется IHttpContextAccessor (или IMemoryCache, или что-то еще), это необходимо зарегистрировать. Попробуйте разрешить эти зависимости напрямую, если у вас возникнут проблемы;это скажет вам, какой именно компонент имеет проблемы. (Однако, если вы посмотрите всю трассировку стека полученного исключения, вы должны точно увидеть, что происходит. Вы не включили это сообщение об исключении в вопрос, поэтому мы не можем погрузиться в него.)
...