top of page

How to schedule tasks in .NET?

In this post, we want to expand functionality of our web service with some additional features, such as SMS reports.


This post aims to show you:

  • How to send SMS

  • How to easily add scheduler to our web service

  • How to send SMS periodically


📱 First Step: Send SMS ✔️

For our SMS provider we will use Infobip. Please access our free trial self service account through this link. After grabbing our API key and URL you just need to implement client library in the project.


Library: (GitHub)

dotnet add package Infobip.Api.Client

Then, place your user settings in appsettings.json, so that you can easily grab them through our web app.

"InfobipApi": 
{
    "BasePath": "<User base URL>",
    "ApiKeyPrefix": "<API key prefix (App/Basic/IBSSO/Bearer)>",
    "ApiKey": "API key"  
}

Based on Infobip library, we will create method SendNotificationSms:

private void SendNotificationSms(string smsNotificationMessage)
{
    var configuration = new Configuration()	
    {
        BasePath = _config["InfobipApi:BasePath"],
        ApiKeyPrefix = _config["InfobipApi:ApiKeyPrefix"],
        ApiKey = _config["InfobipApi:ApiKey"]	
    };
        
    var sendSmsApi = new SendSmsApi(configuration);
    var smsMessage = new SmsTextualMessage()	
    {
        From = _config["SmsSettings:From"],
        Destinations = new List<SmsDestination>()		
        {
            new SmsDestination(to: _config     
                                    ["SmsSettings:NotificationSmsNumber"])		
        },
        Text = smsNotificationMessage	
    };
    
    var smsRequest = newSmsAdvancedTextualRequest()	
    {
        Messages = new List<SmsTextualMessage>() { smsMessage }	
    };
    
    try	
    {
        var smsResponse =sendSmsApi.SendSmsMessage(smsRequest);	
    }
    catch (ApiException apiException)	
    {
        // ...	
    }
}

Let’s go through SendNotificationSms(string smsNotificationMessage).

  • First, we set up the configuration we will send to SendSmsApi class.

  • Then create SmsTextualMessage by setting up Form, Destination and Text fields.

  • Finally, add created message to SmsAdvancedTextualRequest.

SendSmsMessage method from Infobip library will do the trick and send the message to our user:

var smsResponse = sendSmsApi.SendSmsMessage(smsRequest);

Now that the basic functionality is up and running, adding it to your scheduler shouldn’t be a problem.


⏰ Second Step: Add scheduler ✔️

For this feature we will use Quartz.NET.

One could describe Quartz.NET as a full-featured, open-source job-scheduling system.


There are two important points you need to know; Job Scheduling and Job Execution.


1. Job Scheduling

  • Jobs are scheduled to run when a given trigger occurs, triggers support wide variety of scheduling options.


2. Job Execution

  • Jobs can be any .NET class that implements the simple IJob interface, leaving infinite possibilities for the work that jobs can perform.


Lets’s add package to our project:

dotnet add package Quartz.Extensions.Hosting

Now, we have to configure and register our Quartz service. Use Program.cs for that. You can follow documentation https://www.quartz-scheduler.net/ on how to configure and register in detail.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)  		 
        .ConfigureWebHostDefaults(webBuilder=>				
        {  
        webBuilder.UseStartup<Startup>();  
        // Add selfhost port for demo  
        webBuilder.UseUrls("http://localhost:8000");  		 
        }) 
        
        .ConfigureServices((hostContext, services) =>				
        {  
            services.AddQuartz(q=>					
            {  q.UseMicrosoftDependencyInjectionScopedJobFactory();  
            //
            // Add periodical/scheduled jobs 
            // Time definition is in appsettings 
            // https://www.quartz-scheduler.net/documentation/quartz-
                                3.x/tutorial/crontriggers.html 
            // Daily - MON-SUN on 08:00 CET for the previous day => Cron 
                                expression: "0 0 8 ? * MON-SUN" 
            q.AddJobAndTrigger<SendDailyNotification>
                                (hostContext.Configuration);  
            // Weekly - MON on 08:01 CET for the past week => Cron 
                                expression: "0 1 8 ? * MON"  
            q.AddJobAndTrigger<SendWeeklyNotification>
                                (hostContext.Configuration);  
            // Monthly - first MON on the month for the past month => Cron 
                                expression: "0 2 8 * * MON#1"  
            q.AddJobAndTrigger<SendMonthlyNotification>
                                (hostContext.Configuration);  		 
            });  
        services.AddQuartzHostedService(  
                            q=>q.WaitForJobsToComplete=true);  		 
    });

In this code example, we try to keep Program.cs as short as possible, hence the extension method for our settings.

public static class ServiceCollectionQuartzConfiguratorExtensions    
{
    public static void AddJobAndTrigger<T>(
        thisIServiceCollectionQuartzConfigurator quartz,
            IConfigurationconfig)
    whereT : IJob        
    {
        // Use the name of the IJob as the appsettings.json key
        string jobName = typeof(T).Name;
        
        // Try and load the schedule from configuration
        var configKey = $"Quartz:{jobName}";
        var cronSchedule = config[configKey];
        
        // Some minor validation
        if (string.IsNullOrEmpty(cronSchedule))            
        {
            throw new Exception($"No Quartz.NET Cron schedule found for 
                        job in configuration at {configKey}");            
        }
        
        // register the job
        var jobKey = new JobKey(jobName);
        quartz.AddJob<T>(opts => opts.WithIdentity(jobKey));
        
        quartz.AddTrigger(opts => opts                
            .ForJob(jobKey)                
            .WithIdentity(jobName + "-trigger")                
            .WithCronSchedule(cronSchedule)); 
            // use the schedule from configuration        
        }



⏲️ Third Step: Send SMS periodically ✔️

Scheduler is all set up and now you need to add jobs. In our report feature we decided to send SMS messages daily, weekly and monthly.


Example project has three jobs defined in project Jobs folder.


Remember:

Jobs can be any .NET class that implements the simple IJob interface.

Example code below sends daily SMS report of how many URLs are shortened:

[DisallowConcurrentExecution]  
public class SendDailyNotification : IJob
{  
    private readonly ISendSms_sendSms;  
    private readonly ILogger<SendDailyNotification> _logger;  
    
    public SendDailyNotification(ILogger<SendDailyNotification> logger, 
    ISendSms sendSms)  	 
    { 
        _logger = logger;  
        _sendSms = sendSms;  	 
    }  
    
    public Task Execute(IJobExecutionContext context)  	 
    {
         _logger.LogInformation("Job start => Sending Daily 
                                         notification.");  
         try		 
         {  
             _sendSms.SendDailyNotification();  		 
         } 
         catch (Exception ex)  		 
         { 
             _logger.LogInformation($"Error sending Daily notification. 
                                 {ex}");  		 
         } 
         
         _logger.LogInformation("Job end => Sending Daily notification.");  
         return Task.CompletedTask;	
    }
}

You may ask yourself: Where is the scheduled time in this code? How can I change it?

Well…that is the beauty of Quartz — it uses Cron expression which is easy to set up.

For example, we have moved scheduler setup to our application settings, so there are separate settings for every job in our application.

"Quartz": 
{
    "HelloWorldJob": "0/5 * * * * ?",
    "SendDailyNotification": "0 0 8 ? * MON-SUN",
    "SendWeeklyNotification": "0 1 8 ? * MON",
    "SendMonthlyNotification": "0 2 8 ? * MON#1"  
}

You can check out for various settings on cron triggers


In our example:

  • Cron expression: “0 0 8 ? * MON-SUN” -> Daily — MON-SUN on 08:00 CET for the previous day

  • Cron expression: “0 1 8 ? * MON” -> Weekly — MON on 08:01 CET for the past week

  • Cron expression: “0 2 8 * * MON#1” -> Monthly — first MON on the month for the past month

If you go back to Program.cs you will see how we grab and use this settings with our extension method.

Now our web service is ready to send scheduled reports to users.


Conclusion 🎉 🍾

It comes as no surprise that scheduling jobs in applications can be a really daunting work. Quartz makes it so much easier. Bare in mind that we just scratched the surface with our settings. There is a lot more stuff under the hood but probably this is enough for most of use cases.


The same thing can be said about our SMS notification feature. You don’t need to write API request to Infobip platform. Just add library to your .NET project, set config, create your message objects and than send an SMS. It is also possible to expand this simple example and send reports with other information channels, such as Email and WhatsApp.



Source: Medium - Dino Lozina


The Tech Platform


0 comments

Recent Posts

See All

Comments


bottom of page