# Azure Functions Standard

> **Scope:** blazor-azure
> **Layer:** 2 (on keyword)
> **Keywords:** azure functions, serverless, trigger, function app
> **Load When:** azure functions or serverless keywords detected

Serverless compute patterns with Azure Functions.

---

## Overview

Azure Functions provides:
- **Event-driven**: Triggered by HTTP, timers, queues, blobs, Service Bus, etc.
- **Serverless**: No infrastructure management
- **Auto-scaling**: Scales based on demand
- **Pay-per-execution**: Only pay for execution time
- **Multiple languages**: C#, JavaScript, Python, Java, PowerShell

**Stack:** .NET 10 Isolated Worker Model

---

## Core Principles

1. **Isolated Worker Model**: Use isolated process model for .NET 10
2. **Dependency Injection**: Configure DI in `Program.cs`
3. **Stateless Functions**: Functions should be stateless and idempotent
4. **Durable Functions**: Use for stateful workflows
5. **Local Development**: Test locally with Azurite before deploying

---

## Project Setup

### Create Function App

```bash
# Install Azure Functions Core Tools
npm install -g azure-functions-core-tools@4

# Create new project
func init MyFunctionApp --worker-runtime dotnet-isolated --target-framework net10.0
cd MyFunctionApp

# Add HTTP trigger function
func new --template "HTTP trigger" --name ProcessOrder
```

### Program.cs Configuration

```csharp
// Program.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();

        // Register custom services
        services.AddSingleton<IOrderService, OrderService>();
        services.AddDbContextFactory<AppDbContext>(options =>
            options.UseSqlServer(Environment.GetEnvironmentVariable("SqlConnectionString")));
    })
    .Build();

await host.RunAsync();
```

---

## Trigger Types

### HTTP Trigger

```csharp
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;

public class HttpTriggerFunctions
{
    private readonly ILogger<HttpTriggerFunctions> _logger;
    private readonly IOrderService _orderService;

    public HttpTriggerFunctions(
        ILogger<HttpTriggerFunctions> logger,
        IOrderService orderService)
    {
        _logger = logger;
        _orderService = orderService;
    }

    [Function("ProcessOrder")]
    public async Task<HttpResponseData> ProcessOrder(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequestData req)
    {
        _logger.LogInformation("Processing order request");

        var order = await req.ReadFromJsonAsync<OrderRequest>();
        if (order == null)
        {
            var badResponse = req.CreateResponse(HttpStatusCode.BadRequest);
            await badResponse.WriteStringAsync("Invalid order data");
            return badResponse;
        }

        var result = await _orderService.ProcessOrderAsync(order);

        var response = req.CreateResponse(HttpStatusCode.OK);
        await response.WriteAsJsonAsync(result);
        return response;
    }
}
```

### Timer Trigger

```csharp
public class TimerTriggerFunctions
{
    private readonly ILogger<TimerTriggerFunctions> _logger;

    public TimerTriggerFunctions(ILogger<TimerTriggerFunctions> logger)
    {
        _logger = logger;
    }

    [Function("DailyCleanup")]
    public async Task DailyCleanup(
        [TimerTrigger("0 0 2 * * *")] TimerInfo timerInfo) // Every day at 2 AM UTC
    {
        _logger.LogInformation("Running daily cleanup at {Time}", DateTime.UtcNow);

        if (timerInfo.IsPastDue)
        {
            _logger.LogWarning("Timer is running late");
        }

        await CleanupOldDataAsync();

        _logger.LogInformation("Daily cleanup completed");
    }

    private Task CleanupOldDataAsync()
    {
        // Cleanup logic...
        return Task.CompletedTask;
    }
}
```

### Queue Trigger

```csharp
using Azure.Storage.Queues.Models;

public class QueueTriggerFunctions
{
    private readonly ILogger<QueueTriggerFunctions> _logger;
    private readonly IEmailService _emailService;

    public QueueTriggerFunctions(
        ILogger<QueueTriggerFunctions> logger,
        IEmailService emailService)
    {
        _logger = logger;
        _emailService = emailService;
    }

    [Function("SendEmailFromQueue")]
    public async Task SendEmailFromQueue(
        [QueueTrigger("email-queue", Connection = "AzureWebJobsStorage")] QueueMessage message)
    {
        _logger.LogInformation("Processing email from queue: {MessageId}", message.MessageId);

        var emailRequest = JsonSerializer.Deserialize<EmailRequest>(message.MessageText);

        await _emailService.SendAsync(emailRequest!);

        _logger.LogInformation("Email sent successfully");
    }
}
```

### Service Bus Trigger

```csharp
using Azure.Messaging.ServiceBus;

public class ServiceBusTriggerFunctions
{
    private readonly ILogger<ServiceBusTriggerFunctions> _logger;

    [Function("ProcessOrderFromServiceBus")]
    public async Task ProcessOrderFromServiceBus(
        [ServiceBusTrigger("orders", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message,
        ServiceBusMessageActions messageActions)
    {
        _logger.LogInformation("Processing Service Bus message: {MessageId}", message.MessageId);

        try
        {
            var orderData = message.Body.ToString();
            await ProcessOrderAsync(orderData);

            // Complete the message
            await messageActions.CompleteMessageAsync(message);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process message {MessageId}", message.MessageId);

            // Dead-letter the message
            await messageActions.DeadLetterMessageAsync(message, "ProcessingFailed", ex.Message);
        }
    }

    private Task ProcessOrderAsync(string orderData)
    {
        // Processing logic...
        return Task.CompletedTask;
    }
}
```

### Blob Trigger

```csharp
using Azure.Storage.Blobs;

public class BlobTriggerFunctions
{
    private readonly ILogger<BlobTriggerFunctions> _logger;

    [Function("ProcessUploadedFile")]
    public async Task ProcessUploadedFile(
        [BlobTrigger("uploads/{name}", Connection = "AzureWebJobsStorage")] Stream blobStream,
        string name)
    {
        _logger.LogInformation("Processing blob: {BlobName}, Size: {Size} bytes", name, blobStream.Length);

        // Process the blob (e.g., resize image, extract text, etc.)
        await ProcessBlobAsync(blobStream, name);

        _logger.LogInformation("Blob processing completed for {BlobName}", name);
    }

    private Task ProcessBlobAsync(Stream stream, string fileName)
    {
        // Processing logic...
        return Task.CompletedTask;
    }
}
```

---

## Configuration

### local.settings.json (Local Development)

```json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "SqlConnectionString": "Server=localhost;Database=MyApp;Trusted_Connection=True;",
    "ServiceBusConnection": "Endpoint=sb://..."
  }
}
```

### Application Settings (Production)

Configure in Azure Portal or via Bicep:

```bicep
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp,linux'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'dotnet-isolated'
        }
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};...'
        }
        {
          name: 'SqlConnectionString'
          value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}/secrets/SqlConnectionString/)'
        }
      ]
      linuxFxVersion: 'DOTNET-ISOLATED|10.0'
    }
  }
}
```

---

## Durable Functions (Stateful Workflows)

### Install Package

```bash
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.DurableTask
```

### Orchestrator

```csharp
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;

public class OrderWorkflow
{
    [Function("OrderOrchestrator")]
    public async Task<string> RunOrchestrator(
        [OrchestrationTrigger] TaskOrchestrationContext context)
    {
        var orderId = context.GetInput<string>()!;

        // Step 1: Validate order
        var isValid = await context.CallActivityAsync<bool>("ValidateOrder", orderId);
        if (!isValid)
        {
            return "Order validation failed";
        }

        // Step 2: Process payment
        await context.CallActivityAsync("ProcessPayment", orderId);

        // Step 3: Send confirmation email
        await context.CallActivityAsync("SendConfirmationEmail", orderId);

        return "Order processed successfully";
    }

    [Function("ValidateOrder")]
    public async Task<bool> ValidateOrder([ActivityTrigger] string orderId)
    {
        // Validation logic...
        await Task.Delay(100);
        return true;
    }

    [Function("ProcessPayment")]
    public async Task ProcessPayment([ActivityTrigger] string orderId)
    {
        // Payment processing...
        await Task.Delay(100);
    }

    [Function("SendConfirmationEmail")]
    public async Task SendConfirmationEmail([ActivityTrigger] string orderId)
    {
        // Email sending...
        await Task.Delay(100);
    }

    [Function("StartOrderWorkflow")]
    public async Task<HttpResponseData> StartWorkflow(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
        [DurableClient] DurableTaskClient client)
    {
        var orderId = await req.ReadAsStringAsync();

        var instanceId = await client.ScheduleNewOrchestrationInstanceAsync("OrderOrchestrator", orderId);

        return await client.CreateCheckStatusResponseAsync(req, instanceId);
    }
}
```

---

## Best Practices

### Use IDbContextFactory

```csharp
public class DataService
{
    private readonly IDbContextFactory<AppDbContext> _dbFactory;

    public DataService(IDbContextFactory<AppDbContext> dbFactory)
    {
        _dbFactory = dbFactory;
    }

    [Function("GetData")]
    public async Task<HttpResponseData> GetData(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req)
    {
        await using var db = await _dbFactory.CreateDbContextAsync();

        var data = await db.Orders.ToListAsync();

        var response = req.CreateResponse(HttpStatusCode.OK);
        await response.WriteAsJsonAsync(data);
        return response;
    }
}
```

### Logging

```csharp
_logger.LogInformation("Order {OrderId} processed at {Timestamp}", orderId, DateTime.UtcNow);
_logger.LogWarning("Retry attempt {Attempt} for order {OrderId}", attempt, orderId);
_logger.LogError(ex, "Failed to process order {OrderId}", orderId);
```

### Authentication

```csharp
[Function("SecureEndpoint")]
public async Task<HttpResponseData> SecureEndpoint(
    [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req)
{
    // Function Key authentication (Authorization: Function <key>)
    // Or use AuthorizationLevel.Anonymous with custom auth logic

    var authHeader = req.Headers.GetValues("Authorization").FirstOrDefault();
    if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
    {
        return req.CreateResponse(HttpStatusCode.Unauthorized);
    }

    // Validate JWT token...

    var response = req.CreateResponse(HttpStatusCode.OK);
    await response.WriteStringAsync("Authenticated!");
    return response;
}
```

---

## Local Development

```bash
# Start Azurite (local storage emulator)
azurite --silent

# Run functions locally
func start

# Test HTTP function
curl -X POST http://localhost:7071/api/orders \
  -H "Content-Type: application/json" \
  -d '{"orderId": "12345"}'
```

---

## References

- [Azure Functions Documentation](https://learn.microsoft.com/azure/azure-functions/)
- [Durable Functions](https://learn.microsoft.com/azure/azure-functions/durable/)
- [Isolated Worker Model](https://learn.microsoft.com/azure/azure-functions/dotnet-isolated-process-guide)

---

*MORPH-SPEC by Polymorphism Tech*
