Copied to clipboard

Flag this post as spam?

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


  • K.Garrein 164 posts 629 karma points
    Jul 23, 2019 @ 10:29
    K.Garrein
    0

    Scheduled tasks in Umbraco 8 no longer available?

    Hey.

    I need to setup a recurring task for our new project. But it looks like scheduled tasks are gone from umbracoSettings.config in version 8?

    How do I setup a scheduled task in this new Umbraco version?

    Thank you. Kris.

  • Garðar Þorsteinsson 119 posts 565 karma points
    Jul 23, 2019 @ 10:51
    Garðar Þorsteinsson
    4

    Hi,

    I would recommend using https://www.hangfire.io/

  • K.Garrein 164 posts 629 karma points
    Jul 23, 2019 @ 10:52
    K.Garrein
    0

    Thanks I will check it out

  • Nik 1617 posts 7263 karma points MVP 7x c-trib
    Jul 23, 2019 @ 11:18
    Nik
    1

    Yep HangFire is recommended at an alternative, the built in facility was removed from v8. :-)

  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Jul 23, 2019 @ 11:26
    Sebastiaan Janssen
    3

    I've blogged about Hangfire as well, Hangfire is fantastic: https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/

  • K.Garrein 164 posts 629 karma points
    Jul 23, 2019 @ 12:29
    K.Garrein
    0

    Your tutorial makes some jumps that are not clear to me, I get to see a partial Hangfire layout in the settings section, but it looks like css and js are not loading.

  • K.Garrein 164 posts 629 karma points
    Jul 23, 2019 @ 12:52
    K.Garrein
    102

    What's not indicated in your tutorial is that you need to modify the web.config:

    <add key="Umbraco.Core.ReservedPaths" value="~/hangfire" />
    

    After that change it seems to work.

    Cheers.

  • Joe Begly 19 posts 110 karma points
    Jan 22, 2020 @ 20:05
    Joe Begly
    0

    Sebastiaan, Since comments on your blog are closed I just wanted to let you know how much I appreciate your tutorial! Excellent material!

  • Bo Jacobsen 608 posts 2406 karma points
    Jan 22, 2020 @ 23:38
    Bo Jacobsen
    0

    Hi Sebastiaan.

    Can you Dependency inject, like below? And how would you differ what methods to run between environments?

        using Hangfire;
        using Hangfire.Server;
    
        namespace Cultiv.Hangfire
        {
            public class ScheduleHangfireJobs
            {
                private readonly IContentService _contentService;
    
                public void DoSomething(IContentService contentService)
                {
                    _contentService = contentService;
                    RecurringJob.AddOrUpdate(() => DoIt(null), Cron.HourInterval(12));
                }
    
                public void DoIt(PerformContext context)
                {
                    // Do something!
                }
            }
        }
    
  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Jan 23, 2020 @ 15:48
    Sebastiaan Janssen
    0

    I don't know, have you tried it? 🙈

    For enviroment-specific methods, I would "just" introduce an AppSetting in web.config with the environment name. When you deploy to each environment you can do a web.config transform to set the environment name to the target environment.

  • Kevin Jump 2343 posts 14890 karma points MVP 8x c-trib
    Jan 23, 2020 @ 15:57
    Kevin Jump
    0

    Hi,

    Also you could use the runtime state to workout what type of server you are on (master, slave, standalone) as you probably don't want to run your scheduled task on all the severs

    see https://our.umbraco.com/Documentation/Reference/Scheduling/#using-runtimestate

  • Bo Jacobsen 608 posts 2406 karma points
    Jan 23, 2020 @ 16:44
    Bo Jacobsen
    0

    Hi Kevin.

    Is there a way to set the runtime server role?

  • Kevin Jump 2343 posts 14890 karma points MVP 8x c-trib
    Jan 23, 2020 @ 20:49
    Kevin Jump
    0

    Hi

    Yeah if you mean can i force a server to be master / slave then you can do it via an IServiceRegister class (see https://our.umbraco.com/Documentation/Getting-Started/Setup/Server-Setup/Load-Balancing/flexible-advanced#explicit-master-scheduling-server )

    I usally have a bit of code like this :

    public class MasterServerRegistrar : IServerRegistrar2
    {
        public IEnumerable<IServerAddress> Registrations
        {
            get { return Enumerable.Empty<IServerAddress>(); }
        }
    
        public ServerRole GetCurrentServerRole()
        {
            return ServerRole.Master;
        }
    
    
        public string GetCurrentServerUmbracoApplicationUrl()
        {
            // NOTE: If you want to explicitly define the URL that your application is running on,
            // this will be used for the server to communicate with itself, you can return the 
            // custom path here and it needs to be in this format:
            // http://www.mysite.com/umbraco
    
            return null;
        }
    }
    
    public class FrontEndReadOnlyServerRegistrar : IServerRegistrar2
    {
        public IEnumerable<IServerAddress> Registrations
        {
            get { return Enumerable.Empty<IServerAddress>(); }
        }
        public ServerRole GetCurrentServerRole()
        {
            return ServerRole.Slave;
        }
        public string GetCurrentServerUmbracoApplicationUrl()
        {
            return null;
        }
    }
    

    and then in a Component, i read something from web.config to work out if it should be a master or slave server. (set when the site is deployed).

    var masterOrSlave = ConfigurationManager.AppSettings["MasterSlave"];
    
    if (masterOrSlave.InvariantEquals("Master"))
    {
       // ServerRegistrarResolver.Current.SetServerRegistrar(new MasterServerRegistrar());
    }
    else if (masterOrSlave.InvariantEquals("Slave"))
    {
       ServerRegistrarResolver.Current.SetServerRegistrar(new FrontEndReadOnlyServerRegistrar());
    }
    

    Kevin

  • Bo Jacobsen 608 posts 2406 karma points
    Jan 23, 2020 @ 21:57
    Bo Jacobsen
    0

    Thanks Kevin, that's very useful and something i will definitely implement.

  • Joe Begly 19 posts 110 karma points
    Jan 23, 2020 @ 16:08
    Joe Begly
    0

    I ran into problems trying inject dependencies into the "DoSomething" method. I actually needed the IContentService too, oddly enough. I don't remember the details of whether it just wouldn't compile or if the service wasn't instantiated properly, but I couldn't get it working.

    The best (i.e. quickest...this was a project with a tight deadline and budget) solution I came up with was to make a controller action that performed the work I needed done. Inside the "DoSomething" method, I made a webrequest to the url of my controller action. It got the job done and it's been running like a champ in production, every 15 minutes :-)

    There are probably better solutions, but I just wanted to share that approach in case someone has a similar issue and this happens to be 'good enough'

    In the below example, the values I pull from my ConfigurationHelper class are the url and parameters of the Controller action I created.

    public class ScheduleHangfireJobs
    {
        public void DoSomething()
        {
            RecurringJob.AddOrUpdate(() => DoIt(null), "*/15 * * * *");
        }
    
        public void DoIt(PerformContext context)
        {
            //Since the import requires umbraco dependencies, it is easier
            //to just request it over the web than manage the dependencies in the background
            //task.
            WebClient client = new WebClient();
            var url = ConfigurationHelper.GetConfigSetting<string>("ProductionImportUrl");
            var key = ConfigurationHelper.GetConfigSetting<string>("ProductionImportKey");
            string downloadString = client.DownloadString($"{url}?key={key}&source=ScheduledJob");
        }
    }
    
  • Bo Jacobsen 608 posts 2406 karma points
    Jan 23, 2020 @ 16:38
    Bo Jacobsen
    0

    When i think about it, you would properly have to do it like this.

    public class ScheduleHangfireJobs
    {
        private readonly IContentService _contentService;
    
        public ScheduleHangfireJobs(IContentService contentService)
        {
            _contentService = contentService;
        }
    
        public void DoSomething()
        {
            RecurringJob.AddOrUpdate(() => DoIt(null), "*/15 * * * *");
        }
    
        public void DoIt(PerformContext context)
        {
            //Since the import requires umbraco dependencies, it is easier
            //to just request it over the web than manage the dependencies in the background
            //task.
            WebClient client = new WebClient();
            var url = ConfigurationHelper.GetConfigSetting<string>("ProductionImportUrl");
            var key = ConfigurationHelper.GetConfigSetting<string>("ProductionImportKey");
            string downloadString = client.DownloadString($"{url}?key={key}&source=ScheduledJob");
        }
    }
    
  • Joe Begly 19 posts 110 karma points
    Jan 23, 2020 @ 18:07
    Joe Begly
    0

    Yep, pretty sure that's what I started with. Again, I don't remember the details of why I bailed, unless it's something like the concrete version of the content service that you'd inject from the startup class not having everything it needs at the point it runs, then errors when you try to use the injected object in your Hangfire class.

    I could be totally wrong and maybe I just missed something obvious.

  • Marshall Penn 84 posts 269 karma points
    Jan 24, 2020 @ 11:17
    Marshall Penn
    0

    Scheduled Tasks in Umbraco 8 are handled via classes inherited from RecurringTaskBase. Here is a scheduled task we run on a live U8 project:

    [ComposeAfter(typeof(SettingsDataComposer))]
    public class SharePriceRunnerComposer : ComponentComposer<SharePriceRunnerComponent> {
    }
    
    public class SharePriceRunnerComponent : IComponent {
    
        private IProfilingLogger _logger;
        private IRuntimeState _runtime;
        private BackgroundTaskRunner<IBackgroundTask> _sharePriceRunnerRunner;
        private readonly SettingsDataComponent _settings;
        private readonly IContentService _contentService;
    
        public SharePriceRunnerComponent(IProfilingLogger logger, IRuntimeState runtime, SettingsDataComponent settings, IContentService contentService) {
            _settings = settings;
            _logger = logger;
            _runtime = runtime;
            _contentService = contentService;
            _sharePriceRunnerRunner = new BackgroundTaskRunner<IBackgroundTask>("GetSharePrice", _logger);
        }
    
        public void Initialize() {
            int delayBeforeWeStart = 60000; // 60000ms = 1min
            int howOftenWeRepeat = 1800000; //300000ms = 5mins 1800000 = 30 mins
    
            if (_settings.IsScheduledTaskServer(Environment.MachineName) && _settings.DoScheduledTasks) {
                _logger.Info<GetSharePrice>("Initialize- GetSharePrice - {ServerRole}", _runtime.ServerRole);
    
                if (_settings.SharePriceFreqMinutes > 0) {
                    howOftenWeRepeat = (_settings.SharePriceFreqMinutes * 60000);
                }
    
                var task = new GetSharePrice(_sharePriceRunnerRunner, delayBeforeWeStart, howOftenWeRepeat, _runtime, _logger, _settings, _contentService);
    
                //As soon as we add our task to the runner it will start to run (after its delay period)
                _sharePriceRunnerRunner.TryAdd(task);
            }
        }
    
        public void Terminate() {
        }
    }
    
    // Now we get to define the recurring task
    public class GetSharePrice : RecurringTaskBase {
        private IRuntimeState _runtime;
        private IProfilingLogger _logger;
        private SettingsDataComponent _settings = null;
        private readonly IContentService _contentService;
    
        public GetSharePrice(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayBeforeWeStart, int howOftenWeRepeat, IRuntimeState runtime, IProfilingLogger logger, SettingsDataComponent settings, IContentService contentService)
            : base(runner, delayBeforeWeStart, howOftenWeRepeat) {
            _runtime = runtime;
            _logger = logger;
            _settings = settings;
            _contentService = contentService;
        }
    
        public override bool PerformRun() {
    
    
            SharePriceService pricesservice = new SharePriceService(_settings);
            ReturnClass outcome = pricesservice.UpdateSharepriceCache();
    
            if (!outcome.Success) {
                string errormessage = outcome.GetBothMessages();
                _logger.Error<GetSharePrice>("Get Share Price-{ServerRole}", _runtime.ServerRole);
                _logger.Error<GetSharePrice>("Error {errormessage}", errormessage);
            } else {
                _logger.Info<GetSharePrice>("Get Share Price successfull-{ServerRole}", _runtime.ServerRole);
            }
    
            // If we want to keep repeating - we need to return true
            // But if we run into a problem/error & want to stop repeating - return false
            return true;
        }
    
        public override bool IsAsync => false;
    }
    

    Our setup has load balancing so we use the machine name to only set the process to run on a specific machine.

    Also we need the settings from "SettingsDataComponent settings", so we ensure that that one is initialised before this one

  • Joe Begly 19 posts 110 karma points
    Jan 24, 2020 @ 14:20
    Joe Begly
    0

    Wow this is cool! A nice alternative to the Hangfire approach. Aside from logs, is there a good way to monitor success/failure when scheduling tasks this way? The Hangfire console is really nice and gives you a history, allows you to run task on demand etc.

Please Sign in or register to post replies

Write your reply to:

Draft