Autofac - время жизни и модули

Проблема (аннотация)

Дан модуль, который регистрирует зависимость X. Зависимость X имеет другое время жизни в приложении MVC3 (время жизни для HttpRequest), чем в консольном приложении (зависимость на время жизни с именем). Где или как указать время жизни зависимости X?

случай

Я поместил весь код, связанный с моей базой данных, в сборку с модулем, который регистрирует все репозитории. Теперь регистрация ISession (Nhibernate) также находится в модуле.

ISession является зависимостью X (в данном проблемном случае). Время жизни ISession в приложении MVC3 отличается (время жизни на запрос), чем в консольном приложении, где я определяю именованную область жизни.

Должна ли регистрация ISession находиться за пределами модуля? Было бы странно, так как это деталь реализации.

Что лучше всего сделать здесь? Недостаток дизайна или есть умные конструкции для этого:)?

2 ответа

Решение

Учитывая ваше описание варианта использования, я бы сказал, что у вас есть несколько вариантов.

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

Во-вторых, вы можете обернуть общую часть (за вычетом времени жизни) в метод расширения ContainerBuilder, который можно использовать в каждом приложении. Это по-прежнему означало бы, что у каждого приложения есть небольшой "дублированный код", но общая логика была бы заключена в простое расширение.

public static IRegistrationBuilder<TLimit, ScanningActivatorData, DynamicRegistrationStyle>
  RegisterConnection<TLimit, ScanningActivatorData, DynamicRegistrationStyle>(this ContainerBuilder builder)
{
  // Put the common logic here:
  builder.Register(...).AsImplementedInterfaces();
}

Использование такого расширения в каждом приложении будет выглядеть так:

builder.RegisterConnection().InstancePerHttpRequest();
// or
builder.RegisterConnection().InstancePerLifetimeScope();

Наконец, если вы знаете, что это веб или не веб, вы могли бы создать собственный модуль, который обрабатывает переключатель:

public class ConnectionModule : Autofac.Module
{
  bool _isWeb;
  public ConnectionModule(bool isWeb)
  {
    this._isWeb = isWeb;
  }

  protected override void Load(ContainerBuilder builder)
  {
    var reg = builder.Register(...).AsImplementedInterfaces();
    if(this._isWeb)
    {
      reg.InstancePerHttpRequest();
    }
    else
    {
      reg.InstancePerLifetimeScope();
    }
  }
}

В каждом приложении вы можете зарегистрировать модуль:

// Web application:
builder.RegisterModule(new ConnectionModule(true));

// Non-web application:
builder.RegisterModule(new ConnectionModule(false));

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

public class ConnectionModule : Autofac.Module
{
  object _scopeTag;
  public ConnectionModule(object scopeTag)
  {
    this._scopeTag = scopeTag;
  }

  protected override void Load(ContainerBuilder builder)
  {
    var reg = builder.Register(...)
                     .AsImplementedInterfaces()
                     .InstancePerMatchingLifetimeScope(this._scopeTag);
  }
}

Расход аналогичен:

// Web application (using the standard tag normally provided):
builder.RegisterModule(new ConnectionModule("httpRequest"));

// Non-web application (using your custom scope name):
builder.RegisterModule(new ConnectionModule("yourOtherScopeName"));

Я бы рекомендовал не использовать просто InstancePerLifetimeScope в веб-приложении, если это на самом деле то, что вы намерены. Как отмечено в других ответах / комментариях, InstancePerHttpRequest использует определенную именованную область действия, чтобы было безопасно создавать дочерние области действия; с помощью InstancePerLifetimeScope не имеет такого ограничения, поэтому вы фактически получите одно соединение на дочернюю область, а не одно соединение для запроса. Лично я не предполагаю, что другие разработчики не будут использовать дочерние области действия ( что является рекомендуемой практикой), поэтому в своих приложениях я очень конкретен. Если вы полностью контролируете свое приложение и можете убедиться, что вы не создаете дополнительные дочерние области или что вам действительно нужно одно соединение для каждой области, то, возможно, InstancePerLifetimeScope решит вашу проблему.

Обычной практикой является использование одного соединения на HTTP-запрос. В этом случае соединения будут регистрироваться с использованием.InstansePerLifetimeScope(). Например, вы можете сделать что-то вроде:

builder
    .Register(c => {
                       var conn = new SqlConnection(GetConnectionString());
                       conn.Open();
                       return conn;
                   })
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();
Другие вопросы по тегам