Claude
Skill
dotnet-azure-functions
Build, review, or migrate Azure Functions in .NET with correct execution model, isolated worker setup, bindings, DI, and Durable Functions patterns.
Virus-scanned
Reviewed automatically before listing.
Download
postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-azure-functions-bfa4ebd.zip · 13 KB
Install
skills CLI
npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-azure-functions
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git
git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
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
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
Detect current project shape:
- Target framework and runtime version
- Worker model (isolated vs in-process)
- Binding packages and host configuration
Use standard .NET patterns in isolated model:
- Normal dependency injection
- Middleware pipeline
IOptions<T>for configurationILogger<T>for logging
For Durable Functions:
- Validate orchestration determinism constraints
- Handle replay behavior correctly
- Use typed activity patterns
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
- Use isolated worker model for new development - Full .NET ecosystem access, middleware support, and longer support lifecycle
- Inject dependencies via constructor - Use
ILogger<T>and service interfaces for testability - Keep orchestrator code deterministic - No I/O, random, DateTime.Now, or Guid.NewGuid() in orchestrators
- Handle sensitive data in activities - Fetch secrets from Key Vault in activity functions, never in orchestrators
- Use unique task hub names - Prevent accidental sharing when multiple apps use the same storage
- Avoid large inputs/outputs - Serialize to blob storage for large payloads to prevent history bloat
- Configure concurrency limits - Set appropriate limits in host.json for resource-intensive functions
- 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
Files (dotnet-skills)
-
references
-
anti-patterns.md 19.7 KB
# Azure Functions Anti-Patterns ## Isolated Worker Model Anti-Patterns ### Using In-Process APIs in Isolated Worker ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```csharp // 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 ```json // 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 ```csharp // 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 ```csharp // 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 }; } } ``` -
patterns.md 17.6 KB
# Azure Functions Patterns ## Isolated Worker Model Patterns ### Dependency Injection with Scoped Services ```csharp // 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 ```csharp // 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 ```csharp 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 ```csharp // 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 ```csharp [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 ```csharp [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 ```csharp // 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 ```csharp [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 ```csharp [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 ```csharp [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 ```csharp 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 ```csharp 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 ```csharp 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 }); } } ```
-
-
SKILL.md 9.6 KB
--- name: dotnet-azure-functions version: "1.0.0" category: "Cloud" description: "Build, review, or migrate Azure Functions in .NET with correct execution model, isolated worker setup, bindings, DI, and Durable Functions patterns." compatibility: "Requires an Azure Functions project or a migration plan for one." --- # 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](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide) - [Differences between in-process and isolated worker process](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-in-process-differences) - [Migrate C# app from in-process to isolated worker model](https://learn.microsoft.com/en-us/azure/azure-functions/migrate-dotnet-to-isolated-model) - [Durable Functions overview](https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview) - [Durable Functions best practices and diagnostic tools](https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-best-practice-reference) ### References - [Patterns](references/patterns.md) - Isolated worker patterns, Durable Functions patterns, advanced binding patterns - [Anti-Patterns](references/anti-patterns.md) - 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 configuration - `ILogger<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 ```csharp // Program.cs var host = new HostBuilder() .ConfigureFunctionsWebApplication() .ConfigureServices(services => { services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); services.AddSingleton<IMyService, MyService>(); }) .Build(); host.Run(); ``` ### HTTP Trigger Function ```csharp 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 ```csharp 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 ```csharp // 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 ```csharp [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 ```csharp [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 ```csharp [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 ```json { "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
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.