Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Judy 1 post 71 karma points
    Mar 22, 2019 @ 10:38
    Judy
    0

    Hangfire dependency injection

    Hi!

    I'm using Hangfire with Umbraco 8 and followed this post to set it up: https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/

    I'm using LightInject and now trying to inject a custom service in my scheduled job class. Like so:

    public class ImportValuesJob
    {
        private IImportService _importService;
    
        public ImportValuesJob(IImportService importService)
        {
            _importService = importService;
        }
    
        public void SetTimeInterval()
        {
            RecurringJob.AddOrUpdate(() => SetTimeInterval(), Cron.MinuteInterval(1));
        }
    
        public void Import(PerformContext context)
        {
            var success = _importService.ImportData();
        }
    }
    

    However, this would require me to pass IImportService as a parameter in my OwinStartup class when creating a new instance of ImportValuesJob, and so far I have found no way to access my service.

    var importJob = new ImportValuesJob();
    importJob .SetTimeInterval();
    

    Is there a different, clever way of accomplishing this?

    Thanks!

  • Andreas Emtinger 16 posts 157 karma points
    Mar 25, 2019 @ 09:18
    Andreas Emtinger
    0

    Hi I had the same problem, but I solved it yesterday.

    First set upp Hangfire with dependency incjection in UmbracoDefaultOwinStartup.Configuration (I'm using Autofac and with package Hangfire.Autofac):

     var builder = new ContainerBuilder();
     builder.RegisterType<QueService>().As<IQueService>();
     Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(builder.Build());
    

    Than add jobs like this:

    RecurringJob.AddOrUpdate<IQueService>("QueService.ProcessQue", que => que.ProcessQue(), processQueCron);
    
  • Jesper Rasmussen 3 posts 73 karma points
    Apr 10, 2019 @ 09:10
    Jesper Rasmussen
    0

    Hi Andreas

    Are you using Autofac alongside Umbraco 8's LightInject implementation (and is that even possible??)?

    The reason I ask is, that if I change the dependency resolver to Autofac, the backoffice doesn't work. My guess is that the backoffice is using dependencies registered in umbracos LightInject implementation, and that they are now missing in Autofac.

    I also found that i have to register umbraco's services in Autofac setup to be able to use them in my hangfirejob. Have you found an elegant solution to this, where you can reuse the registrations already done by Umbraco?

    Thanks

  • Sebastiaan Janssen 4850 posts 14396 karma points MVP admin hq
    Mar 25, 2019 @ 09:18
    Sebastiaan Janssen
    0

    Hi Judy, the advise would be similar to the advise here: register your importservice into dependency injection and then you're good to go: https://our.umbraco.com/forum/extending-umbraco-and-using-the-api/96310-umbraco-8-package-rewrite-some-help-please#comment-304955

    So something like:

    [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
    public class MyComposer : IUserComposer
    {
        public void Compose(Composition composition)
        {
            composition.Register<IImportService, ImportService >(Lifetime.Singleton);
        }
    }
    
  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 08, 2019 @ 13:30
    Rasmus Fjord
    0

    Hey Sebastiaan

    There is something here where im missing a link.

    So its the same case as the thread here is about, using DI, with Hangfire, but it just dosnt work, and im gussing its because im doing it wrong :)

    So I have a ton of services working in my project with DI. Hope you can help me. The setup is like this:

    MyService:

     public class MyService
    {
        private readonly OtherService _otherService;
    
        public MyService(OtherService otherService)
        {
            _otherService= otherService;
        }
    
        public void SomeJob(PerformContext hangfire)
        {
            //DO IT! :D 
        }
    }
    

    Composer:

     [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
    public class HangfireJobsComposer : IUserComposer
    {
        public void Compose(Composition composition)
        {
            composition.Register<MyService>(Lifetime.Singleton);
    
        }
    }
    

    OwinStartup

        public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
    {
        private readonly SomeService _someService;
    
        public UmbracoStandardOwinStartup(SomeService someService)
        {
            _someService = someService;
    
        }
    
        public override void Configuration(IAppBuilder app)
        {
            //ensure the default options are configured
            base.Configuration(app);
    
            // Configure hangfire
            var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
            var connectionString = System.Configuration
               .ConfigurationManager
               .ConnectionStrings["umbracoDbDSN"]
               .ConnectionString;
    
            GlobalConfiguration.Configuration
                .UseSqlServerStorage(connectionString, options)
                .UseConsole();
            var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
            app.UseHangfireDashboard("/hangfire", dashboardOptions);
            app.UseHangfireServer();
    
            RecurringJob.AddOrUpdate("SomeName", () => _someService .SomeJob(null), Cron.HourInterval(12));
    
    
    
        }
    
    
    
    }
    

    But this gets me a "parameterless constructor not found" on the OwinStartup.

    I cant seem to figur out how to use my DI services with hangfire. Ive tried some packages like :

    Couldnt seem to get any of it to work really.

  • Andreas Emtinger 16 posts 157 karma points
    Apr 10, 2019 @ 13:33
    Andreas Emtinger
    0

    We still use Umbraco 7 (latest version).

    I had to add this:

    // register all controllers found in this assembly
    builder.RegisterControllers(Assembly.GetExecutingAssembly()); //.PropertiesAutowired();
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
    
    // register Umbraco MVC + web API controllers used by the admin site
    builder.RegisterControllers(typeof(UmbracoApplication).Assembly);
    builder.RegisterApiControllers(typeof(UmbracoApplication).Assembly);
    builder.RegisterInstance(applicationContext.Services.MemberService).As<IMemberService>();
    //builder.RegisterType<Members>().AsSelf();
    
    // umbraco helper
    builder.Register(c => ApplicationContext.Current).InstancePerRequest();
    builder.Register(c => UmbracoContext.Current).InstancePerRequest();
    builder.Register(c => new UmbracoHelper(c.Resolve<UmbracoContext>())).InstancePerRequest();
    
    // plugins
    builder.RegisterApiControllers(typeof(Lecoati.LeBlender.Extension.Controllers.LeBlenderEditorManagerTreeController).Assembly);
    builder.RegisterApiControllers(typeof(Diplo.GodMode.Controllers.GodModeTreeController).Assembly);
    builder.RegisterApiControllers(typeof(Jumoo.uSync.BackOffice.Controllers.uSyncApiController).Assembly);
    builder.RegisterApiControllers(Assembly.Load("Umbraco.Forms.Web"));
    
  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    Oct 08, 2019 @ 14:03
    Thomas Rydeen Skyldahl
    1

    The default LightInject scope management in Umbraco 8 will give you issues if you don't stick to Singleton services in your Hangfire jobs. as it relies on HttpContext to store the active scope.

    We have done workarounds to get around the issue, but it isn't exactly the most beautiful code I have ever seen.

    but here are the basic steps to get Hangfire hooked up.

    You can get the LightInject ServiceContainer by using the following inside an IComposer Compose method.

    var container = composition.Concrete as LightInject.ServiceContainer;
    

    You can copy the code from the source repository for Hangfire.LightInject to your project and compile it against the embedded verison of LightInject inside Umbraco.

    We hookup the Hangfire Config inside the an IComposer something like the following, you can't use this exactly as is but it will give you an idea, ours is a little convoluted as we have parts that are .net standard and .net framework so we use the DI as if it was based on the .NET Core IServiceProvider interface, to be ready for the shift when it comes:

            private void ComposeHangfire(IServiceProvider serviceProvider)
        {
            var connectionString = System.Configuration
                                         .ConfigurationManager
                                         .ConnectionStrings[HangfireConnectionStringName]
                                         .ConnectionString;
    
            // configure Hangfire storage and ServiceProvider
            GlobalConfiguration.Configuration
                   .UseSqlServerStorage(connectionString, new SqlServerStorageOptions { PrepareSchemaIfNecessary = true })
                   .UseServiceProviderJobActivator(serviceProvider);
        }
    

    Last part is to override the UmbracoDefaultOwinStartup, remember to replace it in web.config as well.

    [assembly: OwinStartup(nameof(HangfireUmbracoOwinStartup), 
     typeof(HangfireUmbracoOwinStartup))]
     namespace ContentService.Infrastructure
    {
    public class HangfireUmbracoOwinStartup : UmbracoDefaultOwinStartup
    {
        public ConnectionStringsConfiguration ConnectionStrings { get; set; }
    
        public override void Configuration(IAppBuilder app)
        {
            //ensure the default options are configured
            base.Configuration(app);
    
            try
            {
                // NOTE: Triggers exception if the Storage hasn't been configured at this point
                // ReSharper disable once UnusedVariable
                var currentStorage = JobStorage.Current;
    
                app.UseHangfireServer();
                // place inside umbraco folder to avoid exposing it
                app.UseHangfireDashboard("/umbraco/hangfire", new DashboardOptions
                {
                    Authorization = new[]
                    {
                        // Only grant access to users in Umbraco with access to the Hangfire Section
                        new UmbracoAuthorizationFilter()
                    },
                    // avoid showing back to site link
                    AppPath = null
                });
            }
            catch (InvalidOperationException ex)
            {
                // JobStorage.Current is not configured when installing updates
                if (!ex.Message.StartsWith("JobStorage.Current"))
                    throw;
            }
        }
    }
    
  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 09, 2019 @ 07:57
    Rasmus Fjord
    0

    Hey Thomas,

    Thank you for getting back to me, it gives me something to go on.

    So Im hitting a wall here. Ive installed the "Hangfire.LightInject" package, it matches our umbraco 8 solution just fine. So unless your modding the hangfire.lightinject package.

    The things ive done so far is to move the config of hangfire into an IUserComposer like this:

    [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
    public class HangfireJobsComposer : IUserComposer
    {
    
        public void Compose(Composition composition)
        {
            composition.Register<SomeService>(Lifetime.Singleton);
    
    
            // Configure hangfire
            var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
            var connectionString = System.Configuration
                .ConfigurationManager
                .ConnectionStrings["umbracoDbDSN"]
                .ConnectionString;
    
            var container = composition.Concrete as LightInject.ServiceContainer;
            GlobalConfiguration.Configuration.UseLightInjectActivator(container);
    
            GlobalConfiguration.Configuration
                .UseSqlServerStorage(connectionString, options)
                .UseConsole();
    
       }
    
    }
    

    And in my owinStartup im doing this:

       public override void Configuration(IAppBuilder app)
        {
            //ensure the default options are configured
            base.Configuration(app);
            try
            {
                var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
                app.UseHangfireDashboard("/hangfire", dashboardOptions);
                app.UseHangfireServer();
    
    
                RecurringJob.AddOrUpdate<SomeService>("SendMail", x => x.SendMail(null), Cron.HourInterval(12));
    
    
            }
            catch (InvalidOperationException ex)
            {
                // JobStorage.Current is not configured when installing updates
                if (!ex.Message.StartsWith("JobStorage.Current"))
                    throw;
            }
    

    }

    So hangfire spins up just fine, and my job is registered, when im fireing my job it give me this error:

        System.NullReferenceException: Object reference not set to an instance of an object.
       at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 148
       at LightInject.ScopeManager.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6325
       at Hangfire.LightInject.LightInjecterScope..ctor(ServiceContainer container)
       at Hangfire.LightInject.LightInjectJobActivator.BeginScope()
       at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
       at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_0.<PerformJobWithFilters>b__0()
       at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
       at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
       at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable`1 filters)
       at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
       at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)
    

    My service still looks like this:

     public class MyService
    

    { private readonly OtherService _otherService;

    public MyService(OtherService otherService)
    {
        _otherService= otherService;
    }
    
    public void SomeJob(PerformContext hangfire)
    {
        //DO IT! :D 
    }
    

    }

  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    Oct 09, 2019 @ 10:13
    Thomas Rydeen Skyldahl
    1

    yep, thats caused by not having HttpContext for the scope creation in Hangfire, we faked the HttpContext before any scope access, during scope creation and scope disposal,

    That means you will need to source from Hangfire.LightInject and modify it to create a fake http context before any scope access or component activation.

    or resort to a seperate container for Hangfire usage, but that kind of defeats the point.

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 09, 2019 @ 10:21
    Rasmus Fjord
    0

    ahh okay, i was missing the point of where the httpcontext was missing for lightinject. Yeah i thought of doing it all in seperate container but as you say that defeats the whole purpose.

    I just added the hangfire.lightinject to my project, so i can customize it. Do you have anything to share here?

  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    Oct 09, 2019 @ 10:27
    Thomas Rydeen Skyldahl
    1

    I have been talking to Shannon about the issue, hopefully we can get something done about it, as there are multiple better ways to store the scope today than inside the HttpContext.Items collection.

    What we have done is fake a http context inside LightInjectServiceProvider.GetService() LightInjectServiceProvider.Dispose()

    LightInjectServiceScope.Dispose()

    With code like this before any code inside the methods mentioned above.

            // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if(HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "http://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    

    do note that you can end up with scopes not being disposed if the thread changes during the execution of Hangfire jobs, its a risk we are willing to take, but it depends on your requirements and usage.

  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    Oct 09, 2019 @ 10:38
    Thomas Rydeen Skyldahl
    1

    Oh sorry

    In your code that only uses Hangfire.LightInject

    the naming is different.

    • LightInjectJobActivator.ActivateJob()
    • LightInjectJobActivator.BeginScope()
    • LightInjecterScope.Resolve()
    • LightInjectJobActivator.DisposeScope()
  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 09, 2019 @ 11:15
    Rasmus Fjord
    1

    Awesome Thomas!

    IT WORKS :D

    enter image description here

    For people comming here to look for a solution, as you can see its a bit messy. It should work if you follow my sudo files above. And you have to pull in Hangfire.LightInject into your project(or build it on the sideline), and here your file LightInjectJobActivator.cs Should look like this:

     public class LightInjectJobActivator : JobActivator
    {
        private readonly ServiceContainer _container;
        internal static readonly object LifetimeScopeTag = new object();
    
        public LightInjectJobActivator(ServiceContainer container, bool selfReferencing = false)
        {
            if (container == null)
                throw new ArgumentNullException("container");
    
            this._container = container;
    
        }
    
        public override object ActivateJob(Type jobType)
        {
            // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            // this will fail if you do self referencing job queues on a class with an interface:
            //  BackgroundJob.Enqueue(() => this.SendSms(message)); 
            var instance = _container.TryGetInstance(jobType);
    
            // since it fails we can try to get the first interface and request from container
            if (instance == null && jobType.GetInterfaces().Count() > 0)
                instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
    
            return instance;
    
        }
    
        public override JobActivatorScope BeginScope()
        {
            // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            return new LightInjecterScope(_container);
        }
    
    }
    
    class LightInjecterScope : JobActivatorScope
    {
        private readonly ServiceContainer _container;
        private readonly Scope _scope;
    
        public LightInjecterScope(ServiceContainer container)
        {  
    
            _container = container;
    
            _scope = _container.BeginScope();
        }
    
        public override object Resolve(Type jobType)
        { // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            var instance = _container.TryGetInstance(jobType);
    
            // since it fails we can try to get the first interface and request from container
            if (instance == null && jobType.GetInterfaces().Count() > 0)
                instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
    
            return instance;
    
        }
    
        public override void DisposeScope()
        {
            // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            _scope?.Dispose();
        }
    }
    

    A giant thank you to Thomas.

  • Steve 139 posts 318 karma points
    17 days ago
    Steve
    0

    Hi Rasmus,
    I've followed (most of) your example but now getting an error
    enter image description here
    I didn't follow the step of putting the config of Hangfire into an IUserComposer but instead added

     var container = new LightInject.ServiceContainer();
                // registrations
                GlobalConfiguration.Configuration.UseLightInjectActivator(container);
    to the owin file.
    Note that I followed this example to install and configure Hangfire.
    (this is all new to me and trying to keep it as simple as possible)
    I want to run some C# code to export data on demand or on a schedule.
    It seems like the LightInjectActivator is not initializing properly but I'm not completely sure.
    Do you have any suggestions?
    Thanks, Steve

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    17 days ago
    Rasmus Fjord
    0

    Hey Steve!

    Thanks for reaching out! Did you implement your own version of Hangfire.Lightinject where you fake the httpcontext? As Thomas suggests above? Because that was what did the trick for me.

  • Steve 139 posts 318 karma points
    17 days ago
    Steve
    0

    Hi Rasmus,
    Thanks for your reply!
    I downloaded the Hangfire.LightInject source from GitHub and replaced the contents of LightInjectJobActivator.cs with your code above, built the project on the side and put the .dll into the Umbraco 8 project.
    Hangfire had been operating correctly with simple tasks (context.WriteLine... ) so it seems like it is basically set up properly however I think the trouble is combining the code from Sabastiaan's tutorial with the GlobalConfiguration.Configuration.UseLightInjectActivator(container);
    code to use LightInject.
    Any ideas of how to correctly add that code or change the Hangfire configuration to allow use of the 'UseLightInjectActivator'?
    Sorry if my terminology is a little off - this is all new to me!
    Steve

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    17 days ago
    Rasmus Fjord
    0

    Good morning Steve :)

    NO worries about terminology, let worry about a solution 🤓👍

    SO lets try an compare setups, im running an umbraco 8.1.3.

    Yes Hangfire it self works, if you dont touch anything Umbraco related and Dependency Injection related. Correct?

    The issues I had, the first one was getting it to work with DI(Dependency Injection). To Fix this we are using the Hangfire.LightInject package, but the other issue is that if we touch any Umbraco stuff, like the contentCache, we get another error, because Hangfire is running in a not so weblike context and Umbraco is dependent on the httpContext.

    So for it to work we have to fake the httpcontext, and this is why we have to roll our own version of Hangfire.LightInject where we change the code inside LightInjectJobActivator.

    My file looks like this:

    public class LightInjectJobActivator : JobActivator
    {
        private readonly ServiceContainer _container;
        internal static readonly object LifetimeScopeTag = new object();
    
        public LightInjectJobActivator(ServiceContainer container, bool selfReferencing = false)
        {
            if (container == null)
                throw new ArgumentNullException("container");
    
            this._container = container;
    
        }
    
        public override object ActivateJob(Type jobType)
        {
            // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            // this will fail if you do self referencing job queues on a class with an interface:
            //  BackgroundJob.Enqueue(() => this.SendSms(message)); 
            var instance = _container.TryGetInstance(jobType);
    
            // since it fails we can try to get the first interface and request from container
            if (instance == null && jobType.GetInterfaces().Count() > 0)
                instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
    
            return instance;
    
        }
    
        public override JobActivatorScope BeginScope()
        {
            // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            return new LightInjecterScope(_container);
        }
    
    }
    
    class LightInjecterScope : JobActivatorScope
    {
        private readonly ServiceContainer _container;
        private readonly Scope _scope;
    
        public LightInjecterScope(ServiceContainer container)
        {  
    
            _container = container;
    
            _scope = _container.BeginScope();
        }
    
        public override object Resolve(Type jobType)
        { // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            var instance = _container.TryGetInstance(jobType);
    
            // since it fails we can try to get the first interface and request from container
            if (instance == null && jobType.GetInterfaces().Count() > 0)
                instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
    
            return instance;
    
        }
    
        public override void DisposeScope()
        {
            // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
            // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
            if (HttpContext.Current == null)
            {
                HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                    new HttpResponse(new StringWriter()));
            }
    
            _scope?.Dispose();
        }
    }
    

    So now we should have some components that works, now we need to initate everything correctly. In my setup, I have this process split into 2 areas, my OwinStartup.cs and my HangfireJobsComposer.cs

    HangfireJobsComposer.cs

    [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
        public class HangfireJobsComposer : IComposer
        {
    
            public void Compose(Composition composition)
            {
    
                // Configure hangfire
                var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
                var connectionString = System.Configuration
                    .ConfigurationManager
                    .ConnectionStrings["umbracoDbDSN"]
                    .ConnectionString;
    
                var container = composition.Concrete as LightInject.ServiceContainer;
    
                GlobalConfiguration.Configuration
                    .UseSqlServerStorage(connectionString, options)
                    .UseConsole()
                    .UseLightInjectActivator(container);
    
    composition.Register<InvitationJobs>(Lifetime.Singleton);
    
         }
    

    OwinStartup.cs

          public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
            {
    
                public override void Configuration(IAppBuilder app)
                {
                    //ensure the default options are configured
                    base.Configuration(app);
                    try
                    {
                        var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
                        app.UseHangfireDashboard("/hangfire", dashboardOptions);
                        app.UseHangfireServer();
    
      RecurringJob.AddOrUpdate<InvitationJobs>("DeleteOldInvitations", x => x.DeleteOldInvitations(null), Cron.HourInterval(23));
    
        }
     }
    }
    

    So this is my setup for hangfire. Now lets look at my InvitationJobs.

       public class InvitationJobs
        {
            private readonly IInvitationDbService _invitationDbService;
    
            public InvitationJobs(
                IInvitationDbService invitationDbService
            )
            {
                _invitationDbService = invitationDbService;
    
            }
    
        [AutomaticRetry(Attempts = 0)]
            public void DeleteOldInvitations(PerformContext hangfire)
            {
                try
                {
                    var invitations = _invitationDbService.GetAll();
                    foreach (var invitation in invitations)
                    {
    
                            hangfire.WriteLine($"Deletes invitation {invitation.Id} for tenant {invitation.Tenant} on rental {invitation.RentalId} at state {invitation.State}");
                            _invitationDbService.Delete(invitation.Id);
                        }
                    }
                }
                catch (Exception e)
                {
                    Current.Logger.Error(this.GetType(), e, "Error on DeleteOldInvitations");
                    hangfire.WriteLine("ERROR ON removal of old invitations");
    
                }
                hangfire.WriteLine("Finished removal of old invitations");
            }
    
    }
    

    And again my invitationJobs is also registered in my hangfireJobComposer.

    Hope this gets you futher :)=

  • Steve 139 posts 318 karma points
    16 days ago
    Steve
    0

    Hi Rasmus,
    Thanks for taking the time to write up this very thorough reply!
    I've got Umbraco 8.2.1 installed and everything with Hangfire seems to be running correctly however in the tasks that I need to perform require the HttpContext so I'm pursuing this thread with keen interest.
    Thanks for all of the code.
    I've added to the project and working on integrating with what I've already got set up, including resolving a few minor glitches.
    Learning more about Umbraco 8 by the minute!
    Thanks, Steve

  • Steve 139 posts 318 karma points
    16 days ago
    Steve
    0

    Hi Rasmus,
    I followed everything in your code as best as I could but still seem to be getting a NULL error related to the type characteristic of the job.
    I simplified InvitationJob.cs to the following.
    enter image description here
    I had to add

    public interface IInvitationDbService
    {
    }
    


    Otherwise was getting an error "the type or namespace 'IInvitationDbService' could not be found.."
    Perhaps this is part of the problem? (DI is all new to me!)
    With it though the errors in Visual Studio disappear but see this when the job runs:
    enter image description here
    I checked the Hanfire source and it appears this error has something to do with resolving the type on the job being run.
    enter image description here
    Specifically:

    if (!context.BackgroundJob.Job.Method.IsStatic)
                    {
                        instance = scope.Resolve(context.BackgroundJob.Job.Type);
    
                        if (instance == null)
                        {
                            throw new InvalidOperationException(
                                $"JobActivator returned NULL instance of the '{context.BackgroundJob.Job.Type}' type.");
                        }
                    }
    


    That's the only reference to this error I could find.
    It seems to me that the issue in my job class file.
    Can you send the full code of your InvitationJobs.cs file or let me know how to resolve the error that comes up on the IInvitationDbServive ?
    Thanks - and I definitely owe you a🍺(or two - ).
    Steve

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    16 days ago
    Rasmus Fjord
    1

    A quick answer, sorry for the whole misunderstanding :) the invitationservice, is something from my code, its just an example of how we are using hangfire :)

    So everything besides the invitationjobs is our setup for hangfire, while the InvitationJobs is one of many hangfire scheduled tasks running in our setup. I will just rewrite the example for you so the interface and DI wont confuse :)

    And again no worries about the being new to DI and Umbraco 8, same here, just started in august with umb8 and DI.

    This could be your job class instead of the our invitationJobs:

      public class StevesSuperJobClass
        {
    
    //THIS PART IS WHERE YOU HOOK IN YOUR DI COMPONENTS/SERVICES/REPOS/INSERT-ANOTHER-NAME-FOR-A-CLASS-THAT-DOES-STUFF-FOR-YOU :)
            public StevesSuperJobClass()
            {
    
    
            }
    
        [AutomaticRetry(Attempts = 0)]
            public void SomeJobSteveWantsPerformed(PerformContext hangfire)
            {
                try
                {
                    //LETS GO STEVE DO YOUR BEST HERE ! 
                    //SEND THOSE SEXY EMAILS :D 
     hangfire.WriteLine("write awesome console points to hangfire,");
    
                }
                catch (Exception e)
                {
                    Current.Logger.Error(this.GetType(), e, "Error on SomeJobSteveWantsPerformed");
                    hangfire.WriteLine("ERROR ON SomeJobSteveWantsPerformed");
    
                }
                hangfire.WriteLine("Finished SomeJobSteveWantsPerformed");
            }
    
    }
    

    A thing to notice, i dont know if you have the Hangfire.Console package installed? With this you can do as above, adding in the console message.

    Now in OwinStartup it should look like this:

    OwinStartup.cs

          public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
            {
    
                public override void Configuration(IAppBuilder app)
                {
                    //ensure the default options are configured
                    base.Configuration(app);
                    try
                    {
                        var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
                        app.UseHangfireDashboard("/hangfire", dashboardOptions);
                        app.UseHangfireServer();
    
      RecurringJob.AddOrUpdate<StevesSuperJobClass>("SomeJobSteveWants", x => x.SomeJobSteveWantsPerformed(null), Cron.HourInterval(23));
    
        }
     }
    }
    

    Hope this makes it a bit more clear :) Else please share you job class and owin startup up, assuming the rest is already in place. Also ensure that you hook up your new job class inside the HangfireJobsComposer as this

    composition.Register<StevesSuperJobClass>(Lifetime.Singleton);
    

    :D A beer is always welcome :) Lets get this thing fixed for you. If this dosnt work, lets go another around the christames tree, and try to describe to me what it is you want to achive, or we could a quick skype session.

  • Steve 139 posts 318 karma points
    16 days ago
    Steve
    0

    Hi Rasmus,
    Thanks for yet another detailed reply and your patience working through this.
    I've now got it working with access to the Http context in the Hangfire job.
    The solution in my case was just to have an empty class constructor for the job.

    public StevesSuperJobClass()
        {
    
    
        }
    


    I'm not using any DI components or other services right now but need to learn how that works. For now it is empty like above and job is running smoothly.
    Thanks again for all of your help - and let me know how to buy you a beer! :)
    Steve

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    1 week ago
    Rasmus Fjord
    0

    Wuhuu awesome :)

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 09, 2019 @ 18:21
    Rasmus Fjord
    0

    Actually i found another "issue" with DI/LightInject and Hangfire.

    In some of my services I call a membership helper to get an ipublishedcontent of a member.

    I do this like this:

     var helper = Current.Factory.GetInstance<MembershipHelper>();
    

    Normally this works in every other situation, but when used through hangfire and LightInject i get a Object not set error like this:

    Object reference not set to an instance of an object.
               at Umbraco.Web.Runtime.WebInitialComposer.<>c.<Compose>b__0_2(IFactory factory)
           at LightInject.PerRequestLifeTime.GetInstance(Func`1 createInstance, Scope scope)
           at Umbraco.Core.FactoryExtensions.GetInstance[T](IFactory factory)
           at my services.....
    

    In umbraco 7, when we needed this we had to ensure an umbraco context, gussing its something like this aswell, just like we are faking the httpcontext, but cant figure out how.

  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    Oct 09, 2019 @ 19:21
    Thomas Rydeen Skyldahl
    1

    you properly need to constructor inject the IUmbracoContextFactory and

    use it like

    using (var context = _umbracoContextFactory.EnsureUmbracoContext())
    {
      // do stuff here that require umbraco context
    }
    

    if you need the MembershipHelper you should inject the MembershipHelper into the constructor of your service and rely on then code we did earlier today to fake the context.

    When you access the Current.Factory you are going around that work, and have to fake the context again.

    but accessing the Current.Factory is an anti pattern in DI terms, as you dont rely on the lift time scopes and other stuff provided by the IoC container.

    Its going back to the Service Locator Pattern and you should try to avoid that.

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 09, 2019 @ 21:17
    Rasmus Fjord
    0

    Hey again Thomas,

    im starting to owe you a beer ;)

    So, ive tried 2 things, one thing works, but i shouldnt use it because it defeats the purpose of the DI as you say. The other way around dosnt work.

    So this works: My constructor

         private readonly IUmbracoContextFactory _umbracoContextFactory;
        public MyService(
            IUmbracoContextFactory iUmbracoContextFactory
            )
        {
            _umbracoContextFactory = iUmbracoContextFactory;
        }
    

    The method :

        using (var context = _umbracoContextFactory.EnsureUmbracoContext())
                {
                    // do stuff here that require umbraco context
                    var helper = Current.Factory.GetInstance<MembershipHelper>();
                var member = helper.GetById(memberId);
                }
    

    The above didnt work before but i was missing the ensureumbracocontext.

    The way i want to do it is this, but this dosnt work: My constructor

         private readonly MembershipHelper _membershipHelper;
        public MyService(
            MembershipHelper membershipHelper
            )
        {
            _membershipHelper = membershipHelper;
        }
    

    The method

                var member = _membershipHelper.GetById(memberId);
    

    Its like im not allowed to inject the MembershipHelper, ive been looking into the source code and trying to find a service or something that can help me here to get ipublished member content, but nothing seems to work correct.

  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    Oct 09, 2019 @ 21:26
    Thomas Rydeen Skyldahl
    0

    I think LightInject also support Func factories like

    public class MyService {
        private readonly Func<MembershipHelper> _membershipHelperFactory;
        private readonly IUmbracoContextFactory _umbracoContextFactory
        public MyService(
        IUmbracoContextFactory umbracoContextFactory, Func<MembershipHelper> membershipHelperFactory)
        {
          _umbracoContextFactory = umbracoContextFactory;
          _membershipHelperFactory= membershipHelperFactory;
        }
        public void DoMagicStuffWithMember() 
        {
            using (var context = _umbracoContextFactory.EnsureUmbracoContext())
            {
                // do stuff here that require umbraco context
                var helper = _membershipHelperFactory();
                var member = helper.GetById(memberId);
            }
        }
      }
    

    But this could get you back to the old PerWebRequestScopeManagerProvider issue

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 10, 2019 @ 07:46
  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Nov 14, 2019 @ 14:03
    Rasmus Fjord
    0

    Hey Thomas,

    Hope you can help me a bit here again, thought it was working but not a 100%, so hopin you have another input.

    So im trying your above solution like this :

     using (var context = _umbracoContextFactory.EnsureUmbracoContext())
                {
                    // do stuff here that require umbraco context
                    var helper = _membershipHelperFactory(); 
                    var mem = helper.GetById(memberId);
                    l.UmbracoMember = new Umbraco.Models.MyMemberModel(mem);
    
                }
    

    The wierd thing is that the member itself is being pulled out, but not all the properties on the member, they return a NullException. The Umbraco.Models.MyMemberModel is just the generated modelsbuilder model.

    (update) Any ideas why the property binding is broken? Something with NuCache? The wierd thing is, if I step into the code, and step throught it, then sometimes the properties actually comes out.

    A stack trace, just for the "fun" of it :)

    System.NullReferenceException: Object reference not set to an instance of an object.
    

    at Umbraco.Web.Templates.TemplateUtilities.ParseInternalLinks(String text, Boolean preview, UmbracoContext umbracoContext) in d:\a\1\s\src\Umbraco.Web\Templates\TemplateUtilities.cs:line 22 at Umbraco.Web.PropertyEditors.ValueConverters.TextStringValueConverter.ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, Object source, Boolean preview) in d:\a\1\s\src\Umbraco.Web\PropertyEditors\ValueConverters\TextStringValueConverter.cs:line 32 at Umbraco.Core.Models.PublishedContent.PublishedPropertyType.ConvertSourceToInter(IPublishedElement owner, Object source, Boolean preview) in d:\a\1\s\src\Umbraco.Core\Models\PublishedContent\PublishedPropertyType.cs:line 208 at Umbraco.Web.PublishedCache.NuCache.Property.GetInterValue(String culture, String segment) in d:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\Property.cs:line 192 at Umbraco.Web.PublishedCache.NuCache.Property.GetValue(String culture, String segment) in d:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\Property.cs:line 211 at Umbraco.Web.PublishedPropertyExtension.Value[T](IPublishedProperty property, String culture, String segment, Fallback fallback, T defaultValue) in d:\a\1\s\src\Umbraco.Web\PublishedPropertyExtension.cs:line 39 at Umbraco.Web.PublishedContentExtensions.Value[T](IPublishedContent content, String alias, String culture, String segment, Fallback fallback, T defaultValue) in d:\a\1\s\src\Umbraco.Web\PublishedContentExtensions.cs:line 155

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    30 days ago
    Rasmus Fjord
    0

    So i found out that if i loop over all the properties before mapping it to my modelsbuilder model it worked. #hacky

     using (var context = _umbracoContextFactory.EnsureUmbracoContext())
                {
                    var umbracoContext = context.UmbracoContext;
    
                    // do stuff here that require umbraco context
                    var mem = _membershipHelperFactory.Invoke().GetById(l.MemberId);
                    var props = mem.Properties;
                    foreach (var prop in props)
                    {
                        var p = prop.GetValue();
                    }
    
                    l.UmbracoMember = new Umbraco.Models.MyMember(mem);
    
                }
    
  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    30 days ago
    Thomas Rydeen Skyldahl
    0

    Great you found a solution, but not so great that we have to hack our way around stuff because of a Scope Manager in Umbraco

  • Thomas Rydeen Skyldahl 15 posts 130 karma points
    Oct 09, 2019 @ 21:38
    Thomas Rydeen Skyldahl
    0

    I have also found another way to get around the HttpContext issue but its not fully tested yet.

    by replacing the ScopeManagerProvider on the Container we can use another scope manager when there is no HttpContext.

        // its important to inherit from the Umbraco MixedLightInjectScopeManagerProvider 
        // as they are testing for it with the 'is' keyword
        public class ReworkedMixedLightInjectScopeManagerProvider : MixedLightInjectScopeManagerProvider, IScopeManagerProvider
        {
           private readonly IScopeManagerProvider _noneHttpProvider;
           public ReworkedMixedLightInjectScopeManagerProvider()
        {
            _noneHttpProvider = new PerLogicalCallContextScopeManagerProvider();
        }
    
        // override via explicit interface implementation 
        IScopeManager IScopeManagerProvider.GetScopeManager(IServiceFactory factory)
        {
            if (HttpContext.Current == null)
            {
                return _noneHttpProvider.GetScopeManager(factory);
            }
    
            return base.GetScopeManager(factory);
        }
    }
    

    and then assigning this as the ScopeManagerProvider on the container

    container.ScopeManagerProvider = new ReworkedMixedLightInjectScopeManagerProvider();
    

    This is in no way tested so be careful using it.

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    Oct 10, 2019 @ 05:55
    Rasmus Fjord
    0

    Hey Thomas,

    Awesome stuff, I just tested it and its a no go or im doing it wrong.

      private readonly MembershipHelper _membershipHelperFactory;
        public MyService(
               Func<MembershipHelper> membershipHelperFactory
            )
        {
       _membershipHelperFactory = membershipHelperFactory();
        }
    
    //USING IT LIKE SO:
       var helper = _membershipHelperFactory();
           var member = helper.GetById(memberId);
    
  • Bo Jacobsen 308 posts 1360 karma points
    16 days ago
    Bo Jacobsen
    1

    Hi, i have an idea.

    What if you used hangfire as a webhook only?

    So instead of running methods in a hangfire event, then only call a url with authentication?

    Then you can make an UmbracoApi and get a full context thread?

  • Rasmus Fjord 655 posts 1535 karma points c-trib
    16 days ago
    Rasmus Fjord
    0

    Its a great idea for those who can live with stuff only running in a web context, and dosnt take long time to run, and also you dont want feedback inside of hangfire.

    The main idea we use hangfire for is longer running tasks, something that might exceed the webserver process lifetime, also we use the logs on hangfire aswell(of course with other logging aswell).

    But generally, i got it working with all i wanted.

Please Sign in or register to post replies

Write your reply to:

Draft