Azure  

.NET Isolated Azure Functions: Dependency Injection & Secure Configuration for Critical Infrastructure

Table of Contents

  • Introduction

  • A Real-Time Scenario: Smart Grid Load Balancing in Urban Power Networks

  • Dependency Injection in .NET Isolated Azure Functions

  • Configuring Environment Variables for Local Development

  • End-to-End Example: Secure, Testable, and Maintainable Code

  • Best Practices for Enterprise Deployment

  • Conclusion

Introduction

In the world of cloud-native serverless applications, writing functions that are testable, secure, and maintainable isn’t optional—it’s foundational. Nowhere is this more critical than in infrastructure systems where failure cascades can trigger city-wide outages.

Let’s explore dependency injection and environment configuration in .NET Isolated Azure Functions through a live scenario: real-time smart grid load balancing for a metropolitan power utility.

A Real-Time Scenario: Smart Grid Load Balancing in Urban Power Networks

The city’s power grid ingests telemetry from 50,000+ smart meters every 5 seconds. An Azure Function processes this stream to detect overload risks and triggers automatic load-shedding protocols.

Requirements:

  • Inject telemetry clients, circuit breaker logic, and audit loggers without tight coupling

  • Securely manage secrets (e.g., IoT Hub connection strings) in both cloud and local dev

  • Run identical logic on a developer’s laptop and in Azure—no “works on my machine” excuses

This is where .NET Isolated Process DI and proper environment configuration become non-negotiable.

PlantUML Diagram

Dependency Injection in .NET Isolated Azure Functions

Unlike in-process functions, .NET Isolated runs in its own process and uses a startup-based DI model similar to ASP.NET Core.

You define services in a Program.cs file:

// Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SmartGrid.Services;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        // Register telemetry service
        services.AddSingleton<ITelemetryClient, GridTelemetryClient>();
        
        // Register audit logger with scoped lifetime
        services.AddScoped<IAuditLogger, AzureMonitorAuditLogger>();
        
        // Register configuration-bound options
        services.Configure<GridConfig>(Environment.GetEnvironmentVariables()
            .ToDictionary(k => k.Key, v => v.Value));
    })
    .Build();

host.Run();

Then inject dependencies directly into your function class:

// LoadBalancerFunction.cs
public class LoadBalancerFunction
{
    private readonly ITelemetryClient _telemetry;
    private readonly IAuditLogger _logger;
    private readonly GridConfig _config;

    public LoadBalancerFunction(
        ITelemetryClient telemetry,
        IAuditLogger logger,
        IOptions<GridConfig> config)
    {
        _telemetry = telemetry;
        _logger = logger;
        _config = config.Value;
    }

    [Function("ProcessMeterData")]
    public async Task Run([EventHubTrigger("meter-readings", Connection = "EventHubConnection")] string[] events)
    {
        foreach (var evt in events)
        {
            var risk = await _telemetry.AnalyzeOverloadRiskAsync(evt);
            if (risk > _config.Threshold)
            {
                await _logger.LogAsync($"Overload detected: {risk}");
                // Trigger load-shedding
            }
        }
    }
}

This design enables unit testing, mocking, and runtime flexibility—all while keeping the function stateless and scalable.

Configuring Environment Variables for Local Development

During local development, you never hardcode secrets. Instead, use local.settings.json:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "EventHubConnection": "Endpoint=sb://dev-grid.servicebus.windows.net/;SharedAccessKey=...",
    "GRID_THRESHOLD": "0.85",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
  }
}

Critical: This file is never committed to source control. Add it to .gitignore.

At runtime, Azure Functions automatically loads these values into the environment. In code, you access them via Environment.GetEnvironmentVariable("GRID_THRESHOLD") or through strongly-typed IOptions<T> as shown above.

For production, you override these values in the Azure portal or via Azure Key Vault references:

EventHubConnection = @Microsoft.KeyVault(SecretUri=https://grid-kv.vault.azure.net/secrets/EventHubConnStr)

This ensures zero secret leakage and consistent configuration across environments.

End-to-End Example: Secure, Testable, and Maintainable Code

With DI and environment config in place, your function becomes:

  • Testable: Mock ITelemetryClient in unit tests

  • Secure: Secrets never in code

  • Portable: Same binary runs locally and in Azure

  • Observable: Audit logs and metrics wired via injected services

A developer can simulate a grid overload on their laptop using local Event Hub emulator and local.settings.json—then deploy the exact same artifact to production with confidence.

Best Practices for Enterprise Deployment

  1. Always use IOptions<T> for configuration—never read env vars inline.

  2. Register services with appropriate lifetimes: Singleton for stateless clients, Scoped for per-invocation logic.

  3. Use Azure Key Vault + Managed Identity in production—never raw secrets.

  4. Validate config on startup to fail fast.

  5. Keep local.settings.json out of Git—use a local.settings.sample.json template instead.

Conclusion

In enterprise-grade serverless systems, how you manage dependencies and configuration determines your resilience. The .NET Isolated model gives you full control—use it wisely. By embracing dependency injection and secure environment management, you transform Azure Functions from simple scripts into robust, auditable, and production-ready components of critical infrastructure—whether you’re balancing city power grids, monitoring ICU vitals, or processing financial transactions. Remember: Great architecture isn’t just about scale—it’s about safety, testability, and trust.