如何使用FluentScheduler正确配置Simple Injector

Nit*_*ena 3 c# asp.net-mvc dependency-injection simple-injector fluentscheduler

我有以下用于Simple Injector的配置。

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

RegisterScheduler初始化并计划作业。

BackgroundScheduler如下所示:

public class BackgroundScheduler : IJob, IRegisteredObject
    {
        IUserService _userService;

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

        public void Execute() 
        {
            _userService.GetAll();
        }
    }
Run Code Online (Sandbox Code Playgroud)

BackgroundScheduler取决于IUserService。当我尝试在后台调度程序中注入IUserService时,出现以下异常:

BackgroundScheduler已注册为“混合Web请求/线程作用域”生活方式,但是在活动(混合Web请求/线程作用域)作用域的上下文之外请求实例。

堆栈跟踪:

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: 
Run Code Online (Sandbox Code Playgroud)

我不确定为什么会这样?

Ste*_*ven 5

IJobFactory不建议使用FleuntScheduler ,而不会用其他扩展点代替。尽管官方文档似乎缺少有关如何从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);
}
Run Code Online (Sandbox Code Playgroud)

注意:本示例使用ThreadScopedLifestyle生活方式,因为这是您在问题中使用的生活方式。的AsyncScopedLifestyle,然而,优选在ThreadScopedLifestyle作为它的工作原理都在单线程和异步方案。ThreadScopedLifestyle在.NET 4.0中运行时很有用。

在这个例子中 IMyJob是应用程序指定的抽象。这样做时,可以防止您的应用程序代码依赖于FluentScheduler。

此扩展方法可以按如下方式使用:

container.AddFluentSchedulerJob<MyAwesomeJob>(s => s.ToRunEvery(5).Seconds());
Run Code Online (Sandbox Code Playgroud)

其他选项是使用(现已弃用)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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以通过设置来使用该工厂JobManager.JobFactory,就像已经做的那样:

JobManager.JobFactory = new SimpleInjectorJobFactory(container);
Run Code Online (Sandbox Code Playgroud)