Как разрешить из родительской области автозапуска, не передавая ее несколько раз? - PullRequest
0 голосов
/ 26 июня 2018

У меня есть приложение, которое в какой-то момент создает область жизни:

    public class Main
    {
        public void Main()
        {
            using (ILifetimeScope scope = AutofacContainer.Container.BeginLifetimeScope())
            {
                scope.Resolve<SomeClass>();
            }
        }
    }

Внутри SomeClass У меня есть логика, которая затем вызывает много разных классов и так далее .. Затем, около 10 методов в стеке вызовов, для этого мне нужно использовать основную область:

public class ActivatorFactory : IActivatorFactory
{
    public T Create<T>(Type instance)
    {
        using (ILifetimeScope scope = AutofacContainer.Container.BeginLifetimeScope())
        {
            return (T)scope.Resolve(instance);
        }
    }
}

Проблема в том, что теперь я создал новую область видимости, которая просто используется для разрешения типа времени выполнения. Я хочу иметь возможность использовать основной объем для разрешения этого типа. Как я могу сделать это, не передавая основную область видимости этому фабричному классу через 10 различных методов / функций? Единственное «хакерское» решение, о котором я подумал, - это просто иметь статическое свойство на моем ActivatorFactory и установить область действия в моем Main классе следующим образом:

    public class Main
    {
        public void Main()
        {
            using (ILifetimeScope scope = AutofacContainer.Container.BeginLifetimeScope())
            {
                ActivatorFactory.Scope = scope;
                scope.Resolve<SomeClass>();
            }
        }
    }

Есть ли более чистое решение для использования основной области в другой части моего приложения?

Ответы [ 2 ]

0 голосов
/ 08 января 2019

У меня была потребность в CancellationTokenSource экземпляре на всю жизнь, где дети связаны со своим родителем. Если CancellationTokenSource область действия root отменена, все области действия всей жизни CancellationToken отменяются. Для этого я создал:

private sealed class ParentLifetimeScopeAccessor
{
    private readonly ILifetimeScope _lifetimeScope;

    public ParentLifetimeScopeAccessor(ILifetimeScope lifetimeScope)
    {
        _lifetimeScope = lifetimeScope;
        _lifetimeScope.ChildLifetimeScopeBeginning += OnChildLifetimeScopeBeginning;
    }

    public ILifetimeScope ParentLifetimeScope { get; private set; }

    private void OnChildLifetimeScopeBeginning(object sender, LifetimeScopeBeginningEventArgs e) =>
        e.LifetimeScope.Resolve<ParentLifetimeScopeAccessor>().ParentLifetimeScope = _lifetimeScope;
}

После регистрации вы можете получить доступ к области действия своих родителей:

builder.RegisterType<ParentLifetimeScopeAccessor>().InstancePerLifetimeScope();

С родительским аксессором времени жизни можно создать связанные CancellationTokenSource экземпляры:

private static CancellationTokenSource CancellationTokenSourceFactory(IComponentContext context)
{
    var scopeAccessor = context.Resolve<ParentLifetimeScopeAccessor>();
    var parentScope = scopeAccessor.ParentLifetimeScope;
    return null == parentScope
        ? new CancellationTokenSource()
        : CancellationTokenSource.CreateLinkedTokenSource(parentScope.Resolve<CancellationTokenSource>().Token);
}

CancellationToken распознаватель:

private static CancellationToken CancellationTokenResolver(IComponentContext context) =>
context.Resolve<CancellationTokenSource>().Token;

Две регистрации:

builder.Register(CancellationTokenSourceFactory).AsSelf().InstancePerLifetimeScope();
builder.Register(CancellationTokenResolver).AsSelf().InstancePerDependency();
0 голосов
/ 27 июня 2018

Если вы не используете ActivatorFactory для своего приложения (и вы не должны использовать его, если используете инверсию управления), удалите его и подумайте, что вы пытаетесь проверить .

  • Вы пытаетесь проверить, что обычно вы можете просто разрешить проблемы с помощью Autofac? Autofac имеет множество модульных тестов, а также миллионы успешных пользователей. Нет смысла в тестировании фреймворка.
  • Вы пытаетесь проверить, что вы зарегистрировали все, что вам нужно было зарегистрировать? В этом тоже нет особой ценности по нескольким причинам: во-первых, вы получите это в время выполнения довольно быстро и увидеть это в тех тестах; во-вторых, в большой разобщенной системе эти тесты действительно быстро устаревают. Это хлопоты по обслуживанию.
  • Вы пытаетесь проверить, что конкретный граф объектов может быть составлен на основе ваших регистраций? Я мог бы купить этот. Смотри ниже.

Допустим, это последнее - у вас есть действительно сложный и проблемный граф объектов, который вы хотите создать, потому что люди продолжают его ломать. Я мог видеть это.

Разделите ваши регистрации в модуле Autofac. Для проверки используйте модуль Autofac.

public class MyRegistrations : Autofac.Module
{
  protected override void Load(ContainerBuilder builder)
  {
    builder.RegisterType<Thing>();
    // and all your other registrations.
  }
}

затем в модульном тесте

var builder = new ContainerBuilder();
builder.RegisterModule<MyRegistrations>();
var container = builder.Build();
var thing = container.Resolve<Thing>();
// Assert on the resolved thing.

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

Предупреждение. Это скользкий путь между тестированием сложной регистрации и тестированием всех регистраций. Как я уже сказал, вы действительно не хотите тестировать каждую вашу регистрацию. Я упал с этого склона. Это кошмар обслуживания. Добавьте регистрацию в модуль / приложение, добавьте тест. О, мы сделали рефакторинг, теперь все регистрации разные. Тьфу. Это меньше проверяет поведение, чем характеристику (не «что я хочу сделать», а «что он делает сейчас»). Боль. Страдание.

Если вы используете ActivatorFactory в своем приложении , скажем, для определения местоположения службы, вместо использования более стандартной вещи, такой как CommonServiceLocator , которая уже делает это для вас и для которого Autofac уже напрямую интегрирует ... тогда просто протестируйте ActivatorFactory с реальным контейнером, но с некоторыми произвольными тестовыми регистрациями, а не со всем набором из реального приложения. Функциональность ActivatorFactory не имеет никакого отношения к тому, что зарегистрировано внутри него .

И, да, если вы используете ActivatorFactory и вам нужно его сохранить, вам придется вручить его ILifetimeScope при запуске приложения. Вот так работают сервисные локаторы. Все это вы увидите в документах Autofac , когда вы посмотрите, как интегрировать приложения, такие как ASP.NET, WCF и другие.

...