Working with Timer-Triggered Azure Functions in C#

Timer-triggered Azure functions allow you to run a function on a configurable schedule. Unlike HTTP-triggered Azure functions, timing info is stored in an Azure storage account and then passed to the function on execution. As a result, once you add in a timer-triggered function for the first time, there may be some additional steps needed to run your function app project locally.

Basic Setup

Timer-triggered functions can be configured using a CRON expression. In C#, this schedule is set in your function’s TimerTrigger attribute:


[FunctionName("SendDailyEmail")]
public async Task SendDailyEmail([TimerTrigger("0 0 0 * * *")] TimerInfo timerInfo)
{
    await SendDailyEmail();
}

Once that is set up, you may need to make a few changes to your local.settings.json file before running your function app locally.

Changes to the AzureWebJobsStorage Field

First, make sure that the AzureWebJobsStorage field is set. Any non-HTTP-triggered function requires this and will not build if it’s not provided.

You could set the connection string for an actual Azure storage account (assuming you already have one set up):


{
  "Values": {
    "AzureWebJobsStorage": "<connection-string>"
  }
}

Or you could run an Azure storage account emulator and configure your local.settings.json to use local development storage:


{
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  }
}

My personal preference for running Azurite is the VS Code extension; you can also install and run it using NPM or Docker.

Disabling the Function

A recurring function execution can be somewhat disruptive when trying to test or debug an unrelated issue, so it can also be helpful to disable your timer-triggered function. To disable the function, update local.setting.json with a new key for AzureWebJobs.{FunctionName}.Disabled set to “true” as a default:


{
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobs.SendDailyEmail.Disabled": "true"
  }
}

Where to Configure the CRON Expression

Optionally, you could store the CRON expression for the timer-triggered function’s schedule in your settings file. This allows for different schedules across different environments and makes it easier to set it to a specific schedule to assist with manual debugging. Update your local.settings.json with a new key/value, and then reference this field in your timer-triggered function setup:


{
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobs.SendDailyEmail.Disabled": "false",
    "DailyEmailSchedule": "0 0 0 * * *"
  }
}

[FunctionName("SendDailyEmail")]
public async Task SendDailyEmail([TimerTrigger("%DailyEmailSchedule%")] TimerInfo timerInfo)
{
    await SendDailyEmail();
}

Testing the Function (on Your Own Schedule)

Although you could technically just fire up your project and sit there waiting for the function to execute, it’s probably easiest to trigger the function manually. Luckily, you can still do this with a POST to an admin URL that includes the function name.

When testing locally, an Azure storage account should be connected, or a storage account emulator should be running. The function must not be disabled. Using VS Code’s REST Client extension, the request will look something like this:

###
# @name sendDailyEmailManualTrigger
POST http://localhost:7075/admin/functions/SendDailyEmail
Content-Type: application/json

{

}

To manually test against a deployed environment, you will need to include the function app’s master key.


These are the major changes we had to make to our local development environment to get our timer-triggered Azure functions running locally. Non-HTTP-triggered Azure functions require a bit of additional setup and a slightly different local environment configuration. Hopefully, following these steps will help speed up that process for your team.