Explore 15 practical Microsoft Azure WebJobs examples with extensions. Learn triggered and continuous WebJobs, queues, timers, blobs, and real-world background job scenarios.
If you prefer video tutorials, you can watch the full examples here about Microsoft Azure WebJobs:
Table of Contents
Introduction to Microsoft Azure WebJobs
Microsoft Azure WebJobs is a feature of Azure App Service that lets you run programs or scripts as background tasks in the same context as your web application. You deploy a WebJob alongside your web app — no extra VM, no Function App, no AKS cluster. The WebJob shares the same App Service Plan, which means you pay for what you’re already running.
Azure WebJobs are ideal for tasks that don’t need to be web-facing: processing uploaded files, sending notification emails, aggregating analytics, cleaning up old records, or reacting to queue messages. The Azure WebJobs SDK provides trigger and binding extensions that let you connect to Azure Storage, Service Bus, and more with almost zero boilerplate.
WebJobs vs Azure Functions
Where Azure WebJobs shine
• Send batch email notifications by consuming messages from an Azure Storage Queue.
• Run nightly data cleanup jobs on a CRON schedule without a separate server.
• Fan out work by writing messages to a Service Bus topic and processing them in parallel WebJobs.
Continuous and Triggered WebJobs
Microsoft Azure WebJobs come in two execution modes. The right choice depends on whether your job needs to react to events continuously or run on a schedule.
| Type | How it runs | Restarts on crash | Best for |
|---|---|---|---|
| Continuous | Starts immediately, runs in an infinite loop | Yes — automatically | Queue listeners, real-time processing |
| Triggered | Runs once per trigger (manual or scheduled) | No — must re-trigger | CRON jobs, one-off tasks, reports |
Always-on requirement
Azure WebJobs Pricing
Azure WebJobs have no separate cost — they run inside your existing App Service Plan. You pay for the plan you already have, not for individual job executions.
Cost tip
Azure Create WebJob Within Web App
You can create and deploy a WebJob directly from the Azure Portal without any local tooling. This is the fastest way to get a simple script running.
- Open your App Service in the Azure Portal and find WebJobs under the Settings section in the left menu.
- Click + Add. Give the WebJob a name — no spaces, lowercase preferred.
- Upload a .zip file containing your executable or script. Azure supports
.exe,.cmd,.bat,.sh,.php,.py,.js, and compiled .NET binaries. - Select Type: Triggered or Continuous, and for Triggered choose the CRON schedule or leave it for manual trigger.
- Click OK. Azure deploys the job and you can click Run to trigger it manually from the portal.
my-webjob.zip
├── run.cmd ← entry point for Windows (required name)
├── MyJob.exe ← compiled binary
├── MyJob.dll
├── appsettings.json
└── settings.job ← optional: CRON schedule.NET Core Console App as Azure WebJob
The recommended way to build a Microsoft Azure WebJob in C# is a .NET Core Console Application using the Azure WebJobs SDK. The SDK gives you the IHost programming model with dependency injection, logging, and trigger bindings.
Install NuGet packages
# Core SDK
dotnet add package Microsoft.Azure.WebJobs.Extensions # v3+
dotnet add package Microsoft.Azure.WebJobs.Extensions.Storage # Blob + Queue
dotnet add package Microsoft.Extensions.Logging.Console
dotnet add package Microsoft.Extensions.HostingExample 1 — Minimal WebJob Host Setup
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateDefaultBuilder(args)
.ConfigureWebJobs(webJobsBuilder =>
{
webJobsBuilder.AddAzureStorageCoreServices(); // required for SDK
webJobsBuilder.AddAzureStorageQueues(); // Queue extension
webJobsBuilder.AddAzureStorageBlobs(); // Blob extension
})
.ConfigureLogging((context, logging) =>
{
logging.AddConsole();
})
.UseConsoleLifetime();
var host = builder.Build();
using (host)
{
await host.RunAsync();
}The Functions class is your job container.
[QueueTrigger] or [BlobTrigger]) automatically via reflection at startup — no manual registration needed.Publish WebJob to Azure Portal
Publishing a compiled .NET WebJob to Azure is a two-step process: build a Release binary, zip it with the right entry point, and upload it to the portal — or use Visual Studio’s built-in publish profile.
Publish WebJob via CLI (recommended for CI/CD)
# Publish self-contained binary
dotnet publish -c Release -r win-x64 --self-contained true -o ./publish
# Create the zip Azure expects
cd ./publish
zip -r ../my-webjob.zip .
# Upload via Azure CLI
az webapp webjob triggered upload \
--resource-group my-rg \
--name my-webapp \
--webjob-name my-webjob \
--webjob-zip my-webjob.zipPublish WebJob via Visual Studio
- Right-click the console project → Publish
- Choose Azure → Azure App Service (Windows) as target
- Select Publish as Azure WebJob — Visual Studio adds the correct
webjob-publish-settings.json - Choose Triggered or Continuous and hit Publish
{
"$schema": "http://schemastore.org/schemas/json/webjob-publish-settings.json",
"webJobName": "my-report-job",
"runMode": "OnDemand" // "Continuous" | "OnDemand" | "Schedule"
}Azure WebJob Write to File Structure
Azure WebJobs can read and write files on the App Service file system. The host provides well-known environment variables that point to the right directories — never hardcode paths.
Example 2 — Write output to the WebJob log folder
public class FileWriterJob
{
public static void WriteReport(TextWriter log)
{
// Azure sets WEBJOBS_PATH — use it, don't hardcode
var webJobsRoot = Environment.GetEnvironmentVariable("WEBJOBS_PATH")
?? Directory.GetCurrentDirectory();
var outputDir = Path.Combine(webJobsRoot, "output");
Directory.CreateDirectory(outputDir);
var filePath = Path.Combine(outputDir, $"report-{DateTime.UtcNow:yyyyMMdd-HHmm}.txt");
File.WriteAllText(filePath, $"Report generated at {DateTime.UtcNow:O}");
log.WriteLine($"Report written to: {filePath}");
}
}WEBJOBS_PATH → D:\home\site\wwwroot\App_Data\jobs\triggered\my-job\
WEBJOBS_DATA_PATH → D:\home\data\jobs\triggered\my-job\
HOME → D:\home
WEBROOT_PATH → D:\home\site\wwwroot
AzureWebJobsStorage → DefaultEndpointsProtocol=https;AccountName=...CRON Expression for Azure WebJobs
Azure WebJobs use a 6-field CRON expression (not 5-field like standard Unix cron). The extra field is seconds, prepended at the start.
{second} {minute} {hour} {day-of-month} {month} {day-of-week}
Example: "0 30 9 * * 1-5" means "at 09:30 on every weekday"
↑ ↑ ↑ ↑ ↑ ↑
s m h d M DoWCommon CRON expressions
0 */5 * * * *
0 0 * * * *
0 30 9 * * 1-5
0 0 2 * * *
0 0 0 1 * *
0 0 0 * * 0
WEBSITE_TIME_ZONE application setting on your App Service. Don’t assume local time.Schedule WebJob
There are two ways to schedule a triggered Azure WebJob: via a settings.job file inside the zip, or via Application Settings in the portal. The settings.job file approach is the code-first, version-controllable option.
Example 3 — settings.job file
{
"schedule": "0 0 2 * * *"
}Place settings.job in the root of your zip alongside your executable. Azure reads it at deployment time and registers the CRON trigger automatically.
Example 4 — TimerTrigger via WebJobs SDK
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
public class ScheduledFunctions
{
// Runs every day at 2:00 AM UTC
public static void DailyCleanup(
[TimerTrigger("0 0 2 * * *")] TimerInfo timer,
ILogger log)
{
log.LogInformation($"Daily cleanup started at {DateTime.UtcNow:O}");
if (timer.IsPastDue)
log.LogWarning("Timer is running late!");
// Your cleanup logic here
log.LogInformation("Cleanup completed.");
}
}Schedule WebJob in Settings
For environments where you don’t want to redeploy to change a schedule, store the CRON expression in an Application Setting and reference it with the %SETTING_NAME% syntax inside the [TimerTrigger] attribute.
Example 5 — CRON schedule from App Settings
public class ConfigurableSchedule
{
// WEBJOB_SCHEDULE is an App Service Application Setting
public static void Run(
[TimerTrigger("%WEBJOB_SCHEDULE%")] TimerInfo timer,
ILogger log)
{
log.LogInformation("Job triggered by configurable schedule.");
}
}Name: WEBJOB_SCHEDULE
Value: 0 0 6 * * 1-5 ← 6 AM on weekdays, change without redeploymentOperations-friendly pattern
Azure WebJobs Logging
Azure WebJobs SDK integrates with Microsoft.Extensions.Logging. Logs are automatically streamed to the Azure Portal’s Log Stream, written to the WebJob dashboard, and can be forwarded to Application Insights.
Example 6 — Structured logging with ILogger
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
public class LoggingExample
{
private readonly ILogger<LoggingExample> _log;
public LoggingExample(ILogger<LoggingExample> log) => _log = log;
public void ProcessItem(
[QueueTrigger("work-queue")] string message)
{
// Structured log — indexed in App Insights
_log.LogInformation(
"Processing queue message. Length={MessageLength}",
message.Length);
try
{
DoWork(message);
_log.LogInformation("Message processed successfully.");
}
catch (Exception ex)
{
_log.LogError(ex, "Failed to process message: {Message}", message);
throw; // rethrow so SDK can handle retry/poison
}
}
private void DoWork(string msg) { /* business logic */ }
}Enable Application Insights
var builder = Host.CreateDefaultBuilder(args)
.ConfigureWebJobs(b => b.AddAzureStorageCoreServices())
.ConfigureLogging((ctx, logging) =>
{
logging.AddConsole();
string appInsightsKey = ctx.Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"];
if (!string.IsNullOrEmpty(appInsightsKey))
{
logging.AddApplicationInsights(appInsightsKey);
logging.AddFilter<ApplicationInsightsLoggerProvider>(
"", LogLevel.Information);
}
});Azure WebJobs SDK Overview
The Azure WebJobs SDK is the programming model that turns a plain C# class into a jobs host with triggers, bindings, retries, and scaling. Understanding its three core concepts unlocks all the extension examples below.
| Concept | What it does | Examples |
|---|---|---|
| Triggers | Cause a function to run when an event occurs | [QueueTrigger], [BlobTrigger], [TimerTrigger], [ServiceBusTrigger] |
| Input bindings | Read additional data when the function starts | [Blob("container/{name}")] as Stream |
| Output bindings | Write data declaratively without manual SDK calls | [Queue("output")] as out string |
Extension packages
Microsoft.Azure.WebJobs.Extensions.Storage covers Blob and Queue. Microsoft.Azure.WebJobs.Extensions.ServiceBus covers Service Bus. Microsoft.Azure.WebJobs.Extensions covers Timer, HTTP, and more.Blob Extensions Example
The Azure WebJobs blob extension triggers your function whenever a new blob is created in a container, and lets you read or write blobs as input/output bindings.
Example 7 — Process new blob on upload
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
public class BlobFunctions
{
// Triggers when any blob appears in "uploads" container
public static async Task ProcessUploadedFile(
[BlobTrigger("uploads/{name}", Connection = "AzureWebJobsStorage")]
Stream inputBlob,
string name,
[Blob("processed/{name}", FileAccess.Write)]
Stream outputBlob,
ILogger log)
{
log.LogInformation($"Processing blob: {name}, Size: {inputBlob.Length} bytes");
// Transform: copy to processed container
await inputBlob.CopyToAsync(outputBlob);
log.LogInformation($"Blob {name} copied to processed container.");
}
}Example 8 — Write blob as output binding
public static void GenerateReport(
[TimerTrigger("0 0 1 * * *")] TimerInfo timer,
[Blob("reports/daily-{DateTime}.txt", FileAccess.Write)]
out string reportContent,
ILogger log)
{
reportContent = $"Daily report generated at {DateTime.UtcNow:O}\n"
+ "Records processed: 1,452";
log.LogInformation("Daily report written to blob storage.");
}[QueueTrigger] instead.Queue Extensions Example
The Azure WebJobs queue extension is the most common trigger in practice. A message appears in Azure Storage Queue, and your WebJob function is called with the message body — automatically deserialized if it’s JSON.
Example 9 — QueueTrigger with POCO deserialization
public record InvoiceRequest(int InvoiceId, string CustomerEmail);
public class QueueFunctions
{
private readonly IEmailService _email;
public QueueFunctions(IEmailService email) => _email = email;
// SDK deserializes JSON automatically into InvoiceRequest
public async Task SendInvoiceEmail(
[QueueTrigger("invoice-requests")] InvoiceRequest request,
ILogger log)
{
log.LogInformation(
"Sending invoice {InvoiceId} to {Email}",
request.InvoiceId, request.CustomerEmail);
await _email.SendAsync(request.CustomerEmail, request.InvoiceId);
log.LogInformation("Invoice email sent.");
}
}Example 10 — Queue output binding (fan-out)
public static void FanOutOrders(
[TimerTrigger("0 0 8 * * *")] TimerInfo timer,
[Queue("order-processing")] ICollector<OrderTask> queue,
ILogger log)
{
var orders = GetPendingOrders();
foreach (var order in orders)
{
queue.Add(new OrderTask { OrderId = order.Id, Priority = order.Priority });
log.LogInformation("Queued order {OrderId}", order.Id);
}
log.LogInformation("{Count} orders queued for processing.", orders.Count);
}Poison Queue Notify
When a Azure WebJobs queue message fails processing 5 times (by default), the SDK moves it to a poison queue — a queue named {original-queue-name}-poison. You should always monitor and handle poison messages explicitly.
Example 11 — Poison queue notification handler
public class PoisonQueueHandler
{
private readonly IAlertService _alerts;
public PoisonQueueHandler(IAlertService alerts) => _alerts = alerts;
// Triggered by the SDK's poison queue: "invoice-requests-poison"
public async Task HandlePoisonMessage(
[QueueTrigger("invoice-requests-poison")] string poisonMessage,
ILogger log)
{
log.LogError("Poison message detected: {Message}", poisonMessage);
// Alert the on-call team
await _alerts.SendPagerDutyAlertAsync($"Poison queue message:\n{poisonMessage}");
// Optionally: parse and attempt recovery, or write to dead-letter store
await WriteToDeadLetterStoreAsync(poisonMessage);
}
private Task WriteToDeadLetterStoreAsync(string msg)
{
// Write to a database / blob for audit trail
return Task.CompletedTask;
}
}Configure retry count
.ConfigureWebJobs(b =>
{
b.AddAzureStorageQueues(opts =>
{
opts.MaxDequeueCount = 3; // move to poison after 3 failures (default is 5)
opts.BatchSize = 8; // process 8 messages in parallel per instance
opts.NewBatchThreshold = 4; // fetch new batch when 4 remain
});
})Service Bus Extensions Example
The Azure WebJobs Service Bus extension connects your WebJob to Azure Service Bus queues and topics. Service Bus adds features not available in Storage Queues: message sessions, duplicate detection, scheduled delivery, and topic subscriptions for pub/sub patterns.
Install Service Bus extension
dotnet add package Microsoft.Azure.WebJobs.Extensions.ServiceBusExample 12 — ServiceBusTrigger on a queue
using Azure.Messaging.ServiceBus;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
public class ServiceBusFunctions
{
public static async Task ProcessOrder(
[ServiceBusTrigger("orders-queue", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions messageActions,
ILogger log)
{
var body = message.Body.ToString();
log.LogInformation("Received SB message: {Body}", body);
try
{
var order = System.Text.Json.JsonSerializer.Deserialize<Order>(body);
await ProcessOrderAsync(order);
// Explicitly complete — remove from queue
await messageActions.CompleteMessageAsync(message);
}
catch (Exception ex)
{
log.LogError(ex, "Order processing failed.");
// Abandon — returns to queue for retry
await messageActions.AbandonMessageAsync(message);
}
}
private static Task ProcessOrderAsync(Order order) => Task.CompletedTask;
}Example 13 — Subscribe to a Service Bus topic
// Topic: "order-events", Subscription: "email-notifications"
public static void OnOrderEvent(
[ServiceBusTrigger(
topicName: "order-events",
subscriptionName: "email-notifications",
Connection: "ServiceBusConnection")]
string eventBody,
ILogger log)
{
log.LogInformation("Order event received: {Event}", eventBody);
// Multiple subscriptions = multiple WebJobs all receive the same event
}Register Service Bus in Program.cs
.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddServiceBus(opts =>
{
opts.AutoCompleteMessages = false; // we complete manually
opts.MaxConcurrentCalls = 4;
opts.MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(5);
});
})Singleton for Continuous WebJob
When multiple instances of your App Service are running (scale-out), every instance runs its own copy of a continuous WebJob. This can cause duplicate processing. The [Singleton] attribute on the Azure WebJobs SDK ensures only one instance executes a function at a time across all App Service instances.
Example 14 — Singleton lock for continuous WebJob
using Microsoft.Azure.WebJobs;
public class SingletonFunctions
{
// Only one instance runs this across all App Service instances
[Singleton]
public static async Task ProcessWithSingletonLock(
[QueueTrigger("critical-jobs")] string message,
ILogger log)
{
log.LogInformation("Executing under singleton lock: {Message}", message);
await Task.Delay(5000); // simulate work
}
// Singleton scoped per message property — allows parallelism per tenant
[Singleton("{TenantId}")]
public static void ProcessPerTenant(
[QueueTrigger("tenant-jobs")] TenantJob job,
ILogger log)
{
// One concurrent execution per TenantId, parallel across tenants
log.LogInformation("Processing tenant {TenantId}", job.TenantId);
}
}How [Singleton] works
AzureWebJobsStorage connection to be configured.Azure WebJob Continuous Deployment
Deploying Azure WebJobs manually via zip upload doesn’t scale to a team. Use GitHub Actions or Azure DevOps to automate build, test, and deployment on every push to main.
Example 15 — GitHub Actions CI/CD pipeline
name: Deploy Azure WebJob
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build WebJob
run: |
dotnet publish src/MyWebJob/MyWebJob.csproj \
-c Release -r win-x64 \
--self-contained true \
-o ./publish/App_Data/jobs/triggered/my-webjob
- name: Zip artifact
run: zip -r webjob.zip ./publish
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v3
with:
app-name: 'my-webapp'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: webjob.zipZero-downtime WebJob deployment
Store the
AZURE_WEBAPP_PUBLISH_PROFILE secret from the Azure Portal under App Service → Get Publish Profile. Re-download and update the secret whenever you reset credentials.Structure your repo for WebJob co-location
my-solution/
├── src/
│ ├── MyWebApp/ ← ASP.NET Core web application
│ │ └── App_Data/
│ │ └── jobs/
│ │ └── triggered/
│ │ └── my-webjob/ ← WebJob deployed here
│ └── MyWebJob/ ← Console app project
│ ├── Functions.cs
│ ├── Program.cs
│ └── settings.job
├── tests/
│ └── MyWebJob.Tests/
└── .github/
└── workflows/
└── deploy-webjob.ymlConclusion — Choosing the Right Azure WebJob Pattern
| Scenario | Recommended pattern | Extension |
|---|---|---|
| React to file uploads | Continuous + BlobTrigger | Azure WebJobs blob |
| Process work items reliably | Continuous + QueueTrigger | Azure WebJobs queue |
| Pub/sub event routing | Continuous + ServiceBusTrigger (topic) | Azure WebJobs Service Bus |
| Nightly / scheduled batch | Triggered + TimerTrigger CRON | Azure WebJobs extensions |
| Prevent duplicate processing on scale-out | Continuous + [Singleton] | WebJobs SDK built-in |
| Handle failed messages | Separate WebJob on poison queue | Azure WebJobs queue |
Microsoft Azure WebJobs remain one of the most practical tools in the Azure ecosystem for background processing — especially when you already have an App Service and want to add background work without adding infrastructure complexity. The Azure WebJobs SDK with its Blob, Queue, and Service Bus extensions handles the heavy lifting of trigger polling, deserialization, retries, poison queue routing, and distributed locking so your code stays focused on business logic.
The 15 examples in this guide cover the full lifecycle: from setting up a .NET Core Console App as an Azure WebJob, through scheduling with CRON expressions, structured logging, all three major extensions, and ending with a production CI/CD pipeline via GitHub Actions. Apply the patterns that fit your scale requirements today, and know that upgrading to Azure Functions later is straightforward — the SDK programming model is deliberately similar.
