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 17 posts 159 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 4876 posts 14511 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 657 posts 1540 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 17 posts 159 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 30 posts 146 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 657 posts 1540 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 30 posts 146 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 657 posts 1540 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 30 posts 146 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 30 posts 146 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 657 posts 1540 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
    Nov 27, 2019 @ 18:46
    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 657 posts 1540 karma points c-trib
    Nov 27, 2019 @ 20:00
    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
    Nov 27, 2019 @ 21:12
    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 657 posts 1540 karma points c-trib
    Nov 28, 2019 @ 06:59
    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
    Nov 28, 2019 @ 20:05
    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
    Nov 29, 2019 @ 03:27
    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 657 posts 1540 karma points c-trib
    Nov 29, 2019 @ 07:08
    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
    Nov 29, 2019 @ 14:45
    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 657 posts 1540 karma points c-trib
    Dec 02, 2019 @ 13:33
    Rasmus Fjord
    0

    Wuhuu awesome :)

  • David Armitage 243 posts 923 karma points
    Jan 25, 2020 @ 10:48
    David Armitage
    0

    Got there in the end!

    Thanks Rasmus.

    For me I was having issues with LightInjectJobActivator stuff. I was attempting all sorts of stuff. Installing Hangfire.LightInject through nuget, navigating referencing issues then copying the new LightInjectJobActivator classes into my project.

    I later realised none of this is required.

    With the LightInjectJobActivator stuff.

    1. Download the package source from https://github.com/sbosell/Hangfire.LightInject

    2. Replace the LightInjectJobActivator.cs with the contents in Ramus' example.

    3. Build this then use the Hangfire.LightInject from this project and include it in your umbraco project.

    4. Once this is done you can pretty much copy and paste the rest of the stuff in Ramus' example.

    I plan to write a blog for this end to end starting with the hangfire install. There was a lot of scrolling up and down and referencing other forum posts.

    I will pop it all in one place.

  • Sebastiaan Janssen 4876 posts 14511 karma points MVP admin hq
    Jan 25, 2020 @ 11:53
    Sebastiaan Janssen
    0

    I've just been experimenting with this, finally, after reading all these posts.

    You don't even need to do all that, you can tell Hangfire to use any JobActivator you want. My composer looks like this:

    using Hangfire;
    using Hangfire.Console;
    using Hangfire.SqlServer;
    using Umbraco.Core;
    using Umbraco.Core.Composing;
    
    namespace Example.Namespace
    {
        [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
        public class HangfireJobsComposer : IComposer
        {
            public void Compose(Composition composition)
            {
                // Configure hangfire
                var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
                const string umbracoConnectionName = Constants.System.UmbracoConnectionName;
                var connectionString = System.Configuration
                    .ConfigurationManager
                    .ConnectionStrings[umbracoConnectionName]
                    .ConnectionString;
    
                var container = composition.Concrete as LightInject.ServiceContainer;
    
                GlobalConfiguration.Configuration
                    .UseSqlServerStorage(connectionString, options)
                    .UseConsole()
                    .UseActivator(new LightInjectJobActivator(container));
            }
        }
    }
    

    Note the .UseActivator(new LightInjectJobActivator(container)); at the end.

    For the LightInjectJobActivator class I used (and refactored) Rasmus' version. I'm not sure if the URL in there should point to an actual website though? Seems to work as it is.. I actually am not even sure why all these hoops are necessary, was it because you need to be able to use the ContentService and we need to wait for an Umbraco Context to exist first? Currently I'm not doing anything with ContentService so I am not sure I need the activator to be like this.

    My version just removes a bit of repetition of that Http Context initialization:

    using System;
    using System.IO;
    using System.Linq;
    using System.Web;
    using Hangfire;
    using LightInject;
    
    namespace Example.Namespace
    {
        public class LightInjectJobActivator : JobActivator
        {
            private readonly ServiceContainer _container;
    
            public LightInjectJobActivator(ServiceContainer container, bool selfReferencing = false)
            {
                _container = container ?? throw new ArgumentNullException(nameof(container));
            }
    
            public override object ActivateJob(Type jobType)
            {
                Context.InitializeFakeHttpContext();
    
                // 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().Any())
                    instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
    
                return instance;
            }
    
            [Obsolete("Please implement/use the BeginScope(JobActivatorContext) method instead. Will be removed in 2.0.0.")]
            public override JobActivatorScope BeginScope()
            {
                Context.InitializeFakeHttpContext();
                return new LightInjectScope(_container);
            }
        }
    
        internal class LightInjectScope : JobActivatorScope
        {
            private readonly ServiceContainer _container;
            private readonly Scope _scope;
    
            public LightInjectScope(ServiceContainer container)
            {
                _container = container;
                _scope = _container.BeginScope();
            }
    
            public override object Resolve(Type jobType)
            {
                Context.InitializeFakeHttpContext();
    
                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().Any())
                    instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
    
                return instance;
            }
    
            public override void DisposeScope()
            {
                Context.InitializeFakeHttpContext();
                _scope?.Dispose();
            }
        }
    
        internal static class Context
        {
            public static void InitializeFakeHttpContext()
            {
                // 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()));
                }
            }
        }
    }
    
  • Sebastiaan Janssen 4876 posts 14511 karma points MVP admin hq
    Jan 25, 2020 @ 11:57
    Sebastiaan Janssen
    0

    And for completion's sake, here's my OwinStartup class. The combination of these three should get you set up.

    using Hangfire;
    using Microsoft.Owin;
    using Owin;
    using Umbraco.Web;
    
    [assembly: OwinStartup("UmbracoStandardOwinStartup", typeof(UmbracoStandardOwinStartup))]
    namespace Example.Namespace
    {
        public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
        {
            public override void Configuration(IAppBuilder app)
            {
                //ensure the default options are configured
                base.Configuration(app);
    
                // Give hangfire a URL and start the server
                var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
                app.UseHangfireDashboard("/hangfire", dashboardOptions);
                app.UseHangfireServer();
    
                // Add jobs
                RecurringJob.AddOrUpdate<MyService>(x  => x.DoStuff(), Cron.Daily);
            }
        }
    }
    

    My only problem is that I need 1 Service to be injected into this OwinStartup class so I can make a list of jobs where just 1 variable changes based on a list of string (that list comes from the Service). But oh well, I can live with newing up that single Service.

  • Rasmus Fjord 657 posts 1540 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 30 posts 146 karma points
    Oct 09, 2019 @ 19:21
    Thomas Rydeen Skyldahl
    2

    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 657 posts 1540 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 30 posts 146 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 657 posts 1540 karma points c-trib
    Oct 10, 2019 @ 07:46
  • Rasmus Fjord 657 posts 1540 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 657 posts 1540 karma points c-trib
    Nov 15, 2019 @ 06:33
    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 30 posts 146 karma points
    Nov 15, 2019 @ 07:32
    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

  • David Armitage 243 posts 923 karma points
    Jan 27, 2020 @ 04:51
    David Armitage
    0

    Hi Rasmus,

    Did you every figure out a more solid solution for this?

    I have been experiencing the exact same issue. Your comment above also seemed to work for me bit I am just worried about other problems might come of the back of this.

  • Rasmus Fjord 657 posts 1540 karma points c-trib
    Jan 27, 2020 @ 06:50
    Rasmus Fjord
    0

    No sadly not :(

    But we ended up dishing Umbraco members for the parts we needed, but our case is not comparable though :(

    But for the time we used it, it worked for us, but we needed to loop over the properties, and if you debugged into this, you could see that the properties are in the "Properties" list, but still cannot get mapped.

    My hunt for the holy hack grail stopped here hehe :)

  • Thomas Rydeen Skyldahl 30 posts 146 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 657 posts 1540 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 352 posts 1529 karma points
    Nov 29, 2019 @ 09:48
    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 657 posts 1540 karma points c-trib
    Nov 29, 2019 @ 09:52
    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.

  • David Armitage 243 posts 923 karma points
    Jan 09, 2020 @ 14:13
    David Armitage
    0

    Hi Guys,

    I have followed the examples about.

    My InvitationJobs looks something like this.

    public class InvitationJobs { private readonly ISiteService _siteService;

        public InvitationJobs(ISiteService siteService)
        {
            _siteService = siteService;
        }
    
        [AutomaticRetry(Attempts = 0)]
        public void TestScheduledTask(PerformContext hangfire)
        {
            try
            {
                _siteService.InsertTest("test scheduled insert");
                hangfire.WriteLine("TestScheduledTask complete");
            }
            catch (Exception e)
            {
    
            }
        }
    }
    

    The schedule task seems to be firing every minute like intended. My service does not seem to be getting fired though.

    I am getting the following errors. Any ideas?

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

    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.Web.PerWebRequestScopeManager.getCurrentScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 129 at LightInject.ScopeManager.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6325 at LightInject.ServiceContainer.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 2587 at Hangfire.LightInject.LightInjecterScope..ctor(ServiceContainer container) at Hangfire.LightInject.LightInjectJobActivator.BeginScope() at Hangfire.JobActivator.BeginScope(JobActivatorContext context) at Hangfire.JobActivator.BeginScope(PerformContext context) at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.BackgroundJobPerformer.<>cDisplayClass90.

  • Rasmus Fjord 657 posts 1540 karma points c-trib
    Jan 10, 2020 @ 06:53
    Rasmus Fjord
    0

    So lets go through the checks :)

    Did you do the follwing:

    • Made a copy of the Hangfire.Lightinject project mentioned above?
    • Did you make the nessesary changes in the Hangfire.Lightinject project?
    • Did you add the custom ServiceContainer in owinstartup?
    • Did you register your service to a composition?

    Please share :)

  • David Armitage 243 posts 923 karma points
    Jan 25, 2020 @ 12:41
    David Armitage
    0

    Hi Guys,

    As promised I wrote a blog article to put all this together in one place.

    https://www.umbrajobs.com/blog/posts/2020/january/umbraco-8-scheduled-tasks-with-hangfire-dependency-injection/

    I think you can follow this guide and get up and running pretty easily if you closely follow each step.

    (Credit: Sebastiaan Janssen) https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/

    And obviously (Credit: Rasmus Fjord)

    Hope this helps someone. It's took me a fair while to get up and running. Just hope this speeds the process up for you.

    Regards

    David

  • David Armitage 243 posts 923 karma points
    Jan 28, 2020 @ 08:36
    David Armitage
    0

    Hey Peeps,

    Does anyone keep getting these errors. I have had it a few times and not sure how I fixed it. I think restarting my solution or something.

    I am getting it quite often now and want to get to the bottom of it.

    This is related to the hangfire / LightInject stuff.

    enter image description here

    Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.TypeLoadException: Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

    Source Error:

    An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

    Stack Trace:

    [TypeLoadException: Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.] Umbraco.Core.Composing.LightInject.LightInjectContainer.CreateServiceContainer() +0 Umbraco.Web.Composing.LightInject.LightInjectContainer.Create() in D:\a\1\s\src\Umbraco.Web\Composing\LightInject\LightInjectContainer.cs:24

    [TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0 System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) +91 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +105 Umbraco.Core.Composing.RegisterFactory.Create() in D:\a\1\s\src\Umbraco.Core\Composing\RegisterFactory.cs:45 Umbraco.Web.UmbracoApplicationBase.GetRegister() in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:30 Umbraco.Web.UmbracoApplicationBase.HandleApplicationStart(Object sender, EventArgs evargs) in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:63 Umbraco.Web.UmbracoApplicationBase.Application_Start(Object sender, EventArgs evargs) in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:74

    [HttpException (0x80004005): Exception has been thrown by the target of an invocation.] System.Web.HttpApplicationFactory.EnsureAppStartCalledForIntegratedMode(HttpContext context, HttpApplication app) +475 System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) +118 System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) +176 System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +220 System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +303

    [HttpException (0x80004005): Exception has been thrown by the target of an invocation.] System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +659 System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +89 System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +189

  • David Armitage 243 posts 923 karma points
    Jan 28, 2020 @ 09:23
    David Armitage
    0

    Further to this error.

    I have tracked it down to when I accidentally click clean solution. Building and re-building works ok but if I clean this error is presented and its very difficult to fix up.

    The only way I can seem to fix this up is by restoring in Git. Not ideal if I have already done a lot of work.

    I cant seem to figure this out but it's something to do with the LightInject been in my core project.

    I noticed when I do a clean there is 3 new dlls which make their way to the Web project. I am pretty sure it's related but not sure how to fix this yet. Ideally I would like to get this project cleaned up so these issues don't happen.

    This Hangfire stuff has been a nightmare to get up and running. Umbraco 8 has been awesome up to now but I think it's been a little let down with the scheduled tasks. I wish this was built into the core.

    enter image description here

  • David Armitage 243 posts 923 karma points
    Jan 28, 2020 @ 09:56
    David Armitage
    0

    So I actually figured out how to fix this...

    So I had edited the source code of the Hangfire.LightInject project. Updated the Activator.

    I then used this DLL to include in my project. I added this into my WEB project. This seamed to work but as soon as I click clean solution is Visual Studios it breaks everything as per above and I needed to restore.

    To stop this happening I deleted the reference to Hangfire.LightInject.dll from my Web project and instead moved this to the Core project.

    I tested and all the dependency injection stuff is still working. I also tested cleaning my solution and the website built and run fine.

    I updated my blog article with a full step by step how to get up and running with this including all the issues I encountered along the way.

    https://www.umbrajobs.com/blog/posts/2020/january/umbraco-8-scheduled-tasks-with-hangfire-dependency-injection/

    Regards

    David

  • Sebastiaan Janssen 4876 posts 14511 karma points MVP admin hq
    Jan 28, 2020 @ 09:57
    Sebastiaan Janssen
    0

    Scheduled tasks ARE built into the core, they don't have a lovely interface like this though.

    In 99.99999999% of the time a "clean" or a "rebuild" of the solution is never necessary, you do not need to do this. A regular build WILL write the changes to the bin folder, if something doesn't appear to be working, "clean" or "rebuild" is not the answer, the answer is to review why it wasn't working. :-)

    That said, to fix your problem: the 3 dlls you're missing are most likely NOT referenced in your website project, with no reference a fresh version will not be copied over when you build (probably leading you to see weird errors, prompting you to "clean" or "rebuild").

    This is, by the way, a VS setup problem and not something that Umbraco is responsible for.

    It's hard to give you a specific example, but you can test this easily:

    • Delete all the files in the bin folder of your website (locally)
    • Do a regular build in VS
    • See which dlls are missing and add a reference to them in Visual Studio
    • Rinse & repeat until all required dlls show up during a regular build after the bin folder was empty
  • Sebastiaan Janssen 4876 posts 14511 karma points MVP admin hq
    Jan 28, 2020 @ 09:58
    Sebastiaan Janssen
    0

    I don't know if you've seen my previous replies, but you can create a class and use it, instead of recompiling Hangfire.LightInject, you don't need that project to be installed at all :-)

    https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection#comment-316038

  • David Armitage 243 posts 923 karma points
    Jan 28, 2020 @ 11:36
    David Armitage
    0

    Hi Sebastiaan,

    Thanks for your quick response.

    I did consider your comments prior. A couple of follow up points...

    I never usually touch the clean solution but sometime I miss click when I am in a rush. Its just annoying when this happens and the solution breaks. That and when there's multiple people working on the same project in git it becomes a nightmare. There is always someone who clicks it then the possibility of checking in broken code.

    Regarding the built in Scheduled tasks. I did look at this. With Umbraco 8 I noticed all the config had disappeared form the umbracoSettings.config. I google around and I am almost certain I read somewhere that it had been removed from Umbraco 8. That's why I moved back to Hangfire which had proven to be awesome with my Umbraco 8 projects. I was actually meaning I wish Hangfire had been built into Umbraco any actually. It's been a tried and tested feature in Umbraco 7 and from the comments around it's looks as though widely used.

    I also saw you comment before regarding not needing the do the dll stuff with the Activator. I actually tried to implement it but I was getting an error. From memory I think it was saying something like the container is null when it hit my Owin startup. I fiddled about for an hour but couldn't get it to work. I ended up rolling back to the working solution which proved to have it's own issues as per this message.

    Anyhow it seems to be working now.

    I might give you solution another go through for completeness. It makes much more since than hacking at a dll and referencing it in my project.

    Cheers for your help on all this.

    Both you and Rasmus have been amazing help. I owe you both a beer if I see you at codegarden anytime.

    Regards

    David

  • Sebastiaan Janssen 4876 posts 14511 karma points MVP admin hq
    Jan 28, 2020 @ 11:49
    Sebastiaan Janssen
    0

    Sure thing! Clean solution should be harmless, most of the time it is that you're missing a reference somehow and the correct dlls don't get copied in after. So it is actually a great idea to use it to test that your team has added the correct references! ;-)

    In v8 we have an excellent BackgroundTaskRunner -https://our.umbraco.com/documentation/Reference/Scheduling/

    However it does not come with any UI, just code. I still think that Hangfire is by far superior for this due to the UI and built in logging and retries, etc.

    The scheduler you're talking about was very very old and very very silly (it would just ping a public endpoint, so if someone guessed the URL to your content importer and called it a 100 times, your site would go down).

    I don't know why the Activator wouldn't work for you but it might again relate to missing references. It's working beautifully for me!

    At some point I'll split out this code into an example project but don't have the time right now to make this into a nice thing :-)

  • David Armitage 243 posts 923 karma points
    Jan 28, 2020 @ 11:55
    David Armitage
    0

    Thanks for pointing the BackgroundTaskRunner out. I really didn't know about that.

    That's a viable solution for a lot of people. Sometimes I don't need the logging anyway.

Please Sign in or register to post replies

Write your reply to:

Draft