Schedule tasks in NopCommerce 4.0

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
6 years ago
Hi,

I created a new schedule task inside a plugin in NopCommerce 4.0.

I tried to run the task manually from the administration page. After I pressed "Run now" on my task, nopcommerce shows the green "Schedule task was run" message, but the Execute() Method is never called.

The type field for my task inside the database is "TypeName, DLL-Name" ("Plugin.Name.MyTask, Plugin.Name").

But in Nop.Services.Tasks.Task on line 46 Type.GetType(ScheduleTask.Type); is always null.


Are there any new changes for custom tasks in NopCommerce 4.0 or does anyone know why GetType can't find the task class?

Also, some kind of error message would be nice. There are error messages for exceptions, but not for null checks or similar checks. So even if the task coul'd not be found, NopCommerces shows a success message.

Thanks!
6 years ago
It should work fine. I can only presume that you have some typo.

Thanks a lot for suggetion about logging. I've just implemented it.

Tasks will work the same way in 4.00. The only main change is that now they are invoked during HTTP request (not in background like it was done in previous versions)
6 years ago
Hi.
You should specify the FullName of the assembly in which the task is located. You can see how it's done for the plugin Square here
6 years ago
Seems like a good place to mention this:  I have a proposal to augment ScheduleTask functionality.

We've seen requests to allow for schedule tasks to run at a defined time, which is difficult when you must account for app recycles, or simply running on a particular day of a month.  I don't like the idea of hardcoding an allowed run period in the task itself, so I copied the existing files and created CustomTask.cs, CustomTaskManager.cs, and CustomTaskThread.cs and tried to implement "NextRunDate" behavior while changing as little as possible.

Here are additional properties for ScheduleTask.cs entity:


    partial class ScheduleTask : BaseEntity
    {
        public DateTime? NextRunDateTimeUtc { get; set; } // DateTime NextRunDateTime is redundant naming, apologies

        public int RunIntervalId {get; set; }

        public RunInterval RunInterval
        {
            get { return ((RunInterval) (RunIntervalId)); }
            set { RunIntervalId = (int)value; }
        }
    }

    public enum RunInterval
    {
        CUSTOM = 0, //use "Seconds"
        MINUTELY = 1,
        HOURLY = 2,
        DAILY = 3,
        WEEKLY = 4,
        MONTHLY = 5,
        ANNUALLY = 6
    }



In CustomTaskManager.cs we add ScheduleTasks that have a defined  "NextRunDate" to their own thread with the new CustomTaskThread property "HasNextRunDate."


....
            var taskService = EngineContext.Current.Resolve<IScheduleTaskService>();
            var scheduleTasks = taskService
                .GetAllTasks()
                .OrderBy(x => x.Seconds)
                .ToList();


            var scheduleTasksWithNextRunDate = scheduleTasks.Where(q => q.NextRunDateTimeUtc.HasValue).ToList();
            var scheduleTasksWithoutNextRunDate = scheduleTasks.Except(scheduleTasksWithNextRunDate).ToList();

            //each task with start date lives in own thread
            foreach (var scheduleTask in scheduleTasksWithNextRunDate)
            {
                var taskThread = new CustomTaskThread
                {
                    HasNextRunDate = true //new property of CustomTaskThread
                };
                
                var task = new CustomTask(scheduleTask);
                taskThread.AddTask(task);
                
                this._taskThreads.Add(taskThread);
            }
              
            
            //group by threads with the same seconds
            foreach (var scheduleTaskGrouped in scheduleTasksWithoutNextRunDate.GroupBy(x => x.Seconds))
            {
                //create a thread
                var taskThread = new CustomTaskThread
                {
                    Seconds = scheduleTaskGrouped.Key
                };
                foreach (var scheduleTask in scheduleTaskGrouped)
                {
                    var task = new CustomTask(scheduleTask);
                    taskThread.AddTask(task);
                }
                this._taskThreads.Add(taskThread);
            }




In CustomTaskThread.cs we add the property HasNextRunDate and adjust the interval getter.


....
        public bool HasNextRunDate { get; set; }

        public int Interval
        {
            get
            {
                return HasNextRunDate
                    ? 30000 //if next run date specified, default interval to 30 seconds
                    : this.Seconds * 1000;
            }
        }



in CustomTask.cs we must determine if it's appropriate to run this task - if so, and "NextRunDate" is defined, we update accordingly when finished as new-next-run-date = old-next-run-date + interval.  Added "runNow" arg for when user specifically clicks "Run Now" button.



   public void Execute(bool throwException = false, bool dispose = true, bool ensureRunOnOneWebFarmInstance = true, bool runNow = false)
        {
          
            var scope = EngineContext.Current.ContainerManager.Scope();
            var scheduleTaskService = EngineContext.Current.ContainerManager.Resolve<IScheduleTaskService>("", scope);
            var scheduleTask = scheduleTaskService.GetTaskByType(this.Type);

            var utcNow = DateTime.UtcNow;
            if (scheduleTask != null && scheduleTask.NextRunDateTimeUtc.HasValue
&& scheduleTask.NextRunDateTimeUtc.Value > utcNow && !runNow) //if NextRunDate
            {
                return;
            }

.... //now task has executed
            if (scheduleTask != null)
            {
                scheduleTask.LastEndUtc = this.LastEndUtc;
                scheduleTask.LastSuccessUtc = this.LastSuccessUtc;

                if (scheduleTask.NextRunDateTimeUtc.HasValue && !runNow)
                {
                    var timeSpan = GetNextRunDateInterval((RunInterval) scheduleTask.RunIntervalId,
                        scheduleTask.NextRunDateTimeUtc.Value, scheduleTask.Seconds);

                    scheduleTask.Seconds = timeSpan.Seconds;
                    scheduleTask.NextRunDateTimeUtc += timeSpan;
                }

                scheduleTaskService.UpdateTask(scheduleTask);
            }

....

public static TimeSpan GetNextRunDateInterval(RunInterval runInterval, DateTime startDate, int seconds = 0)
        {
            TimeSpan timeSpan, totalTimeSpan = new TimeSpan(0,0,0,0);
            DateTime nextRunDate = startDate;

            do
            {
                switch (runInterval)
                {
                    case RunInterval.MINUTELY:
                        timeSpan = startDate.AddMinutes(1) - startDate;
                        break;
                    case RunInterval.HOURLY:
                        timeSpan = startDate.AddHours(1) - startDate;
                        break;
                    case RunInterval.DAILY:
                        timeSpan = startDate.AddDays(1) - startDate;
                        break;
                    case RunInterval.WEEKLY:
                        timeSpan = startDate.AddDays(7) - startDate;
                        break;
                    case RunInterval.MONTHLY:
                        timeSpan = startDate.AddMonths(1) - startDate;
                        break;
                    case RunInterval.ANNUALLY:
                        timeSpan = startDate.AddYears(1) - startDate;
                        break;
                    default:
                        timeSpan = startDate.AddSeconds(seconds > 0 ? seconds : 60) -
                                   startDate;
                        break;
                }

                nextRunDate += timeSpan;
                totalTimeSpan += timeSpan;

            } while (nextRunDate < DateTime.UtcNow); //ensure next run date is in future
            return totalTimeSpan;
        }


I'll spare you the details about the admin view for now.  I'm interested if anyone else has had any similar requests, and/or their feelings on this.  Task manageability is sometimes an important part of an ecommerce system, thus I believe there is value in making adjustments similar to this in nopCommerce.
6 years ago
Hi Adam,

Thanks a lot for suggestion and this contribtrion. I've just created a work item and will check it in details soon
6 years ago
UPDATE: we've found a way to avoid fully qualified names. Please see this commit
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.