18

I need to run a scheduled task on every new day in asp.net mvc core application. Can I do it, and how?!

thnx

Wasyster
  • 2,279
  • 4
  • 26
  • 58

8 Answers8

14

Updated answer - Nov 2022

With DotNet Core 2 came up a new interface IHostedService that will take care of background tasks. On this microsoft document you`ll have all the details about Background tasks with hosted services in ASP.NET Core implementation.

A simple HostedService implementation:


    public class SimpleHostedService : IHostedService
    {
        private readonly ILogger<SimpleHostedService> _logger;

        public SimpleHostedService(ILogger<SimpleHostedService> logger)
        {
            _logger = logger;
        }

        public async Task StartAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Timed Hosted Service Started.");

            while (!stoppingToken.IsCancellationRequested)
            {
                DoWork();

                // Wait one second
                await Task.Delay(1000);
            }

        }

        private void DoWork()
        {
            // Do something
        }

        public Task StopAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Hosted Service is stopping.");

            return Task.CompletedTask;
        }
    }

About Schedule Tasks I`ll show two options:

  1. Using the out of the box System.Threading.Timer to trigger the task's DoWork method. Take a look at Timed background tasks:
public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer? _timer = null;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero,
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        var count = Interlocked.Increment(ref executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}", count);
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}
  1. A more complete and flexible solution to handle specific Schedules Configuration can be achieved using the Cron like project ServiceWorkerCronJob. Here you`ll have an article showing great capabilities of the component and how to implement.

My Tip and Encouragement

Put all BackgroudServices in a dedicated WorkerService application, take a look at Worker Services in .NET.

Implementing BackgroundServices togheter AspNet Web/Api (in the same project I mean) will bring many complexities when you need to Scale Out. So, keep in mind that:

  1. Processing CPU-intensive or Memory-intensive may impact performance of your AspNet Response time.
  2. Scaling out of more then one instance will duplicate the BackgroundService execution on each instance and cause unwanted bugs or wasted use of resources.
  3. Over time application business rules tend to grow and in the future you may have to handle the above issues.
Bruno Matuk
  • 562
  • 5
  • 11
8

Short answer

You can't.

Long answer

You need third party libraries, like Quarz, Hangfire or Azure WebJobs which will trigger it from outside or inside the ASP.NET Core application.

Be aware though that if you use Quarz or Hangfire to run it inside the ASP.NET Core application, it may be subject to process life cycle, i.e. if you run it with IIS or Azure App Service, then you have no control when IIS will stop the application (due to inactivity or whatever) or if it will start it w/o an external request (IIS can be configured to restart the app immediately, default is on next request).

That being said, it could be possible that you skip the trigger when the application is being shut down by IIS or some other process. So its best to have the scheduler run outside of ASP.NET Core process (i.e. console application, background worker on Azure or use Azure Web Jobs).

Community
  • 1
  • 1
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • Is this really true? I was just thinking to put a System.TaskScheduler using C# into Program.cs just before `host.Run()` – Ole K Jan 25 '17 at 09:20
  • 2
    @OleK: Spinning up threads in ASP.NET is a rather bad idea, first it may reduce the number of threads available for the requests (they are shared with the app) and hence limit performance/lower number of requests it can handle. Second is, that ASP.NET apps can be recycled at anytime, especially when running on Azure App Service or IIS. There are also issues if you dependency injection and some of the services are scoped, then you'd need to manage these scopes yourself (normally ASP.NET creates 1 scope per request, when you run outside of request you have to manage yourself) – Tseng Jan 25 '17 at 10:43
  • To avoid recycling on IIS there might be an option to stop the recyling similar to what I noted here: http://stackoverflow.com/questions/40467009/net-core-slow-after-idle/41263683#41263683 – Ole K Jan 25 '17 at 11:02
  • @OleK: There is no guarantee for it. You can only change the idle time this way, but if your IIS runs low on resources it will recycle it anyways, no matter of the options set. When that happens, you have no control over it when it gets started up again. And, when you edit the web.config, then IIS will forcibly restart/recycle your application, no matter what. It's just not a good practice to do this kind of stuff inside ASP.NET application. There are other options (consoll applications, windows service, etc.) that are all more suitable and robust than that – Tseng Jan 25 '17 at 11:36
  • so to solve just log if the task has been done or not, for example inside appsettings.json compare against current time, and it be done the first moment it startups again. It wont solve critical timers, like your work-allarm-out-of-bed, but its usuable enough for states in most application, before you do a landing page get check it. – Peter May 13 '22 at 08:52
3

As an idea using the Startup.cs with System.Threading.Timer.

// add the below into your Startup contructor
public Startup(IHostingEnvironment env)
{
    // [...]
    var t = new System.Threading.Timer(doSomething);
    // do something every 15 seconds
    t.Change(0, 15000);
}

// define a DateTime property to hold the last executed date
public DateTime LastExecuted { get; set; }

// define the doSomething callback in your Startup class
private void doSomething(object state)
{
    System.Diagnostics.Debug.WriteLine("The timer callback executes.");

    // this callback is called every 15 seconds
    // but the condition below makes sure only it
    // only takes place at 17:00 
    // every day, when LastExecuted is not today
    if(DateTime.UtcNow.Hour == 17 && DateTime.UtcNow.Minute == 0 && DateTime.UtcNow.Date != LastExecuted.Date)
    {
        LastExecuted = DateTime.UtcNow;
        System.Diagnostics.Debug.WriteLine("#### Do the job");
    }
}
Ole K
  • 754
  • 1
  • 9
  • 32
  • You'll have trouble using DI/IoC to inject services with this approach, as in startup your container is not yet set up and with scoped services such as `DbContext`. And if you spin threads inside it you may completely mess up how ASP.NET manages the threadpool – Tseng Jan 25 '17 at 10:55
  • @Tseng I fully agree, but question does not actually focus on the use of any service of DBContext. Alternatively we can even put those lines into singleton service... – Ole K Jan 25 '17 at 10:58
3

This is an old question but I've seen a lot of improvements with the latest framework versions and not a lot of information out there how to make it work in a clean way.

AspNet Core 3.x implements the webhost builder in a bit more abstract way than ever, allowing to run different services types perfectly. The initialization of a web container is identical to run a SignalR server, Web Api, MVC or workers so we can mix them up without issues.

For example, having an API/MVC app initialized in the following way:

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
               .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
}

We can use the template to declare any kind of service in the usual Startup code. Configure your services in the way you need them and they will be ready for any hosted application. Now, let's add a timed worker, we need to create a simple class following Microsoft's documentation:

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

Source: Microsoft

And add our worker to the builder in Program.cs:

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
               .ConfigureServices((hostContext, services) =>
               {
                   services.AddHostedService<TimedHostedService>();
               })
               .ConfigureWebHostDefaults(webBuilder =>
               {
                   webBuilder.UseStartup<Startup>();
               });
}

Your web app will start, run the normal services to accept requests and also, you will get a timed host service. The advantage is you also get Dependency Injection and you can just add to the TimedHostService constructor any interface and it will be injected by the framework transparently.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
Maximiliano Rios
  • 2,196
  • 2
  • 20
  • 34
2

I would suggest running a powershell script that makes a GET request to any api/endpoint in your application. This way you will facilitate the windows based task scheduler in a .NET Core Application.

1. Create a powershell script and save it on your server as myjob.ps1

$url="http://www.myapp.com/api/runjob"
$content=(New-Object System.Net.WebClient).DownloadString("$url");

$Logfile = "D:\Temp\job.log"
Add-content $Logfile -value $content

2. Set the powershell execution policy to unrestricted

Open up Powershell and run the following commands:

Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted
Set-Executionpolicy -Scope Process -ExecutionPolicy UnRestricted

Read more about setting up a scheduled task here:https://www.metalogix.com/help/Content%20Matrix%20Console/SharePoint%20Edition/002_HowTo/004_SharePointActions/012_SchedulingPowerShell.htm

3. Create a scheduled job and execute your script

Create a new task i Task Schedulerer. When creating action set it to "Start a program"

Program: Powershell
Argument: -noprofile -executionpolicy bypass -file "C:\Jobs\myjob.ps1"
Rikard Askelöf
  • 2,762
  • 4
  • 20
  • 24
1

If you host your code on Azure something like this is possible. With Azure WebJobs you can request a specific url like a cronjob, but you can also run specific pieces of code if you want to. This can be an asp.net core app if you want to. You do need to change some things in the startup project VS creates for you though.

For example let listen to a queue and send an email when something gets queued. Or create a thumbnail from a big image.

Mor info can be found here: https://azure.microsoft.com/en-us/documentation/articles/websites-dotnet-webjobs-sdk-get-started/

Tom Droste
  • 1,324
  • 10
  • 14
0

You can developer worker service using .NET Core 3.0 onwards. Take a look on it

https://medium.com/@nickfane/introduction-to-worker-services-in-net-core-3-0-4bb3fc631225

https://levelup.gitconnected.com/net-core-worker-service-as-windows-service-or-linux-daemons-a9579a540b77

Worker service is responsible for executing background tasks as per the given interval time.

R15
  • 13,982
  • 14
  • 97
  • 173
-1

Generally if you don't want to use azure or any other service you can create a new project. Some kind of a rule engine. The scheduled task will be stored on an DB (for example SQL Server) and then the application will read this task and run the logic. So generally it's better to have a different a different application to handle scheduled tasks (like cleaning files, emails etc). This type of solution allows you to easilly scale your logic to multiple type of task and apply rules. For example sending email based on various rules you have decided

A_kat
  • 1,459
  • 10
  • 17