Как правильно настроить простой инжектор с FluentScheduler - PullRequest
0 голосов
/ 28 декабря 2018

У меня есть следующая конфигурация для простого инжектора.

public class SimpleInjectorIntegrator
{
    private static Container container;

    public static Container Setup()
    {
        container = new Container();
        container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
            defaultLifestyle: new WebRequestLifestyle(),
            fallbackLifestyle: new ThreadScopedLifestyle());

        container.Register<IUserService, UserService>(Lifestyle.Scoped);
        container.Register<IJob, BackgroundScheduler>(Lifestyle.Scoped);

        JobManager.JobFactory = new SimpleInjectorJobFactory(container);
        JobManager.Initialize(new RegisterScheduler());
    }
}

public class SimpleInjectorJobFactory : IJobFactory
{
    Container Container;

    public SimpleInjectorJobFactory(Container container)
    {
        this.Container = container;
    }

    public IJob GetJobInstance<T>() where T : IJob
    {
        return Container.GetInstance<IJob>();
    }
}

RegisterScheduler инициализирует и планирует задание.

BackgroundScheduler выглядит следующим образом:

public class BackgroundScheduler : IJob, IRegisteredObject
    {
        IUserService _userService;

        public BackgroundScheduler(IUserService userService)
        {
            _userService = userService;
        }

        public void Execute() 
        {
            _userService.GetAll();
        }
    }

BackgroundScheduler зависит от IUserService.Когда я пытаюсь внедрить IUserService в фоновом планировщике, я получаю следующее исключение:

BackgroundScheduler регистрируется как образ жизни «Гибридный веб-запрос / Потоковая область», но экземпляр запрашивается вне контекста активного (ГибридныйОбласть веб-запросов / темы).

Трассировка стека:

SimpleInjector.ActivationException was unhandled by user code
  HResult=-2146233088
  Message=The BackgroundScheduler is registered as 'Hybrid Web Request / Thread Scoped' lifestyle, but the instance is requested outside the context of an active (Hybrid Web Request / Thread Scoped) scope.
  Source=SimpleInjector
  StackTrace:
       at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration`1 registration)
       at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration`1 registration, Scope scope)
       at SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
       at lambda_method(Closure )
       at SimpleInjector.InstanceProducer.BuildAndReplaceInstanceCreatorAndCreateFirstInstance()
       at SimpleInjector.InstanceProducer.GetInstance()
       at SimpleInjector.Container.GetInstanceFromProducer(InstanceProducer instanceProducer, Type serviceType)
       at SimpleInjector.Container.GetInstanceForRootType[TService]()
       at SimpleInjector.Container.GetInstance[TService]()
       at FluentScheduler.JobManager.<>c__12`1.<GetJobAction>b__12_0() in __the_file_path_omitted__:line 76
       at System.Threading.Tasks.Task.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()
  InnerException: 

Я не уверен, почему это происходит?

1 Ответ

0 голосов
/ 28 декабря 2018

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

Посколькуиспользование замыкания означает разрешение задания, его обертывание в область действия и регистрацию вашей работы в Simple Injector, наиболее практичным решением было бы перенести эту логику в метод расширения.Это может выглядеть так:

public static void AddFluentSchedulerJob<TJob>(
    this Container container, Action<Schedule> schedule)
    where TJob : class, IMyJob
{
    container.Register<TJob>();

    JobManager.AddJob(() =>
        {
            using (ThreadScopedLifestyle.BeginScope(container))
            {
                container.GetInstance<TJob>().Run();
            }
        },
        schedule);
}

ПРИМЕЧАНИЕ. В этом примере используется ThreadScopedLifestyle стиль жизни, поскольку именно это вы используете в своем вопросе.Однако AsyncScopedLifestyle предпочтительнее, чем ThreadScopedLifestyle, поскольку он работает как в однопоточном, так и в асинхронном сценариях.ThreadScopedLifestyle полезен при работе в .NET 4.0.

В этом примере IMyJob является абстракцией, определенной приложением.При этом вы не допускаете зависимости кода приложения от FluentScheduler.

Этот метод расширения можно использовать следующим образом:

container.AddFluentSchedulerJob<MyAwesomeJob>(s => s.ToRunEvery(5).Seconds());

Другими вариантами является использование (теперь устарело) IJobFactory.Это требует ваших заданий для реализации интерфейса FluentScheduler IJob.Сложность реализации фабрики заданий заключается в том, что вам нужно найти способ обернуть операцию в область действия.

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

Вот фабрика, которая использует область действия:

public class SimpleInjectorJobFactory : IJobFactory
{
    private readonly Container container;

    public SimpleInjectorJobFactory(Container container) => this.container = container;

    public IJob GetJobInstance<T>() where T : IJob
    {
        return new ThreadScopedJobDecorator(
            this.container,
            () => (IJob)this.container.GetInstance(typeof(T)));
    }

    private sealed class ThreadScopedJobDecorator : IJob
    {
        private readonly Container container;
        private readonly Func<IJob> decorateeFactory;

        public ThreadScopedJobDecorator(Container container, Func<IJob> decorateeFactory)
        {
            this.container = container;
            this.decorateeFactory = decorateeFactory;
        }

        public void Execute()
        {
            using (ThreadScopedLifestyle.BeginScope(this.container))
            {
                this.decorateeFactory().Execute();
            }
        }
    }
}

Вы можетеиспользуйте эту фабрику, установив JobManager.JobFactory, как вы уже делаете:

JobManager.JobFactory = new SimpleInjectorJobFactory(container);
...