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.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-azure-functions-bfa4ebd.zip · 13 KB
Part of postpartum-genushyacinthus29/dotnet-skills — 80 skills

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

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 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

// 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
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.

No comments yet.

Reviews (0)

No reviews yet.

Related