
Azure Functions
- 16 installs
- 466 repo stars
- Updated July 25, 2026
- managedcode/dotnet-skills
Helps with ai & agent building tasks.
About
azure-functions is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- azure-functions
- AI & Agent Building
- AI-coding skill
Azure Functions by the numbers
- 16 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #11,068 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/managedcode/dotnet-skills --skill azure-functionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 16 |
|---|---|
| repo stars | ★ 466 |
| Last updated | July 25, 2026 |
| Repository | managedcode/dotnet-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Azure Functions for .NET
Trigger On
- working on Azure Functions in .NET
- migrating from the in-process model to the isolated worker model
- adding Durable Functions, bindings, or host configuration
Documentation
- Guide for running C# Azure Functions in an isolated worker process
- Differences between in-process and isolated worker process
- Migrate C# app from in-process to isolated worker model
- Durable Functions overview
- Durable Functions best practices and diagnostic tools
References
- Patterns - Isolated worker patterns, Durable Functions patterns, advanced binding patterns
- Anti-Patterns - Common Azure Functions mistakes and how to avoid them
Workflow
1. Use isolated worker model for all new work:
- In-process model reaches end of support on November 10, 2026
- Runtime v1.x ends support on September 14, 2026
- Target .NET 8+ for longest support window
2. Detect current project shape:
- Target framework and runtime version
- Worker model (isolated vs in-process)
- Binding packages and host configuration
3. Use standard .NET patterns in isolated model:
- Normal dependency injection
- Middleware pipeline
IOptions<T>for configurationILogger<T>for logging
4. For Durable Functions:
- Validate orchestration determinism constraints
- Handle replay behavior correctly
- Use typed activity patterns
5. Verify both local and deployment behavior.
Isolated Worker Model Setup
Basic Function with DI
// Program.cs
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureServices(services =>
{
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();
services.AddSingleton<IMyService, MyService>();
})
.Build();
host.Run();HTTP Trigger Function
public class HttpFunctions(ILogger<HttpFunctions> logger, IMyService myService)
{
[Function("GetItems")]
public async Task<IActionResult> GetItems(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "items")] HttpRequest req)
{
logger.LogInformation("Processing GetItems request");
var items = await myService.GetItemsAsync();
return new OkObjectResult(items);
}
}Queue Trigger with Options
public class QueueFunctions(ILogger<QueueFunctions> logger, IOptions<ProcessingOptions> options)
{
[Function("ProcessMessage")]
public async Task ProcessMessage(
[QueueTrigger("myqueue", Connection = "AzureWebJobsStorage")] string message)
{
logger.LogInformation("Processing message: {Message}", message);
// Process with retry policy from options
}
}Middleware Pattern
Custom Middleware
// Program.cs
var host = new HostBuilder()
.ConfigureFunctionsWebApplication(builder =>
{
builder.UseMiddleware<ExceptionHandlingMiddleware>();
builder.UseMiddleware<CorrelationIdMiddleware>();
})
.Build();
// CorrelationIdMiddleware.cs
public class CorrelationIdMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
var correlationId = context.Features.Get<IHttpRequestFeature>()?.Headers["X-Correlation-Id"]
?? Guid.NewGuid().ToString();
context.Items["CorrelationId"] = correlationId;
await next(context);
}
}Durable Functions Patterns
Function Chaining
[Function(nameof(ChainOrchestrator))]
public static async Task<string> ChainOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var result1 = await context.CallActivityAsync<string>(nameof(Step1), "input");
var result2 = await context.CallActivityAsync<string>(nameof(Step2), result1);
var result3 = await context.CallActivityAsync<string>(nameof(Step3), result2);
return result3;
}
[Function(nameof(Step1))]
public static string Step1([ActivityTrigger] string input) => $"Step1({input})";
[Function(nameof(Step2))]
public static string Step2([ActivityTrigger] string input) => $"Step2({input})";
[Function(nameof(Step3))]
public static string Step3([ActivityTrigger] string input) => $"Step3({input})";Fan-Out/Fan-In
[Function(nameof(FanOutFanInOrchestrator))]
public static async Task<int[]> FanOutFanInOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var workItems = await context.CallActivityAsync<string[]>(nameof(GetWorkItems), null);
// Fan out - process all items in parallel
var tasks = workItems.Select(item =>
context.CallActivityAsync<int>(nameof(ProcessWorkItem), item));
// Fan in - wait for all to complete
var results = await Task.WhenAll(tasks);
return results;
}
[Function(nameof(ProcessWorkItem))]
public static int ProcessWorkItem([ActivityTrigger] string item)
{
// Process item and return result
return item.Length;
}Human Interaction Pattern
[Function(nameof(ApprovalOrchestrator))]
public static async Task<string> ApprovalOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var request = context.GetInput<ApprovalRequest>();
// Send notification
await context.CallActivityAsync(nameof(SendApprovalRequest), request);
// Wait for external event with timeout
using var cts = new CancellationTokenSource();
var approvalTask = context.WaitForExternalEvent<bool>("ApprovalEvent");
var timeoutTask = context.CreateTimer(context.CurrentUtcDateTime.AddDays(7), cts.Token);
var winner = await Task.WhenAny(approvalTask, timeoutTask);
if (winner == approvalTask)
{
cts.Cancel();
return approvalTask.Result ? "Approved" : "Rejected";
}
return "Timed out";
}Best Practices
1. Use isolated worker model for new development - Full .NET ecosystem access, middleware support, and longer support lifecycle 2. Inject dependencies via constructor - Use ILogger<T> and service interfaces for testability 3. Keep orchestrator code deterministic - No I/O, random, DateTime.Now, or Guid.NewGuid() in orchestrators 4. Handle sensitive data in activities - Fetch secrets from Key Vault in activity functions, never in orchestrators 5. Use unique task hub names - Prevent accidental sharing when multiple apps use the same storage 6. Avoid large inputs/outputs - Serialize to blob storage for large payloads to prevent history bloat 7. Configure concurrency limits - Set appropriate limits in host.json for resource-intensive functions 8. Keep SDK and extensions updated - Latest versions include performance improvements and bug fixes
Anti-Patterns to Avoid
| Anti-Pattern | Why It's Bad | Better Approach |
|---|---|---|
| Mixing in-process and isolated guidance | Incompatible APIs and patterns | Choose one model consistently |
| Non-deterministic orchestrator code | Replay failures, stuck orchestrations | Use context.CurrentUtcDateTime, no I/O |
| Large orchestrator inputs/outputs | History bloat, memory issues | Store large data in blob storage |
| Shared task hub names | Message conflicts, stuck orchestrations | Use unique names per app |
| Secrets in orchestrator history | Security risk, exposed in logs | Fetch secrets in activity functions |
| Blocking calls in async functions | Thread pool exhaustion | Use await throughout |
| Missing retry policies | Transient failures cause job loss | Configure retry in bindings or code |
| Ignoring execution model migration | EOL November 2026 for in-process | Migrate to isolated worker model |
Deployment Considerations
Linux Consumption Plan Limitations
.NET 10+ apps cannot run on Linux Consumption plan.
Use Flex Consumption plan or App Service for .NET 10+.
.NET 9 is the last version supported on Linux Consumption.host.json Configuration
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "MyUniqueTaskHub",
"maxConcurrentActivityFunctions": 10,
"maxConcurrentOrchestratorFunctions": 5
}
},
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
}
}Deliver
- correct Functions project setup for the isolated worker model
- clear binding and host configuration
- middleware for cross-cutting concerns
- Durable Functions with proper orchestration patterns
- migration-safe guidance when upgrading execution models
Validate
- execution model guidance is consistent (isolated only for new work)
- orchestrator code is deterministic
- bindings and host settings match the target runtime
- large payloads are externalized to blob storage
- retry policies are configured for transient failures
- local and deployment behavior are both verified
{
"version": "1.0.0",
"category": "Cloud",
"package_prefix": "Microsoft.Azure.Functions"
}
Azure Functions Anti-Patterns
Isolated Worker Model Anti-Patterns
Using In-Process APIs in Isolated Worker
// WRONG - Using in-process types in isolated worker
public class BrokenFunction
{
[Function("BrokenHttp")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestMessage req) // Wrong type
{
// HttpRequestMessage is in-process model
return new OkResult();
}
}
// CORRECT - Using isolated worker types
public class CorrectFunction
{
[Function("CorrectHttp")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req) // Correct type
{
return new OkResult();
}
}Service Locator Instead of Constructor Injection
// WRONG - Using service locator anti-pattern
public class ServiceLocatorFunction
{
[Function("BadDI")]
public async Task Run(
[QueueTrigger("myqueue")] string message,
FunctionContext context)
{
// Anti-pattern: getting services from context
var service = context.InstanceServices.GetRequiredService<IMyService>();
await service.ProcessAsync(message);
}
}
// CORRECT - Constructor injection
public class ConstructorInjectionFunction(IMyService myService, ILogger<ConstructorInjectionFunction> logger)
{
[Function("GoodDI")]
public async Task Run([QueueTrigger("myqueue")] string message)
{
logger.LogInformation("Processing message");
await myService.ProcessAsync(message);
}
}Blocking Async Code
// WRONG - Blocking on async operations
public class BlockingFunction
{
[Function("BlockingCall")]
public string Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
// Anti-pattern: blocking on async
var result = _httpClient.GetStringAsync("https://api.example.com/data").Result;
return result;
}
}
// CORRECT - Async all the way
public class AsyncFunction
{
[Function("AsyncCall")]
public async Task<string> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
var result = await _httpClient.GetStringAsync("https://api.example.com/data");
return result;
}
}Creating HttpClient Per Request
// WRONG - Creating new HttpClient per request
public class BadHttpClientFunction
{
[Function("BadHttpClient")]
public async Task<string> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
// Anti-pattern: socket exhaustion risk
using var client = new HttpClient();
return await client.GetStringAsync("https://api.example.com/data");
}
}
// CORRECT - Using IHttpClientFactory
public class GoodHttpClientFunction(IHttpClientFactory httpClientFactory)
{
[Function("GoodHttpClient")]
public async Task<string> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
var client = httpClientFactory.CreateClient("ApiClient");
return await client.GetStringAsync("https://api.example.com/data");
}
}
// Program.cs registration
services.AddHttpClient("ApiClient", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(30);
});Ignoring Cancellation Tokens
// WRONG - Ignoring cancellation
public class NoCancellationFunction
{
[Function("NoCancellation")]
public async Task Run(
[QueueTrigger("longrunning")] string message)
{
// Anti-pattern: long operation without cancellation support
await LongRunningOperation();
}
}
// CORRECT - Respecting cancellation
public class CancellationAwareFunction
{
[Function("WithCancellation")]
public async Task Run(
[QueueTrigger("longrunning")] string message,
CancellationToken cancellationToken)
{
// Pass cancellation token to long operations
await LongRunningOperation(cancellationToken);
}
private async Task LongRunningOperation(CancellationToken cancellationToken)
{
foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
await ProcessItem(item, cancellationToken);
}
}
}Static State Across Invocations
// WRONG - Mutable static state
public class StaticStateFunction
{
private static int _requestCount = 0; // Anti-pattern: race conditions
private static List<string> _cache = new(); // Anti-pattern: memory growth
[Function("StaticState")]
public string Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
_requestCount++; // Race condition in concurrent execution
_cache.Add(req.Path); // Unbounded memory growth
return $"Count: {_requestCount}";
}
}
// CORRECT - Use proper distributed state
public class DistributedStateFunction(IDistributedCache cache)
{
[Function("DistributedState")]
public async Task<string> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
var count = await cache.GetStringAsync("request-count") ?? "0";
var newCount = int.Parse(count) + 1;
await cache.SetStringAsync("request-count", newCount.ToString());
return $"Count: {newCount}";
}
}---
Durable Functions Anti-Patterns
Non-Deterministic Orchestrator Code
// WRONG - Non-deterministic operations in orchestrator
[Function(nameof(BadOrchestrator))]
public static async Task BadOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
// Anti-pattern: DateTime.Now changes on replay
var timestamp = DateTime.Now;
// Anti-pattern: Guid.NewGuid() changes on replay
var correlationId = Guid.NewGuid();
// Anti-pattern: Random values change on replay
var random = new Random().Next();
// Anti-pattern: Reading config/environment in orchestrator
var setting = Environment.GetEnvironmentVariable("MySetting");
// Anti-pattern: I/O in orchestrator
var httpClient = new HttpClient();
var result = await httpClient.GetStringAsync("https://api.example.com");
}
// CORRECT - Deterministic orchestrator
[Function(nameof(GoodOrchestrator))]
public static async Task GoodOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
// Use context for deterministic time
var timestamp = context.CurrentUtcDateTime;
// Use NewGuid from context or pass from activity
var correlationId = context.NewGuid();
// Move I/O and non-deterministic operations to activities
var result = await context.CallActivityAsync<string>(nameof(FetchDataActivity), null);
var config = await context.CallActivityAsync<ConfigData>(nameof(GetConfigActivity), null);
}Large Orchestrator Input/Output
// WRONG - Large payloads in orchestrator history
[Function(nameof(LargePayloadOrchestrator))]
public static async Task<byte[]> LargePayloadOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
// Anti-pattern: Large input stored in history
var largeInput = context.GetInput<byte[]>(); // Could be MB of data
// Anti-pattern: Large activity result stored in history
var processedData = await context.CallActivityAsync<byte[]>(
nameof(ProcessLargeFile),
largeInput); // Each call adds to history
return processedData; // Large output in history
}
// CORRECT - Use blob storage for large payloads
[Function(nameof(SmallPayloadOrchestrator))]
public static async Task<string> SmallPayloadOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
// Pass blob reference instead of data
var blobRef = context.GetInput<BlobReference>();
// Activity processes blob and returns new reference
var resultBlobRef = await context.CallActivityAsync<BlobReference>(
nameof(ProcessLargeFileFromBlob),
blobRef);
return resultBlobRef.Url; // Small reference in history
}
public class BlobReference
{
public string ContainerName { get; set; }
public string BlobName { get; set; }
public string Url => $"https://storage.blob.core.windows.net/{ContainerName}/{BlobName}";
}Shared Task Hub Across Applications
// WRONG - Default or shared task hub name
// host.json
{
"version": "2.0",
"extensions": {
"durableTask": {
// Anti-pattern: Using default name or sharing across apps
// "hubName": "DurableFunctionsHub" (default)
}
}
}
// CORRECT - Unique task hub per application
// host.json
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "OrderProcessingApp-Prod-Hub"
}
}
}Secrets in Orchestrator History
// WRONG - Secrets passed through orchestrator
[Function(nameof(SecretsInHistoryOrchestrator))]
public static async Task SecretsInHistoryOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
// Anti-pattern: API key in orchestrator input (stored in history)
var input = context.GetInput<ProcessingInput>();
var apiKey = input.ApiKey; // Visible in Durable Functions storage
// Anti-pattern: Fetching secrets in orchestrator
var secret = await context.CallActivityAsync<string>(nameof(GetSecret), "my-secret");
// Anti-pattern: Passing secret as activity input
await context.CallActivityAsync(nameof(CallApi), new { ApiKey = secret });
}
// CORRECT - Activities fetch their own secrets
[Function(nameof(SecureOrchestrator))]
public static async Task SecureOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var input = context.GetInput<ProcessingInput>();
// Pass only non-sensitive identifiers
await context.CallActivityAsync(nameof(CallApiSecurely), new {
SecretName = "api-key-name", // Just the name, not the value
Endpoint = input.Endpoint
});
}
[Function(nameof(CallApiSecurely))]
public static async Task CallApiSecurely(
[ActivityTrigger] ApiCallInput input,
FunctionContext context)
{
// Fetch secret in activity (not stored in orchestrator history)
var secretClient = new SecretClient(new Uri("https://myvault.vault.azure.net"), new DefaultAzureCredential());
var apiKey = await secretClient.GetSecretAsync(input.SecretName);
// Use secret directly
await MakeApiCall(input.Endpoint, apiKey.Value.Value);
}Infinite Orchestration Without ContinueAsNew
// WRONG - Unbounded history growth
[Function(nameof(InfiniteLoopOrchestrator))]
public static async Task InfiniteLoopOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
while (true)
{
// Anti-pattern: History grows unbounded
await context.CallActivityAsync(nameof(DoWork), null);
await context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(5), CancellationToken.None);
// Eventually: OutOfMemory, slow replays, storage bloat
}
}
// CORRECT - Use ContinueAsNew to reset history
[Function(nameof(EternalOrchestrator))]
public static async Task EternalOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var state = context.GetInput<OrchestratorState>() ?? new OrchestratorState();
// Do bounded amount of work
for (int i = 0; i < 10; i++)
{
await context.CallActivityAsync(nameof(DoWork), state.LastProcessedId);
state.IterationCount++;
state.LastProcessedId++;
}
await context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(5), CancellationToken.None);
// Reset history and continue with current state
context.ContinueAsNew(state);
}Awaiting Non-Durable Tasks
// WRONG - Awaiting non-durable async operations
[Function(nameof(NonDurableAwaitOrchestrator))]
public static async Task NonDurableAwaitOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
// Anti-pattern: Direct HTTP call in orchestrator
var client = new HttpClient();
var result = await client.GetStringAsync("https://api.example.com"); // Not replayed correctly
// Anti-pattern: Direct database call
using var db = new MyDbContext();
var data = await db.Items.ToListAsync(); // Not replayed correctly
// Anti-pattern: Reading from file system
var content = await File.ReadAllTextAsync("data.json"); // Not replayed correctly
}
// CORRECT - Use activities for all I/O
[Function(nameof(DurableAwaitOrchestrator))]
public static async Task DurableAwaitOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
// All I/O through activities
var apiResult = await context.CallActivityAsync<string>(nameof(CallApiActivity), null);
var dbData = await context.CallActivityAsync<List<Item>>(nameof(GetDatabaseItemsActivity), null);
var fileContent = await context.CallActivityAsync<string>(nameof(ReadFileActivity), "data.json");
}Missing Retry Policies for External Calls
// WRONG - No retry policy for unreliable operations
[Function(nameof(NoRetryOrchestrator))]
public static async Task NoRetryOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
// Anti-pattern: External call without retry
var result = await context.CallActivityAsync<string>(nameof(CallExternalApi), null);
// First transient failure = orchestration failure
}
// CORRECT - Configure appropriate retry policies
[Function(nameof(RetryOrchestrator))]
public static async Task RetryOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var retryOptions = new TaskOptions
{
Retry = new RetryPolicy(
maxNumberOfAttempts: 5,
firstRetryInterval: TimeSpan.FromSeconds(1),
backoffCoefficient: 2.0,
maxRetryInterval: TimeSpan.FromMinutes(1),
retryTimeout: TimeSpan.FromMinutes(5))
};
var result = await context.CallActivityAsync<string>(
nameof(CallExternalApi),
null,
retryOptions);
}---
Configuration and Deployment Anti-Patterns
Hardcoded Connection Strings
// WRONG - Hardcoded secrets
public class HardcodedSecretsFunction
{
[Function("HardcodedSecrets")]
public async Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo timer)
{
// Anti-pattern: Secrets in code
var connectionString = "AccountName=myaccount;AccountKey=abc123...";
var apiKey = "sk-live-abcdefg";
}
}
// CORRECT - Use configuration and Key Vault
// Program.cs
var host = new HostBuilder()
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
var keyVaultUri = builtConfig["KeyVaultUri"];
config.AddAzureKeyVault(new Uri(keyVaultUri), new DefaultAzureCredential());
})
.Build();
// Function
public class SecureFunction(IConfiguration configuration)
{
[Function("SecureConfig")]
public async Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo timer)
{
var connectionString = configuration["StorageConnectionString"]; // From Key Vault
}
}Missing Logging Correlation
// WRONG - Basic logging without correlation
public class NoCorrelationFunction
{
private readonly ILogger _logger;
[Function("NoCorrelation")]
public async Task Run([QueueTrigger("myqueue")] string message)
{
// Anti-pattern: No correlation across function calls
_logger.LogInformation("Processing message");
await CallDownstreamService();
_logger.LogInformation("Done"); // Can't correlate with downstream
}
}
// CORRECT - Correlation via middleware and structured logging
// Middleware
public class CorrelationMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
var correlationId = context.TraceContext?.TraceParent
?? Guid.NewGuid().ToString();
using (_logger.BeginScope(new Dictionary<string, object>
{
["CorrelationId"] = correlationId,
["FunctionName"] = context.FunctionDefinition.Name
}))
{
await next(context);
}
}
}Over-Provisioned or Under-Provisioned Concurrency
// WRONG - Default concurrency for all workloads
{
"version": "2.0"
// No concurrency configuration - defaults may not fit workload
}
// WRONG - Aggressive concurrency for CPU-bound work
{
"version": "2.0",
"extensions": {
"queues": {
"batchSize": 32,
"newBatchThreshold": 16
}
}
// CPU-bound functions will compete for threads
}
// CORRECT - Tuned for workload type
// For I/O-bound queue processing
{
"version": "2.0",
"extensions": {
"queues": {
"batchSize": 16,
"newBatchThreshold": 8,
"maxDequeueCount": 5,
"visibilityTimeout": "00:05:00"
}
}
}
// For CPU-intensive Durable Functions
{
"version": "2.0",
"extensions": {
"durableTask": {
"maxConcurrentActivityFunctions": 4,
"maxConcurrentOrchestratorFunctions": 2
}
}
}Ignoring Cold Start Impact
// WRONG - Heavy initialization in function code
public class HeavyInitFunction
{
[Function("HeavyInit")]
public async Task<string> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
// Anti-pattern: Initialization on every cold start
var bigCache = LoadLargeDataset(); // 5 seconds
var mlModel = LoadMachineLearningModel(); // 10 seconds
return "Ready";
}
}
// CORRECT - Lazy initialization or warm-up
public class OptimizedInitFunction
{
private static readonly Lazy<BigDataset> _cache = new(() => LoadLargeDataset());
[Function("OptimizedInit")]
public async Task<string> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
// Lazy loaded once, reused across invocations
var data = _cache.Value;
return "Ready";
}
}
// Program.cs - Pre-warm in startup
services.AddSingleton<IModelService>(sp =>
{
var service = new ModelService();
// Warm up during startup, not first request
_ = service.EnsureModelLoadedAsync();
return service;
});Missing Health Checks for Dependencies
// WRONG - No health checks, silent failures
public class NoHealthCheckFunction(IMyService service)
{
[Function("NoHealthCheck")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
// Fails with cryptic errors if database is down
var result = await service.GetDataAsync();
return new OkObjectResult(result);
}
}
// CORRECT - Health endpoint for monitoring
public class HealthCheckFunction(
IMyService service,
IHealthCheck[] healthChecks)
{
[Function("HealthCheck")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "health")] HttpRequest req)
{
var results = new Dictionary<string, string>();
var isHealthy = true;
foreach (var check in healthChecks)
{
try
{
var result = await check.CheckHealthAsync(new HealthCheckContext());
results[check.GetType().Name] = result.Status.ToString();
if (result.Status != HealthStatus.Healthy)
{
isHealthy = false;
}
}
catch (Exception ex)
{
results[check.GetType().Name] = $"Failed: {ex.Message}";
isHealthy = false;
}
}
return isHealthy
? new OkObjectResult(new { status = "Healthy", checks = results })
: new ObjectResult(new { status = "Unhealthy", checks = results })
{ StatusCode = 503 };
}
}Azure Functions Patterns
Isolated Worker Model Patterns
Dependency Injection with Scoped Services
// Program.cs - Register services with appropriate lifetimes
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureServices(services =>
{
// Singleton - shared across all function invocations
services.AddSingleton<ICacheService, RedisCacheService>();
// Scoped - one instance per function invocation
services.AddScoped<IUnitOfWork, UnitOfWork>();
// Transient - new instance each time requested
services.AddTransient<IEmailBuilder, EmailBuilder>();
// Options pattern for configuration
services.Configure<StorageOptions>(
hostContext.Configuration.GetSection("Storage"));
})
.Build();Request/Response Pipeline with Middleware
// Full middleware pipeline setup
var host = new HostBuilder()
.ConfigureFunctionsWebApplication(builder =>
{
// Order matters - executed in registration order
builder.UseMiddleware<ExceptionHandlingMiddleware>();
builder.UseMiddleware<RequestLoggingMiddleware>();
builder.UseMiddleware<AuthenticationMiddleware>();
builder.UseMiddleware<CorrelationIdMiddleware>();
})
.Build();
// Exception handling middleware
public class ExceptionHandlingMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(ILogger<ExceptionHandlingMiddleware> logger)
{
_logger = logger;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (ValidationException ex)
{
_logger.LogWarning(ex, "Validation error in {Function}", context.FunctionDefinition.Name);
await WriteErrorResponse(context, StatusCodes.Status400BadRequest, ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception in {Function}", context.FunctionDefinition.Name);
await WriteErrorResponse(context, StatusCodes.Status500InternalServerError, "Internal server error");
}
}
private static async Task WriteErrorResponse(FunctionContext context, int statusCode, string message)
{
var httpReqData = await context.GetHttpRequestDataAsync();
if (httpReqData != null)
{
var response = httpReqData.CreateResponse((HttpStatusCode)statusCode);
await response.WriteAsJsonAsync(new { error = message });
context.GetInvocationResult().Value = response;
}
}
}Output Bindings with Multiple Outputs
public class MultiOutputFunctions
{
[Function("ProcessOrder")]
public async Task<MultiOutput> ProcessOrder(
[QueueTrigger("orders", Connection = "StorageConnection")] Order order,
FunctionContext context)
{
var logger = context.GetLogger<MultiOutputFunctions>();
logger.LogInformation("Processing order {OrderId}", order.Id);
var confirmation = new OrderConfirmation
{
OrderId = order.Id,
ProcessedAt = DateTime.UtcNow
};
return new MultiOutput
{
Confirmation = confirmation,
NotificationMessage = $"Order {order.Id} processed"
};
}
}
public class MultiOutput
{
[QueueOutput("confirmations", Connection = "StorageConnection")]
public OrderConfirmation Confirmation { get; set; }
[QueueOutput("notifications", Connection = "StorageConnection")]
public string NotificationMessage { get; set; }
}Typed Configuration with Validation
// Program.cs
services.AddOptions<ServiceBusOptions>()
.Bind(configuration.GetSection("ServiceBus"))
.ValidateDataAnnotations()
.ValidateOnStart();
// Options class with validation
public class ServiceBusOptions
{
[Required]
public string ConnectionString { get; set; } = string.Empty;
[Required]
public string QueueName { get; set; } = string.Empty;
[Range(1, 100)]
public int MaxConcurrentCalls { get; set; } = 10;
}
// Function using validated options
public class ServiceBusFunctions(IOptions<ServiceBusOptions> options, ILogger<ServiceBusFunctions> logger)
{
[Function("ProcessServiceBusMessage")]
public async Task ProcessMessage(
[ServiceBusTrigger("%ServiceBus:QueueName%", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message)
{
logger.LogInformation("Processing message with max concurrency: {Max}",
options.Value.MaxConcurrentCalls);
}
}---
Durable Functions Patterns
Sub-Orchestrations for Modularity
[Function(nameof(MainOrchestrator))]
public static async Task<OrderResult> MainOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();
// Validate order in sub-orchestration
var isValid = await context.CallSubOrchestratorAsync<bool>(
nameof(ValidationOrchestrator),
order);
if (!isValid)
{
return new OrderResult { Status = "ValidationFailed" };
}
// Process payment in sub-orchestration with retry
var paymentResult = await context.CallSubOrchestratorAsync<PaymentResult>(
nameof(PaymentOrchestrator),
order.Payment,
new TaskOptions { Retry = new RetryPolicy(3, TimeSpan.FromSeconds(5)) });
// Ship order in sub-orchestration
var shipmentResult = await context.CallSubOrchestratorAsync<ShipmentResult>(
nameof(ShippingOrchestrator),
new ShippingRequest { OrderId = order.Id, Address = order.ShippingAddress });
return new OrderResult
{
Status = "Completed",
PaymentId = paymentResult.TransactionId,
TrackingNumber = shipmentResult.TrackingNumber
};
}
[Function(nameof(ValidationOrchestrator))]
public static async Task<bool> ValidationOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();
// Parallel validation checks
var inventoryTask = context.CallActivityAsync<bool>(nameof(CheckInventory), order.Items);
var fraudTask = context.CallActivityAsync<bool>(nameof(CheckFraud), order.Customer);
var creditTask = context.CallActivityAsync<bool>(nameof(CheckCredit), order.Payment);
var results = await Task.WhenAll(inventoryTask, fraudTask, creditTask);
return results.All(r => r);
}Eternal Orchestrations for Long-Running Processes
[Function(nameof(MonitorOrchestrator))]
public static async Task MonitorOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var config = context.GetInput<MonitorConfig>();
// Check status
var status = await context.CallActivityAsync<ServiceStatus>(
nameof(CheckServiceStatus),
config.ServiceEndpoint);
if (status.IsHealthy)
{
// Wait before next check
await context.CreateTimer(
context.CurrentUtcDateTime.AddMinutes(config.CheckIntervalMinutes),
CancellationToken.None);
}
else
{
// Alert and wait shorter interval
await context.CallActivityAsync(nameof(SendAlert), new Alert
{
Service = config.ServiceName,
Status = status,
Timestamp = context.CurrentUtcDateTime
});
await context.CreateTimer(
context.CurrentUtcDateTime.AddMinutes(1),
CancellationToken.None);
}
// Continue as new to prevent history growth
context.ContinueAsNew(config);
}Aggregator Pattern with Entity Functions
// Entity definition
[Function(nameof(CounterEntity))]
public static Task CounterEntity(
[EntityTrigger] TaskEntityDispatcher dispatcher)
{
return dispatcher.DispatchAsync<Counter>();
}
public class Counter
{
public int Value { get; set; }
public void Add(int amount) => Value += amount;
public void Reset() => Value = 0;
public int Get() => Value;
}
// Using entity from orchestrator
[Function(nameof(AggregatorOrchestrator))]
public static async Task<int> AggregatorOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var entityId = new EntityInstanceId(nameof(CounterEntity), "global-counter");
// Signal entity (fire and forget)
await context.Entities.SignalEntityAsync(entityId, "Add", 5);
// Call entity and get result
var currentValue = await context.Entities.CallEntityAsync<int>(entityId, "Get");
return currentValue;
}Saga Pattern with Compensation
[Function(nameof(SagaOrchestrator))]
public static async Task<SagaResult> SagaOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var request = context.GetInput<BookingRequest>();
var compensations = new Stack<Func<Task>>();
try
{
// Step 1: Reserve flight
var flightReservation = await context.CallActivityAsync<FlightReservation>(
nameof(ReserveFlight), request.Flight);
compensations.Push(() => context.CallActivityAsync(nameof(CancelFlight), flightReservation.Id));
// Step 2: Reserve hotel
var hotelReservation = await context.CallActivityAsync<HotelReservation>(
nameof(ReserveHotel), request.Hotel);
compensations.Push(() => context.CallActivityAsync(nameof(CancelHotel), hotelReservation.Id));
// Step 3: Reserve car
var carReservation = await context.CallActivityAsync<CarReservation>(
nameof(ReserveCar), request.Car);
compensations.Push(() => context.CallActivityAsync(nameof(CancelCar), carReservation.Id));
// Step 4: Charge payment
var payment = await context.CallActivityAsync<PaymentConfirmation>(
nameof(ProcessPayment), new PaymentRequest
{
Amount = flightReservation.Price + hotelReservation.Price + carReservation.Price,
CustomerId = request.CustomerId
});
return new SagaResult
{
Success = true,
FlightConfirmation = flightReservation.ConfirmationNumber,
HotelConfirmation = hotelReservation.ConfirmationNumber,
CarConfirmation = carReservation.ConfirmationNumber
};
}
catch (Exception ex)
{
// Compensate in reverse order
while (compensations.Count > 0)
{
var compensation = compensations.Pop();
try
{
await compensation();
}
catch (Exception compEx)
{
// Log compensation failure but continue
context.SetCustomStatus($"Compensation failed: {compEx.Message}");
}
}
return new SagaResult
{
Success = false,
FailureReason = ex.Message
};
}
}Retry Policies and Error Handling
[Function(nameof(ResilientOrchestrator))]
public static async Task<ProcessingResult> ResilientOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var input = context.GetInput<ProcessingInput>();
// Retry policy for transient failures
var retryPolicy = new RetryPolicy(
maxNumberOfAttempts: 5,
firstRetryInterval: TimeSpan.FromSeconds(1),
backoffCoefficient: 2.0,
maxRetryInterval: TimeSpan.FromMinutes(1));
try
{
// Call with retry
var result = await context.CallActivityAsync<string>(
nameof(UnreliableActivity),
input.Data,
new TaskOptions { Retry = retryPolicy });
return new ProcessingResult { Success = true, Data = result };
}
catch (TaskFailedException ex) when (ex.FailureDetails?.ErrorType == "TransientException")
{
// Handle specific transient failure after retries exhausted
await context.CallActivityAsync(nameof(NotifyOperations), new OperationsAlert
{
Message = "Activity failed after retries",
Exception = ex.Message
});
return new ProcessingResult { Success = false, Error = "Service temporarily unavailable" };
}
catch (TaskFailedException ex)
{
// Handle permanent failure
return new ProcessingResult { Success = false, Error = ex.Message };
}
}
[Function(nameof(UnreliableActivity))]
public static async Task<string> UnreliableActivity(
[ActivityTrigger] string input,
FunctionContext context)
{
var logger = context.GetLogger(nameof(UnreliableActivity));
// Simulate transient failure
if (Random.Shared.NextDouble() < 0.3)
{
throw new TransientException("Temporary service unavailable");
}
return await ProcessData(input);
}Timer-Based Scheduling in Orchestrations
[Function(nameof(ScheduledOrchestrator))]
public static async Task ScheduledOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var schedule = context.GetInput<ProcessingSchedule>();
foreach (var scheduledTime in schedule.Times)
{
// Wait until scheduled time
var fireAt = scheduledTime;
if (fireAt > context.CurrentUtcDateTime)
{
await context.CreateTimer(fireAt, CancellationToken.None);
}
// Execute scheduled work
await context.CallActivityAsync(nameof(ScheduledWork), new WorkItem
{
ScheduledFor = scheduledTime,
ActualStart = context.CurrentUtcDateTime
});
}
}
[Function(nameof(BatchProcessingOrchestrator))]
public static async Task BatchProcessingOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var config = context.GetInput<BatchConfig>();
var processedCount = 0;
while (processedCount < config.TotalItems)
{
// Process batch
var batchSize = Math.Min(config.BatchSize, config.TotalItems - processedCount);
await context.CallActivityAsync(nameof(ProcessBatch), new BatchRequest
{
StartIndex = processedCount,
Count = batchSize
});
processedCount += batchSize;
// Delay between batches to avoid throttling
if (processedCount < config.TotalItems)
{
await context.CreateTimer(
context.CurrentUtcDateTime.AddSeconds(config.DelayBetweenBatchesSeconds),
CancellationToken.None);
}
}
}---
Advanced Binding Patterns
Blob Input/Output with Metadata
public class BlobFunctions(ILogger<BlobFunctions> logger)
{
[Function("ProcessBlob")]
[BlobOutput("processed/{name}", Connection = "StorageConnection")]
public async Task<byte[]> ProcessBlob(
[BlobTrigger("uploads/{name}", Connection = "StorageConnection")]
BlobClient blobClient,
string name,
FunctionContext context)
{
logger.LogInformation("Processing blob: {Name}", name);
// Read blob content
var downloadResult = await blobClient.DownloadContentAsync();
var content = downloadResult.Value.Content.ToArray();
// Get blob properties
var properties = await blobClient.GetPropertiesAsync();
logger.LogInformation("Blob size: {Size}, Content-Type: {ContentType}",
properties.Value.ContentLength,
properties.Value.ContentType);
// Process and return for output binding
return TransformContent(content);
}
}Cosmos DB with Partition Key Routing
public class CosmosDbFunctions(ILogger<CosmosDbFunctions> logger)
{
[Function("ProcessCosmosDocument")]
[CosmosDBOutput(
databaseName: "MyDatabase",
containerName: "ProcessedItems",
Connection = "CosmosDBConnection",
PartitionKey = "/category")]
public ProcessedItem ProcessDocument(
[CosmosDBTrigger(
databaseName: "MyDatabase",
containerName: "Items",
Connection = "CosmosDBConnection",
LeaseContainerName = "leases",
CreateLeaseContainerIfNotExists = true)]
IReadOnlyList<MyDocument> documents)
{
logger.LogInformation("Processing {Count} documents", documents.Count);
var processed = documents.Select(doc => new ProcessedItem
{
Id = Guid.NewGuid().ToString(),
OriginalId = doc.Id,
Category = doc.Category, // Partition key
ProcessedAt = DateTime.UtcNow,
Data = TransformData(doc)
});
// Return for output binding
return processed.First();
}
}Event Grid with CloudEvents
public class EventGridFunctions(ILogger<EventGridFunctions> logger)
{
[Function("HandleCloudEvent")]
public async Task HandleCloudEvent(
[EventGridTrigger] CloudEvent cloudEvent)
{
logger.LogInformation("Received event: Type={Type}, Source={Source}",
cloudEvent.Type,
cloudEvent.Source);
// Deserialize event data
var data = cloudEvent.Data?.ToObjectFromJson<MyEventData>();
if (data != null)
{
await ProcessEventData(data);
}
}
[Function("PublishEvent")]
[EventGridOutput(TopicEndpointUri = "EventGridTopicUri", TopicKeySetting = "EventGridTopicKey")]
public EventGridEvent PublishEvent(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
return new EventGridEvent(
subject: "myapp/items/created",
eventType: "ItemCreated",
dataVersion: "1.0",
data: new { ItemId = Guid.NewGuid(), CreatedAt = DateTime.UtcNow });
}
}