← Back to all posts

Azure Function Triggers in .NET

Azure Function Triggers in .NET

Reading Time: 8 minutes

The Problem

Most event-driven work in a backend system follows the same pattern: something happens, your code needs to respond.

A row is inserted into a database. A file is uploaded. A message arrives on a queue. A timer fires. A webhook is called.

Without a trigger-based execution model, the default approach is polling: a long-running background service that constantly checks for changes and reacts when it finds some.

Polling works but it has a cost:

  • compute runs continuously even when nothing is happening
  • lower polling frequency means higher latency for event detection
  • higher polling frequency wastes resources and creates load on the source
  • each new event source requires its own polling loop and error-handling logic

The result is background services that are hard to maintain, inefficient at rest, and surprisingly complex for what should be simple reactive logic.

The Solution

Azure Functions are built around a trigger model.

A trigger is what causes a function to execute. Instead of polling for changes, the Azure Functions runtime watches the event source on your behalf and invokes your function when a condition is met.

That means:

  • no polling loops to write or maintain
  • your code runs only when there is something to handle
  • scaling is driven by event volume rather than manual configuration
  • event sources are integrated through a consistent binding model

Each Azure Function has exactly one trigger. The trigger defines both when the function runs and what input data the function receives.

How Triggers and Bindings Work

Trigger - the event that causes the function to execute.

Binding - a declarative way to connect inputs and outputs without writing plumbing code.

Input bindings let you read additional data when the function runs. Output bindings let you write results to external systems without manually calling SDKs.

For example, a function triggered by a Service Bus message could:

  • receive the message automatically via the trigger binding
  • read configuration from Azure App Configuration via an input binding
  • write a result to Azure Cosmos DB via an output binding

All of that without writing connection management code directly in the function body.

The Isolated Process Model

All examples below use the Isolated Process worker model, which is the current standard for .NET.

Program.cs setup:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
    })
    .Build();

await host.RunAsync();

HTTP Trigger

The most familiar trigger. The function runs when a matching HTTP request arrives.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public class OrdersFunction
{
    private readonly ILogger<OrdersFunction> _logger;

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

    [Function("CreateOrder")]
    public IActionResult CreateOrder(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequest req)
    {
        _logger.LogInformation("CreateOrder triggered");

        // read body, process, return
        return new OkObjectResult(new { orderId = Guid.NewGuid() });
    }
}

Good for: webhooks, lightweight API endpoints, callbacks from external systems.

Timer Trigger

The function runs on a schedule defined by a CRON expression.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public class CleanupFunction
{
    private readonly ILogger<CleanupFunction> _logger;

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

    [Function("DailyCleanup")]
    public void Run([TimerTrigger("0 0 2 * * *")] TimerInfo timerInfo)
    {
        _logger.LogInformation("Daily cleanup started at {Time}", DateTime.UtcNow);

        if (timerInfo.ScheduleStatus?.Last is not null)
        {
            _logger.LogInformation("Last run was at {LastRun}", timerInfo.ScheduleStatus.Last);
        }

        // perform cleanup work
    }
}

Good for: scheduled maintenance, report generation, periodic data sync.

The CRON expression 0 0 2 * * * runs the function at 02:00 UTC every day.

Blob Storage Trigger

The function runs when a blob is created or modified in a specified container.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public class BlobProcessorFunction
{
    private readonly ILogger<BlobProcessorFunction> _logger;

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

    [Function("ProcessUploadedFile")]
    public void Run(
        [BlobTrigger("uploads/{name}", Connection = "AzureWebJobsStorage")] Stream blobStream,
        string name)
    {
        _logger.LogInformation("Processing uploaded file: {FileName}, Size: {Size} bytes", name, blobStream.Length);

        // process contents of the uploaded blob
    }
}

Good for: image resizing after upload, document processing pipelines, ETL workflows triggered by file arrival.

The {name} placeholder in the path pattern is extracted from the blob name and injected into the function parameter.

Service Bus Trigger

The function runs when a message arrives on an Azure Service Bus queue or topic subscription.

using Azure.Messaging.ServiceBus;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public class OrderProcessorFunction
{
    private readonly ILogger<OrderProcessorFunction> _logger;

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

    [Function("ProcessOrderMessage")]
    public void Run(
        [ServiceBusTrigger("orders-queue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message)
    {
        _logger.LogInformation("Processing order message. MessageId: {MessageId}", message.MessageId);

        var body = message.Body.ToString();
        _logger.LogInformation("Message body: {Body}", body);

        // process the order
    }
}

Good for: decoupled order processing, reliable async command handling, fan-out architectures using topics and subscriptions.

The Service Bus trigger handles message locking, retry, and deadlettering automatically. If the function throws, the message is not completed and will be retried according to the queue policy.

Service Bus with typed deserialization

A more production-friendly pattern deserializes the message body into a typed record:

using System.Text.Json;
using Azure.Messaging.ServiceBus;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public sealed record OrderPlacedMessage(Guid OrderId, string CustomerId, decimal Total);

public class TypedOrderProcessorFunction
{
    private readonly ILogger<TypedOrderProcessorFunction> _logger;

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

    [Function("ProcessTypedOrderMessage")]
    public void Run(
        [ServiceBusTrigger("orders-queue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message)
    {
        var order = JsonSerializer.Deserialize<OrderPlacedMessage>(message.Body.ToString());

        if (order is null)
        {
            _logger.LogWarning("Received null or invalid order message. MessageId: {MessageId}", message.MessageId);
            return;
        }

        _logger.LogInformation("Processing order {OrderId} for customer {CustomerId}", order.OrderId, order.CustomerId);
    }
}

Azure SQL Trigger

The Azure SQL trigger watches for changes in a SQL Server or Azure SQL table and invokes the function when rows are inserted or updated.

It uses SQL change tracking under the hood, so the table must have change tracking enabled.

-- Enable change tracking on the database
ALTER DATABASE [YourDatabase]
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON);

-- Enable change tracking on the target table
ALTER TABLE [dbo].[Orders]
ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED = ON);
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public sealed record Order(int Id, string CustomerId, string Status, decimal Total);

public class SqlOrderTriggerFunction
{
    private readonly ILogger<SqlOrderTriggerFunction> _logger;

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

    [Function("OnOrderChanged")]
    public void Run(
        [SqlTrigger("[dbo].[Orders]", Connection = "SqlConnectionString")] IReadOnlyList<SqlChange<Order>> changes)
    {
        foreach (var change in changes)
        {
            _logger.LogInformation(
                "Order change detected: Operation={Operation}, OrderId={OrderId}, Status={Status}",
                change.Operation,
                change.Item.Id,
                change.Item.Status);
        }
    }
}

The SqlChange<T> wrapper carries both the changed item and the operation type (Insert, Update, Delete).

Good for: near-real-time reactions to database changes without polling, event sourcing from SQL, sync pipelines between SQL and other systems.

Blob Storage Trigger with Output Binding

Triggers can be combined with output bindings to produce results in other systems.

This example processes an uploaded file and writes a result record to Cosmos DB:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public class BlobToCosmosFunction
{
    private readonly ILogger<BlobToCosmosFunction> _logger;

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

    [Function("ProcessAndStore")]
    [CosmosDBOutput("file-processing", "results", Connection = "CosmosDBConnection", CreateIfNotExists = true)]
    public object Run(
        [BlobTrigger("incoming/{name}", Connection = "AzureWebJobsStorage")] Stream blobStream,
        string name)
    {
        _logger.LogInformation("Processing {FileName}", name);

        return new
        {
            id = Guid.NewGuid().ToString(),
            fileName = name,
            processedAt = DateTime.UtcNow,
            sizeInBytes = blobStream.Length
        };
    }
}

This pattern is useful for pipelines where file arrival drives downstream persistence without writing any explicit SDK code for Cosmos DB.

Choosing the Right Trigger

TriggerBest for
HTTPWebhooks, lightweight APIs, callbacks
TimerScheduled jobs, cleanup, reports
Blob StorageFile processing pipelines, ETL on upload
Service BusReliable async messaging, decoupled workflows
Azure SQLReact to database changes without polling
Event HubHigh-throughput event stream processing
Cosmos DBReact to NoSQL document changes
Queue StorageSimple async task queues

Connection Security

All connection strings for triggers and bindings should come from:

  • application settings (environment variables in production)
  • Azure Key Vault references in application settings
  • managed identity where supported (Service Bus, Blob Storage, Cosmos DB all support this)

Never hardcode connection strings in function code or local.settings.json committed to source control.

Example using managed identity for Service Bus:

[ServiceBusTrigger("orders-queue", Connection = "ServiceBusConnection__fullyQualifiedNamespace")]

And in application settings:

ServiceBusConnection__fullyQualifiedNamespace = your-namespace.servicebus.windows.net

With a managed identity assigned and the appropriate role, no connection string or key is needed.

Summary

Azure Function triggers replace polling with event-driven invocation.

The runtime watches your event sources - queues, blobs, databases, schedules, HTTP endpoints - and calls your function code only when something happens.

That model is:

  • more efficient than polling at rest
  • easier to scale under load
  • simpler to maintain than custom background workers
  • consistent across a wide range of event sources

Each trigger type is designed for a specific event shape. Choosing the right one comes down to where the event originates and how it needs to be processed.

For all .NET functions, use the Isolated Process model. Wire connections through app settings or Key Vault references, prefer managed identity where the service supports it, and let the trigger infrastructure handle the event delivery.

References