Azure Functions is Microsoft's serverless compute platform. You deploy a function, configure a trigger, and Azure handles scaling, infrastructure, and billing by execution. For .NET developers, it's a natural fit — you write C#, use the same NuGet ecosystem, and get the same IDE experience.
The Basics: Triggers and Bindings
Every Azure Function needs a trigger — the event that causes it to run. Common triggers include:
- HTTP — Runs on an incoming HTTP request (REST API)
- Timer — Runs on a CRON schedule
- Service Bus — Runs when a message arrives on a queue or topic
- Blob Storage — Runs when a file is created or modified
Bindings are optional. They declaratively connect your function to Azure services for input or output — no SDK code required:
[Function("ProcessOrder")]
[ServiceBusOutput("orders-processed", Connection = "ServiceBusConnection")]
public string Run(
[ServiceBusTrigger("orders-incoming", Connection = "ServiceBusConnection")]
string message,
FunctionContext context)
{
var logger = context.GetLogger("ProcessOrder");
logger.LogInformation("Processing order: {Message}", message);
return message; // output binding sends this to orders-processed
}
The return value goes to the output binding — no manual SDK call needed to put a message on a queue.
The Isolated Worker Model
Azure Functions for .NET now uses the Isolated Worker Model by default. Your function runs in a separate .NET process from the Functions host, which means:
- Full control over the .NET version (target .NET 8 or 9 without waiting for host support)
- Standard dependency injection via
HostBuilder - Middleware support
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddSingleton<IOrderService, OrderService>();
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Environment.GetEnvironmentVariable("SqlConnection")));
})
.Build();
host.Run();
Services registered here are available for injection into your function classes.
Local Development
The Azure Functions Core Tools (func) run the Functions host locally. Paired with Azurite (the Azure Storage emulator) and the Service Bus emulator, you can develop and test most triggers locally without touching Azure.
npm install -g azure-functions-core-tools@4
func start
Use local.settings.json for environment variables locally — the equivalent of Application Settings in Azure:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"SqlConnection": "Server=localhost;Database=Orders;..."
}
}
Never commit local.settings.json — it contains secrets. Add it to .gitignore.
Deployment with CI/CD
A typical GitHub Actions pipeline for Azure Functions:
- name: Build
run: dotnet publish -c Release -o ./output
- name: Deploy to Azure Functions
uses: Azure/functions-action@v1
with:
app-name: my-functions-app
package: ./output
publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
Alternatively, use Azure CLI or Bicep for infrastructure-as-code deployments. The publish profile approach is quick for getting started; managed identity with OIDC is the production-grade choice.
Cold Starts and the Consumption Plan
On the Consumption plan, Functions scale to zero when idle. The first invocation after a period of inactivity incurs a cold start — typically 1–3 seconds for .NET. Strategies to mitigate:
- Use the Flex Consumption or Premium plan for pre-warmed instances
- Keep function assemblies small (trim unused packages)
- Use the Isolated Worker model (faster than in-process for cold starts)
When to Use Azure Functions
Azure Functions excel at:
- Lightweight HTTP APIs with variable traffic
- Background jobs triggered by queues or timers
- Event-driven processing (file uploads, database changes)
- Glue code connecting Azure services
For long-running processes, stateful workflows, or high-throughput scenarios with consistent load, Azure Container Apps or App Service is often a better fit.