4

I want to send mails and SMSs in a scheduled time. I would prefer not using a Windows Service.

Is there any options to schedule a task in a custom time in ASP.Net?

Please suggest which is best way.

Thanks in advance.

Sunil
  • 3,404
  • 10
  • 23
  • 31
Arun Raj
  • 969
  • 8
  • 19

5 Answers5

6

I was facing a similar problem once when my website was hosted on a Godaddy shared hosting. I wanted to run scheduled tasks for sending emails without any windows service.

Finally, I resolved the issue by the use of cache.

A cache item expires on a given time or duration. In ASP.NET, you can add entries in the Cache and set an absolute expiry date time, or you can set a duration after which the item is removed from the cache. You can do this by utilizing the following method of the Cache class.

    public void Insert ( System.String key , System.Object value , 
                     System.Web.Caching.CacheDependency dependencies , 
                     System.DateTime absoluteExpiration , 
                     System.TimeSpan slidingExpiration , 
                     System.Web.Caching.CacheItemPriority priority , 
                     System.Web.Caching.CacheItemRemovedCallback onRemoveCallback )

The onRemoveCallback is a delegate to a method which is called whenever a cache item expires. In that method, we can do anything we like. So, this is a good candidate for running code periodically, consistently without requiring any page visit.

This means, we can simulate a Windows Service utilizing Cache timeout.

You can find the entire details on the codeproject url http://www.codeproject.com/Articles/12117/Simulate-a-Windows-Service-using-ASP-NET-to-run-sc.

milan m
  • 2,164
  • 3
  • 26
  • 40
2

What about a timer callback, set it up in Application_Start:

protected void Application_Start(object sender, EventArgs e)
{
    TimerCallback callback = (x) => { YourFunction(); };
    int intervalInMS = 2 * 60 * 60 * 1000;  // every 2 hours. 
    timer = new Timer(callback, state: null, dueTime: intervalInMS, period: intervalInMS);
}
GarethOwen
  • 6,075
  • 5
  • 39
  • 56
2

Courtesy of Jeff Atwood..

At startup (global.asx), add an item to the HttpRuntime.Cache with a fixed expiration.

When cache item expires, do your work, such as WebRequest or what have you.

Re-add the item to the cache with a fixed expiration. The code is quite simple, really:

private static CacheItemRemovedCallback OnCacheRemove = null;

protected void Application_Start(object sender, EventArgs e)
{
    AddTask("DoStuff", 60);
}

private void AddTask(string name, int seconds)
{
    OnCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
    HttpRuntime.Cache.Insert(name, seconds, null, 
        DateTime.Now.AddSeconds(seconds), Cache.NoSlidingExpiration,
        CacheItemPriority.NotRemovable, OnCacheRemove);
}

public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
{
    // do stuff here if it matches our taskname, like WebRequest
    // re-add our task so it recurs
    AddTask(k, Convert.ToInt32(v));
}

Your "DoStuff" is the method that triggers your real work required to run.

It's that easy!

Fandango68
  • 4,461
  • 4
  • 39
  • 74
  • 2
    I set this up for daily tasks on my server. It works well, but I found it necessary to check that `r == CacheItemRemovedReason.Expired` before running my task. For some reason, the cache gets cleaned out about every hour on my server with a reason of `CacheItemRemovedReason.Removed`. In those cases I don't run the task, I just re-schedule it (in my case, for 3AM each day) – jramm Jun 25 '20 at 12:39
  • @jramm - good catch as that could be cases with cache changes in the browser. Afterall, this post is quite old now – Fandango68 Jun 26 '20 at 00:19
1

Quartz.Net (http://www.mikesdotnetting.com/article/254/scheduled-tasks-in-asp-net-with-quartz-net) is a library that makes it easy to setup and manage jobs as a part of your application.

Dima Korobskiy
  • 1,479
  • 16
  • 26
0

I created a class to encapsulate this behavior for tasks on my web server. It's inspired by the answers here and on Jeff Atwood's blog post, but I figured I'd share it in case it's useful to anyone else.

This is configured to work for my local timezone (CST), but could be adapted to another timezone pretty easily.

Schedule a Task in Application_Start

This example schedules the TaskUpdateUserRoles.Task method to be called each day at 3:00AM CST. Place it in the Application_Start method in your Global.asax.cs file.

ServerTask.CreateDaily("Update User Roles",
    TaskUpdateUserRoles.Task,
    DateTime.Parse("3:00 AM").TimeOfDay); //This should be the local time. CST in this case.

ServerTask Class

using System;
using System.Diagnostics;
using System.IO;
using System.Web;
using System.Web.Caching;

namespace MySite.Admin.Tasks
{
    /// <summary>
    /// Manages recurring tasks to run on the server.
    /// </summary>
    public class ServerTask
    {
        private string TaskName { get; set; }
        private TaskFrequency Frequency { get; set; }
        private TimeSpan Offset { get; set; }
        private Func<bool> TaskFunction { get; set; }
        private CacheItemRemovedCallback OnCacheRemove = null;
        private static readonly string TaskLogPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Admin/Logs/");

        private ServerTask(string name, TaskFrequency freq, TimeSpan offset, Func<bool> taskFunction)
        {
            TaskName = name;
            Frequency = freq;
            Offset = offset;
            TaskFunction = taskFunction;
        }

        /// <summary>
        /// Creates a daily task that will run at the specified hour and minute.
        /// </summary>
        /// <param name="taskName">The task name.</param>
        /// <param name="hourAndMinute">The hour and minute when the task should run (Central Standard Time).</param>
        public static ServerTask CreateDaily(string taskName, Func<bool> taskFunction, TimeSpan hourAndMinute)
        {
            ServerTask thisTask = new ServerTask(taskName, TaskFrequency.Daily, hourAndMinute, taskFunction);
            thisTask.QueueTask();
            return thisTask;
        }

        /// <summary>
        /// Creates a weekly task that will run on the specified weekday, hour, and minute.
        /// </summary>
        /// <param name="taskName">The task name.</param>
        /// <param name="weekday">The day of the week when the task should run.</param>
        /// <param name="hourAndMinute">The hour and minute when the task should run (Central Standard Time).</param>
        public static ServerTask CreateWeekly(string taskName, Func<bool> taskFunction, DayOfWeek weekday, TimeSpan hourAndMinute)
        {
            TimeSpan ts = new TimeSpan((int)weekday, hourAndMinute.Hours, hourAndMinute.Minutes, 0);
            ServerTask thisTask = new ServerTask(taskName, TaskFrequency.Weekly, ts, taskFunction);
            thisTask.QueueTask();
            return thisTask;
        }

        /// <summary>
        /// Creates a monthly task that will run on the specified day of the month, hour, and minute.
        /// </summary>
        /// <param name="taskName">The task name.</param>
        /// <param name="dayOfTheMonth">The day of the month when the task should run.</param>
        /// <param name="hourAndMinute">The hour and minute when the task should run (Central Standard Time).</param>
        public static ServerTask CreateMonthly(string taskName, Func<bool> taskFunction, int dayOfTheMonth, TimeSpan hourAndMinute)
        {
            TimeSpan ts = new TimeSpan(dayOfTheMonth, hourAndMinute.Hours, hourAndMinute.Minutes, 0);
            ServerTask thisTask = new ServerTask(taskName, TaskFrequency.Monthly, ts, taskFunction);
            thisTask.QueueTask();
            return thisTask;
        }

        private enum TaskFrequency
        {
            Daily,
            Weekly,
            Monthly
        }

        private void QueueTask()
        {
            try
            {
                OnCacheRemove = new CacheItemRemovedCallback(RunTask);
                HttpRuntime.Cache.Insert(TaskName, 1, null,
                    getNextTaskTime(), Cache.NoSlidingExpiration,
                    CacheItemPriority.NotRemovable, OnCacheRemove);
            }
            catch { }
        }

        private void RunTask(string taskName, object o, CacheItemRemovedReason r)
        {
            //First, verify that the cache entry has expired. It turns out the server often removes our Cache
            //item even when it hasn't expired (generally about once per hour - there must be a server process
            //that cleans out the cache regularly). If that happens, just re-queue the task again.
            if (r == CacheItemRemovedReason.Expired)
            {
                try
                {
                    Exception taskException = null;
                    bool taskResult = false;

                    Stopwatch taskTimer = Stopwatch.StartNew();
                    try
                    {
                        taskResult = TaskFunction();
                    }
                    catch (Exception e)
                    {
                        taskException = e;
                    }
                    taskTimer.Stop();

                    //Write a log file entry each time this task runs in a monthly log file
                    if (!Directory.Exists(Path.GetDirectoryName(TaskLogPath)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(TaskLogPath));
                    }
                    DateTime taskTime = Data.DataHelper.ConvertUTCtoCST(DateTime.UtcNow);
                    string logFileName = string.Format("Tasks.{0}.log", taskTime.ToString("MM.yyyy"));
                    using (StreamWriter sw = new StreamWriter(Path.Combine(TaskLogPath, logFileName), true))
                    {
                        if (taskException == null)
                        {
                            string taskResultString = taskResult ? "and reported success" : "but did not report successful completion.";
                            sw.WriteLine("The task \"{0}\" ran at {1} {2}. Task runtime was {3} milliseconds.",
                                taskName, taskTime.ToString(), taskResultString, taskTimer.ElapsedMilliseconds.ToString());
                        }
                        else
                        {
                            sw.WriteLine("Attempted to run the task \"{0}\" at {1}, but the task failed with the following error after {2} milliseconds: {3} [StackTrace: {4}]",
                                taskName, taskTime.ToString(), taskTimer.ElapsedMilliseconds.ToString(), taskException.Message, taskException.StackTrace);
                        }
                    }
                }
                catch { }
            }

            //queue this task to be called again
            QueueTask();
        }

        private DateTime getNextTaskTime()
        {
            if (Frequency == TaskFrequency.Monthly)
            {
                return getNextMonthlyTime();
            }
            else if (Frequency == TaskFrequency.Weekly)
            {
                return getNextWeeklyTime();
            }
            return getNextDailyTime();
        }

        private DateTime getNextDailyTime()
        {
            DateTime now = Data.DataHelper.ConvertUTCtoCST(DateTime.UtcNow);
            DateTime taskTime = new DateTime(now.Year, now.Month, now.Day, Offset.Hours, Offset.Minutes, 0);
            taskTime = taskTime > now ? taskTime : taskTime.AddDays(1);
            taskTime = Data.DataHelper.ConvertCSTtoUTC(taskTime);
            return taskTime;
        }
        private DateTime getNextWeeklyTime()
        {
            DateTime now = Data.DataHelper.ConvertUTCtoCST(DateTime.UtcNow);
            DateTime taskTime = new DateTime(now.Year, now.Month, now.Day, Offset.Hours, Offset.Minutes, 0);
            while ((int)taskTime.DayOfWeek != Offset.Days)
            {
                taskTime = taskTime.AddDays(1);
            }
            taskTime = taskTime > now ? taskTime : taskTime.AddDays(7);
            taskTime = Data.DataHelper.ConvertCSTtoUTC(taskTime);
            return taskTime;
        }
        private DateTime getNextMonthlyTime()
        {
            DateTime now = Data.DataHelper.ConvertUTCtoCST(DateTime.UtcNow);
            DateTime taskTime = new DateTime(now.Year, now.Month, Offset.Days, Offset.Hours, Offset.Minutes, 0);
            if (taskTime.Day < now.Day)
            {
                taskTime = taskTime.AddMonths(1);
            }
            else if (taskTime.Day == now.Day)
            {
                taskTime = taskTime > now ? taskTime : taskTime.AddMonths(1);
            }
            taskTime = Data.DataHelper.ConvertCSTtoUTC(taskTime);
            return taskTime;
        }

        private DateTime ConvertCSTtoUTC(DateTime cstTime)
        {
            try
            {
                TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
                DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(cstTime, cstZone);
                return utcTime;
            }
            catch { }
            return cstTime; //fallback to unconverted time
        }

        private DateTime ConvertUTCtoCST(DateTime utcTime)
        {
            try
            {
                TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
                DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, cstZone);
                return cstTime;
            }
            catch { }
            return utcTime; //fallback to unconverted time
        }
    }
}

Signature of Task Method

public static class TaskUpdateUserRoles
{
    public static bool Task()
    {
        //perform task here. Return true if successful.
    }
}
jramm
  • 751
  • 1
  • 8
  • 26