Part -2: Expert Guide for Azure Functions Triggers Tutorials

Expert Guide for Azure Functions Triggers Tutorials

Introduction

Welcome back to another excellent blog that’s been personally composed by our DEV IT experts. The previous blog, “A Quick Guide to Azure Functions,” went over the descriptions of Azure Functions and covered topics including:

  • What is Azure Function?
  • What is serverless?
  • Azure Function Example

Another blog on the site covered topics about “Using Constructor Injection to Implement DI in Microsoft Azure.” That’s another important blog when it comes to Azure Function Projects, and would be needed to ensure your code’s integrity while working on your Azure Function Project. Now, this new post will cover topics about Dependency Injection for Azure Functions V2 and V3. We won’t cover V1 for now, but if you want a blog on it as well, then please write a comment below, and I will write one soon.

So, let’s get right to it now. This blog will cover various triggers for the execution of Azure Functions.

What is an Azure Function Trigger?

An azure function trigger is a mechanism using which one can invoke the (manually or automatically) function. Consider trigger as an event; when the event occurs, it will invoke the function with appropriate data so that the Azure function can get enough context to decide upon.

There are different types of triggers azure function supports out of the box, which we will go through in this blog post. And if you find that your requirement is not getting covered with any of the existing out-of-the-box supported triggers, don’t worry. We will see an example of a custom trigger and see how a custom trigger can be implemented.

Azure Function Supported Triggers

Following triggers are generally used in any project:

  • HTTP & Webhooks
  • Timer
  • Blog Storage
  • Table Storage
  • Queue Storage
  • Service Bus
  • Event Grid
  • Event Hub

Http & Webhooks

HTTP & webhooks trigger is for invoking an azure function via making an HTTP call. I.e., You can build serverless APIs, or you can treat your APIs as webhook for third-party integration.

In the part – 1, we saw an example of the HTTPTrigger azure function by implementing a HelloWorld example. After the implementation, we saw that the function could be invoked on http://localhost:7071://api/helloworldfunction.

Few considerations you might want to take care of while implementing an HTTP Trigger function:

HTTP Connection Exhaustion

If you are calling HTTP API using HTTPClient in your HTTPTriger function, you might face a problem of port exhaustion which you can solve by sharing HTTPClient objects across multiple function executions. There are other ways as well which are explained here in more detail – Managing HTTP Connection in Azure Functions.

Securing HTTP Trigger function

By default, the HTTP trigger function allows anonymous connections. i.e., Anyone with a URL can make requests to our function. This can be solved in multiple ways:

1.Use Function Keys – Using this key, one can call only the respective azure function only.

2. Use Host Keys – Using this key, one can call all functions in an app service.

3. Use Master Keys – Using this key, one can get access to all functions as well as Azure function management APIs as well for an App Service.

4. Use App Service Authentication Mechanism – Azure app service (function or web app) has built-in support for authentication and authorization endpoints. One can use ASP.NET Identity underneath the azure function to manage identities and generate tokens and let app service authorization middleware handle the authorization part.

5. Use other Azure services such as APIM

6. Similar to using Authentication Mechanism, one can use API management capability to secure the APIs.

From the above-supported options, 4 and 5 are the recommended way to implement if you are going to expose the APIs over the internet in production.

Read here for more information about “Securing HTTP Trigger Functions.”

Important Note: If the azure trigger function fails, azure runtime won’t retry execution of the function, i.e., the Retry mechanism is not built-in for the TimerTrigger function.

Timer

As the name suggests, the timer trigger allows one to invoke a function based on predefined time. Consider a timer triggered function as a schedule function that can execute every day at 12 PM, every Monday at 3:00 AM, or every 30 minutes on Mon, Wed., i.e., Depending on the schedule we define, the timer function will get executed by Azure function runtime.

The timer trigger gets executed based on the NCRONTab expression. For example “0 */5 * * * *” is a cron expression for “once every 5 minutes” and “0 30 9 * Jan Mon” is the cron expression for “at 9:30 AM every Monday in January”.

Note: CRON expression and times in Azure function (or all azure services) are based on UTC only.

Let’s see an example:

[FunctionName("ScheduledImport")]
public static void Run([TimerTrigger("0 */5 * * * *")]TimerInfo timerRequest, ILogger logger)
{
    log.LogInformation($"Timer is running at {DateTime.UtcNow}!");
}

From the example above, we can see that TimerTrigger takes a NCRON expression as input and decides when to execute the function. You can also get this expression from the local.development.json file. It sends TimerInfo as request which as properties such as:

  • “ScheduleStatus.Last” – Last run time of azure function
  • “ScheduleStatus.Next” – Next run time of azure function
  • “IsPastDue” – whether the function is triggered later than the scheduled time.

Important Note: If the azure trigger function fails, azure runtime won’t retry execution of the function, i.e., the Retry mechanism is not built-in for the TimerTrigger function.

Blob Storage

Blog Storage triggered function will get executed when azure function runtime detects a new/updated blob in a configured container. Since detecting new/updated blobs can be challenging in terms of performance, azure runtime may ignore some events which can cause unexpected behaviors. i.e., the azure function may not get executed for blob events because those can be missed.

Let’s see an example:

[FunctionName("ProfilePictureModify")]        
public static void Run([BlobTrigger("profile-pictures/{name}")] Stream blobRequest, string name, ILogger logger)
{
    log.LogInformation($"Function is executing for  blob name:{name} and Size: {myBlob.Length} Bytes");
}

Important Notes:

  • When a blob trigger function fails, it automatically retries up to 5 times. If all five retries are failed, the reference of blob execution is added into queue storage as a poisoned blob. (i.e., the blob will remain as-is in the storage account)
  • MaxDequeueCount is a setting that can be used to perform retries and is default set to 5.
  • By default, 24 blob functions can trigger concurrently due to the default limit, which can be changed in the host.json file.

For more information on the above points, read here –  “Concurrency and Memory usage.”

Table Storage

We will not look at table storage in more detail as the schematics are similar to blob storage with minor differences, which can be read from here – Table Storage Trigger.

Queue Storage

Queue storage triggered is amongst widely used azure function triggers which are quite identical to “ServiceBus,” “EventHub,” and “EventGrid.” So we will see examples of Queue Trigger and then cover key differences between others.

Queue triggered functions are invoked when a message is available in queue storage.

Let’s take an example:

[FunctionName("SendNotificationForGroupCreation")]
public static void Run(
    [QueueTrigger("group creation notification queue", Connection = "StorageConnectionSetting")] string groupCreationRequest, 
    ILogger log)
{
    log.LogInformation($"Function is executing request for {groupCreationRequest}");
}

From the above example, one can see that the function will be invoked/executed whenever a message is found in a queue named “group creation notification queue” in the storage account “StorageConnectionSetting.”

The incoming request can be a string (group creation request) or an object  (in which case azure runtime will deserialize the string to a provided type).

Important Notes:

  • If a function fails, azure runtime retries for five attempts, and if all attempts fail, then azure runtime will move the message to poison queue, i.e. {Original-Queue-Name}-poison.
  • By default, queue batch size is 16, i.e., Azure runtime will get 16 messages from the queue and execute them in parallel. These settings can be changed in the host.json file.

The key difference between Queue Storage Trigger and Service Bus Trigger:

  • While the queue storage triggered function can read messages from queue storage, the Service bus triggered function can read messages from queue and topics.
  • Retry count for queue storage is defined and handled by the function, whereas for service bus, it is defined based on message delivery count (and also function level).

The below table can be considered as a reference for supported trigger types per the azure function version.

Type 1.x 2.x and higher Trigger Input Output
Blob Storage
Azure Cosmos DB
Dapr3  
Event Grid  
Event Hubs  
HTTP & webhooks  
IoT Hub  
Kafka2    
Mobile Apps    
Notification Hubs      
Queue storage  
RabbitMQ2    
SendGrid    
Service Bus  
SignalR    
Table storage  
Timer    
Twilio    

Custom Azure Function Trigger

In the previous section, we saw different types of triggers that the Azure function supports. If none of those are not meeting your requirement, you can write down a custom trigger.

Primarily writing a custom extension involves two-part:

  1. Writing a Trigger – responsible for the invocation
  2. Writing a Binding – responsible for providing required data.

Let’s take a scenario of writing an azure function that executes based on the email received in one’s email box. (Assuming exchange or office 365 here)

Let’s first write down Trigger

[AttributeUsage(AttributeTargets.Parameter)]
[Binding]
public class OutlookTriggerAttribute: Attribute
{
    public string GraphConnectionString { get; set; }
    public string EmailId { get; set; }
    public string Password { get; set; }
    internal string GetOfficeGraphUri()
    {
        return Environment.GetEnvironmentVariable(GraphConnectionString );
    }
}

Now let’s implement a Listener responsible for reading messages:
public class OutlookListener: IListener
    {
        private readonly ITriggeredFunctionExecutor _executor;
        private readonly OutlookTriggerContext _context;
        public OutlookListener(ITriggeredFunctionExecutor executor, OutlookTriggerContext context)
        {
            _executor = executor;
            _context = context;
        }
        public void cancel()
        {
            if (_context == null || _context.Client == null) return;
            _context.Client.Disconnect();
        }

        public void Dispose()
        {
            _context.Client.Dispose();
        }


       public Task StartAsync(CancellationToken cancellationToken)
        {
            return Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    wtoken.Token.ThrowIfCancellationRequested();
	        //_context.Client.GetMessages();
                   // … Get message from graph API an
                    var triggerData = new TriggeredFunctionData
                    {
                        TriggerValue = outlookMessagePayload
                    };
                    var task = _executor.TryExecuteAsync(triggerData, CancellationToken.None);
                    task.Wait();
                    Task.Wait(10000);
                }
            }, wtoken, TaskCreationOptions.LongRunning);
        }
        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.Run(() =>{
                _context.Client.Disconnect();
            });
        }
    }
}

Let’s implement Outlook Trigger context.

    public class OutlookTriggerContext
    {
        public OutlookTriggerAttribute TriggerAttribute;
        public GraphApiClient Client;

        public OutlookTriggerContext(OutlookTriggerAttribute  attribute, GraphApiClient client)
        {
            this.TriggerAttribute = attribute;
            this.Client = client;
        }
    }

Now we will implement Binding for our outlook trigger

[assembly: WebJobsStartup(typeof(OutlookBinding.Startup))]
namespace WebJobs.Extension.Outlook
{
    public class OutlookBinding
    {
        public class Outlookup : IWebJobsStartup
        {
            public void Configure(IWebJobsBuilder builder)
            {
                builder.AddOutlookExtension();
            }
        }
    }
}

We need to add AddOutlookExtension method to finish with

public static class OutlookWebJobsBuilderExtensions
    {
        public static IWebJobsBuilder AddNatsExtension(this IWebJobsBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            builder.AddExtension<OutlookExtensionConfigProvider>();
            builder.Services.AddSingleton<IOutlookServiceFactory, OutlookServiceFactory>();
            return builder;
        }
    }

Now we can write down an azure function that can trigger based on the email you receive in the email box:

public static class OutlookTriggerFunction
    {
        [FunctionName("OutlookTriggerFunction")]
        public static void Run(
            [OutlookTrigger(GraphConnectionString = "mail.outlook.com", EmailId = "myemail@outlook.com", Password = "Password")] string outlookMessage,
            ILogger log)
        {
            log.LogInformation($"Message Received From outlook mailbox is {outlookMessage}");
        }
    }

Conclusion

I hope this blog has helped you learn about V2 and V3 triggers for Azure functions. Again, if you would like a blog on V1 as well, please feel free to mention it in the comments below. Also, if you have any other doubts, please comment below, and our DEV IT engineers will get back to you.