Skip to content

07 - Extending with Triggers (Premium)

Extend beyond HTTP using queue, blob, and timer triggers with the .NET isolated worker model and clear operational checks.

Prerequisites

Tool Version Purpose
.NET SDK 8.0 (LTS) Build and run isolated worker functions
Azure Functions Core Tools v4 Start local host and publish artifacts
Azure CLI 2.61+ Provision Azure resources and inspect app state

Premium plan basics

Premium (EP1) keeps at least one warm instance, supports VNet integration, private endpoints, and deployment slots. No cold-start penalty, with up to 100 instances and no execution timeout.

What You'll Build

You will add queue, blob, and timer triggers to a .NET isolated worker Function App using attribute-based bindings, create the required storage resources, and validate end-to-end trigger firing.

flowchart TD
    A[Queue message] --> B["[QueueTrigger] handler"]
    C[Blob upload] --> D["[BlobTrigger] handler"]
    E[Schedule] --> F["[TimerTrigger] handler"]
    B --> G[Outputs and logs]
    D --> G
    F --> G

Steps

Step 1 - Create storage resources for triggers

# Create queue for queue trigger
az storage queue create \
  --name "incoming-orders" \
  --account-name "$STORAGE_NAME"

# Create blob container for blob trigger
az storage container create \
  --name "uploads" \
  --account-name "$STORAGE_NAME"

Step 2 - Review the queue trigger function

The reference app includes QueueProcessorFunction.cs:

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

namespace AzureFunctionsGuide.Functions;

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

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

    [Function("queueProcessor")]
    public void Run(
        [QueueTrigger("incoming-orders", Connection = "QueueStorage")] string message)
    {
        _logger.LogInformation("Queue message received: {Message}", message);
    }
}

QueueStorage must use a real connection string

The Connection = "QueueStorage" attribute references the QueueStorage app setting. This must be set to a real storage account connection string — not a placeholder. A fake connection string causes 403 errors when the queue listener starts.

Step 3 - Review the blob trigger function

The reference app includes BlobProcessorFunction.cs:

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

namespace AzureFunctionsGuide.Functions;

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

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

    [Function("blobProcessor")]
    public void Run(
        [BlobTrigger("uploads/{name}", Connection = "AzureWebJobsStorage")] string content,
        string name)
    {
        _logger.LogInformation("Blob processed: {Name}, size: {Size} chars", name, content.Length);
    }
}

Step 4 - Review the timer trigger function

The reference app includes ScheduledCleanupFunction.cs:

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

namespace AzureFunctionsGuide.Functions;

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

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

    [Function("scheduledCleanup")]
    public void Run(
        [TimerTrigger("0 0 2 * * *")] TimerInfo timer)
    {
        _logger.LogInformation("Scheduled cleanup fired at {Timestamp}", DateTime.UtcNow.ToString("o"));

        if (timer.IsPastDue)
        {
            _logger.LogWarning("Timer trigger is past due");
        }
    }
}

Step 5 - Build and publish

cd apps/dotnet
dotnet publish --configuration Release --output ./publish

cd publish
func azure functionapp publish "$APP_NAME" --dotnet-isolated

Step 6 - Validate trigger resources

# List queues
az storage queue list \
  --account-name "$STORAGE_NAME" \
  --output table

# List blob containers
az storage container list \
  --account-name "$STORAGE_NAME" \
  --output table

Step 7 - Test queue trigger

# Send a test message to the queue
az storage message put \
  --queue-name "incoming-orders" \
  --account-name "$STORAGE_NAME" \
  --content "test-order-dotnet-001"

# Check Application Insights for the processed message (wait 2-5 minutes)
az monitor app-insights query \
  --app "$APP_NAME" \
  --resource-group "$RG" \
  --analytics-query "traces | where message contains 'Queue message received' | order by timestamp desc | take 5"

Step 8 - Test blob trigger

# Upload a test file
echo "hello blob trigger from dotnet" > /tmp/test-dotnet-upload.txt
az storage blob upload \
  --container-name "uploads" \
  --name "test-dotnet-upload.txt" \
  --file "/tmp/test-dotnet-upload.txt" \
  --account-name "$STORAGE_NAME" \
  --overwrite

# Check Application Insights for the processed blob (wait 2-5 minutes)
az monitor app-insights query \
  --app "$APP_NAME" \
  --resource-group "$RG" \
  --analytics-query "traces | where message contains 'Blob processed' | order by timestamp desc | take 5"

Step 9 - Verify all functions are registered

az functionapp function list \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --output table

Verification

Storage queue list:

Name
----------------
incoming-orders

Storage container list (showing trigger-related containers):

Name
---------
uploads

Function list showing all trigger types:

[
  {
    "name": "queueProcessor",
    "language": "dotnet-isolated"
  },
  {
    "name": "blobProcessor",
    "language": "dotnet-isolated"
  },
  {
    "name": "scheduledCleanup",
    "language": "dotnet-isolated"
  },
  {
    "name": "timerLab",
    "language": "dotnet-isolated"
  },
  {
    "name": "helloHttp",
    "language": "dotnet-isolated"
  },
  {
    "name": "health",
    "language": "dotnet-isolated"
  }
]

All 16 functions deployed and verified:

Function Type Status
health HTTP GET ✅ 200
helloHttp HTTP GET ✅ 200
info HTTP GET ✅ 200
logLevels HTTP GET ✅ 200
slowResponse HTTP GET ✅ 200
testError HTTP GET ✅ 500 (expected)
unhandledError HTTP GET ✅ 500 (expected)
dnsResolve HTTP GET ✅ 200
identityProbe HTTP GET ✅ 200
storageProbe HTTP GET ✅ 200
externalDependency HTTP GET ✅ 200
queueProcessor Queue ✅ Message consumed
blobProcessor Blob ✅ Registered
scheduledCleanup Timer ✅ Registered
timerLab Timer ✅ Registered
eventhubLagProcessor EventHub ✅ Registered

Clean Up

az group delete --name "$RG" --yes --no-wait

Next Steps

Done! You have completed all Premium plan tutorials for .NET. Try another hosting plan:

See Also

Sources