Unity LifeTime Manager for Quartz.net

I am trying to use Quartz.Net in an asp.net MVC application. I use Unity as the the DI, PerRequestLifeTimeManager.

Quartz.Net, however, does not work with PerRequestLifeTimeManager, because, well, it does not have a request to start. Any dependency I am trying to resolve with it returns null.

I created a class as an adapter for using two context-based lifetime managers as follows:

class CustomLifetimeManager : LifetimeManager
{
    private readonly string _key = "CustomLifetimeManagerKey" + Guid.NewGuid();
    private readonly PerResolveLifetimeManager _perResolveLifetimeManager = new PerResolveLifetimeManager();

    private bool IsWebContext => HttpContext.Current != null;

    public override object GetValue()
    {
        return IsWebContext 
            ? HttpContext.Current.Items[_key] 
            : _perResolveLifetimeManager.GetValue();
    }

    public override void SetValue(object newValue)
    {
        if (IsWebContext)
            HttpContext.Current.Items[_key] = newValue;
        else
            _perResolveLifetimeManager.SetValue(newValue);
    }

    public override void RemoveValue()
    {
        if (IsWebContext)
            HttpContext.Current.Items[_key] = null;
        else
            _perResolveLifetimeManager.RemoveValue();
    }
}

I tried PerThreadLifetimeManager, it executes perfectly for the first time, then subsequent executions do not execute with the message

The operation could not be performed because the DbContext was located.

I tried to switch to PerResolveLifeTimeManager, but with an error

Entity object cannot reference multiple instances of IEntityChangeTracker

, :

[DisallowConcurrentExecution]
class MyJob 
{
    IFooRepository _fooRepository;
    IBarRepository _barRepository;
    public MyJob(IFooRepository fooRepository, IBarRepository barRepository)
    {
        _fooRepository = fooRepository;
        _barRepository = barRepository;
    }

    public void Execute(IJobExecutionContext context)
    {
        var foos = _fooRepository.Where(x => !x.Processed);

        foreach(var foo in foos)
        {
            var bar = _barRepository.Where(x => x.Baz == foo.Baz);
            foo.DoMagic(bar);
            foo.Processed = true;
            _fooRepository.Save(foo);
        }
    }
}

factory

public class UnityJobFactory : IJobFactory
{
    private readonly IUnityContainer _container;

    public UnityJobFactory(IUnityContainer container)
    {
        _container = container;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return (IJob)_container.Resolve(bundle.JobDetail.JobType);
    }

    public void ReturnJob(IJob job)
    {

    }
}

?

+4
3

, . Quartz, , ,

LifetimeScope

public class LifetimeScopeJobDecorator : IJob
{
    private readonly IJob _decoratee;
    private readonly Container _container;

    public LifetimeScopeJobDecorator(IJob decoratee, Container container)
    {
        _decoratee = decoratee;
        _container = container;
    }

    public void Execute(IJobExecutionContext context)
    {
            using (_container.BeginLifetimeScope())
            {
                _decoratee.Execute(context);
            }
    }
}

factory

public class SimpleInjectorJobFactory : IJobFactory
{
    private readonly Container _container;

    public SimpleInjectorJobFactory(Container container)
    {
        _container = container;
        _container.Verify();
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
            IJobDetail jobDetail = bundle.JobDetail;
            Type jobType = jobDetail.JobType;
            var job = (IJob)_container.GetInstance(jobType);
            return new LifetimeScopeJobDecorator(job, _container);
    }

    public void ReturnJob(IJob job)
    {
    }
}

public static class QuartzScheduler
{
    private static Container _quartzContainer { get; set; }

    private static void Initialize()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<IUnitOfWork, SqlUnitOfWork>();
        container.Register<ILogger, NLogLogger>();


        //To enable lifetime scoping, please make sure the EnableLifetimeScoping extension method is called during the configuration of the container.
        container.EnableLifetimeScoping();
        container.Verify();
        _quartzContainer = new Container();

        var schedulerFactory = new StdSchedulerFactory();
        _quartzContainer.RegisterSingle<IJobFactory>(() => new SimpleInjectorJobFactory(container));
        _quartzContainer.RegisterSingle<ISchedulerFactory>(schedulerFactory);
        _quartzContainer.Register<IScheduler>(() =>
        {
            var scheduler = schedulerFactory.GetScheduler();
            scheduler.JobFactory = _quartzContainer.GetInstance<IJobFactory>();
            return scheduler;
        }
        );
        _quartzContainer.Verify();

    public static void StartJobs()
    {
        Initialize();

            //Ask the scheduler factory for a scheduler
            IScheduler scheduler = _quartzContainer.GetInstance<IScheduler>();
            scheduler.Start();

    }
+1

Castle.Windsor Quartz.Net. , , - ScopedLifetime, Scope. , , ( UnitOfWork;)), , .

. . Factory, .

  • Factory , Execute(IJobExecutionContext context) Factory , (...) . A using(Factory.BeginScope()) . , - .

    public class MyJob
    {
        private readonly Factory Factory;
    
        public MyJob(Factory factory)
        {
            Factory = factory;
        }
    
        public void Execute(IJobExecutionContext context)
        {
            using (Factory.BeginScope())
            {
                var repo = Factory.Create<IFooRepository>();
                // Do stuff
    
                Factory.Release(repo);
            }
        }
    }
    
  • Factory -, , : Func<IFooRepository> repositoryFunc. Execute ( ) repository, , , . . , Locator Service, Func<> , .

    public class MyJob
    {
        private readonly Factory Factory;
        private readonly Func<IFooRepository> RepositoryFunc;
    
        public MyJob(Factory factory, Func<IFooRepository> repositoryFunc)
        {
            Factory = factory;
            RepositoryFunc= repositoryFunc;
        }
    
        public void Execute(IJobExecutionContext context)
        {
            using (Factory.BeginScope())
            {
                var repo = RepositoryFunc();
                // Do Stuff
            }
        }
    }
    

  • PerThreadLifetimeManager

    , DbContext.

    , Quartz MainThread ThreadPool 10 . MainThread, . , DBC- MainThread. , DBContext, , , , LifeTimeManager . , , DBC- . , DBContext, Thread. LifeTimeManager allready , , .

  • PerResolveLifeTimeManager

    IEntityChangeTracker

    EF. , , , . , , , DBC-. EF DBC- .

+3

, Quartz.Unity nuget https://github.com/hbiarge/Quartz.Unity, ScopedLifetime.

nuget, lifetimemanager , , DBContext , HTTP.

IUnityContainer asp.net mvc/web api IUnityContainer Quartz.

https://github.com/vinodres/DITestingWithQuartz

QuartzStartup.cs, Quartz Scheduler. , IHelloService , , HTTP-. IUnityContainer QuartzContainer QuartzUnityExtention Quartz.Unity nuget. .Configure, unityconfig.cs. Func . .

QuartzStartup.cs

[assembly: OwinStartup(typeof(DiTestingApp.QuartzStartup))]
namespace DiTestingApp
{
    /// <summary>
    /// 
    /// </summary>
    public class QuartzStartup
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(QuartzStartup));
        /// <summary>
        /// Get the hangfire container.
        /// </summary>
        private static readonly Lazy<IUnityContainer> QuartzContainer = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            container.AddNewExtension<QuartzUnityExtension>();
            container.Configure(() => new HierarchicalLifetimeManager());
            return container;
        });

        /// <summary>
        /// 
        /// </summary>
        /// <param name="app"></param>
        public void Configuration(IAppBuilder app)
        {
            Log.Info("Quartz Startup Intitializing...");
            var container = QuartzContainer.Value;
            InitScheduler(container);
            Log.Info("Quartz Startup Intialization Complete...");

            var properties = new AppProperties(app.Properties);
            var cancellationToken = properties.OnAppDisposing;
            if (cancellationToken != CancellationToken.None)
            {
                cancellationToken.Register(() =>
                {
                    QuartzContainer.Value.Dispose();
                    Log.Info("Quartz container disposed (app pool shutdown).");
                });
            }
        }

        private void InitScheduler(IUnityContainer container)
        {
            try
            {
                var scheduler = container.Resolve<IScheduler>();
                scheduler.Start();

                IJobDetail job = JobBuilder.Create<HelloWorldJob>().Build();

                ITrigger trigger = TriggerBuilder.Create()
                    .WithSimpleSchedule(x => x.WithIntervalInSeconds(20).RepeatForever())
                    .Build();

                scheduler.ScheduleJob(job, trigger);
            }
            catch (Exception ex)
            {
                Log.Error(ex);
            }

        }
    }
}

asp.net mvc/web api. UnityMvcActivator.cs, , .Configure extension, PerRequestLifetimeManager.

UnityMvcActivator.cs

using System;
using System.Linq;
using System.Web.Http;
using System.Web.Mvc;
using Common.Logging;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Mvc;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(DiTestingApp.App_Start.UnityWebActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(DiTestingApp.App_Start.UnityWebActivator), "Shutdown")]

namespace DiTestingApp.App_Start
{
    /// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
    public static class UnityWebActivator
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(UnityWebActivator));
        /// <summary>
        /// Get the hangfire container.
        /// </summary>
        private static readonly Lazy<IUnityContainer> WebContainer = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            container.Configure(() => new PerRequestLifetimeManager());
            return container;
        });

        /// <summary>Integrates Unity when the application starts.</summary>
        public static void Start() 
        {
            Log.Info("Web api DI container intializing.");
            var container = WebContainer.Value;

            FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
            FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

            DependencyResolver.SetResolver(new UnityDependencyResolver(container));

            // TODO: Uncomment if you want to use PerRequestLifetimeManager
            Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

            var resolver = new Microsoft.Practices.Unity.WebApi.UnityDependencyResolver(container);

            GlobalConfiguration.Configuration.DependencyResolver = resolver;
            Log.Info("Web api DI container intialization complete.");
        }

        /// <summary>Disposes the Unity container when the application is shut down.</summary>
        public static void Shutdown()
        {
            Log.Info("Web api DI container disposing.");
            var container = WebContainer.Value;
            container.Dispose();
        }
    }
}

, IUnityContainer. configure UnityConfig.cs. IHelloService disposableLifetimeManager. , . PerRequestLifetimeManager, IHelloService asp.net mvc/web api. HierarchicalLifetimeManager, Quartz Job.

UnityConfig.cs

using System;
using DiTestingApp.Models;
using Microsoft.Practices.Unity;
using Quartz;
using Testing.Scheduler;

namespace DiTestingApp
{
    /// <summary>
    /// Specifies the Unity configuration for the main container.
    /// </summary>
    public static class UnityConfig
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="container"></param>
        /// <param name="disposableLifetimeManager"></param>
        /// <returns></returns>
        public static IUnityContainer Configure(this IUnityContainer container, Func<LifetimeManager> disposableLifetimeManager )
        {

            container.RegisterType<IJob, HelloWorldJob>();
            container.RegisterType<IHelloService, HelloService>(disposableLifetimeManager());
            return container;
        }
    }
}

The HierarchicalLifetimeManager is used for the Quartz execution path, so any one-time types will be correctly placed at the end of each job.

If the implementation of Quartz.Unity is insufficient for your use cases, you can always configure it further.

0
source

Source: https://habr.com/ru/post/1661878/


All Articles