Azure Functions C# Tutorial

In this tutorial, I will guide you through mastering Azure Functions with C# using Visual Studio 2022. We will unpack the underlying architecture, explore core operational components, examine hosting mechanics, and review production deployment practices.

Azure Functions C# Tutorial

Deconstructing the Architecture: In-Process vs. Isolated Worker Model

Before writing a single line of C# or provisioning resources in Azure, we must address the architectural shift within the .NET Azure Functions ecosystem: the distinction between the In-Process model and the Isolated Worker model.

The In-Process Model

Historically, .NET Azure Functions ran inside the same runtime process as the underlying Azure Functions host runtime. While this provided minor performance advantages in serialization speed, it tied your application’s execution pipeline directly to the host’s runtime dependencies. If the Azure Functions host ran on .NET Core 3.1 or .NET 6, your application code was locked into those same framework versions and package dependencies, creating assembly conflict headaches.

The Isolated Worker Model

The modern standard for building Azure Functions with C# is the Isolated Worker model.

Under this paradigm, your C# function application runs in a completely separate, dedicated worker process (dotnet.exe) independent of the Azure Functions host runtime. Communication between the host orchestrator and your application code occurs via optimized inter-process communication (gRPC channels).

Choosing the Right Azure Functions Hosting Plan

Selecting an improper compute tier during infrastructure planning can quickly lead to budget overruns or sluggish operational latency. Azure offers three primary hosting models for running serverless C# workloads.

1. Consumption Plan (Serverless Scale)

  • Cost Mechanics: You pay exclusively for compute resources consumed while your function executes, calculated via execution count and gigabyte-seconds (GB-s) of memory usage. When idle, cost drops to zero.
  • Scale Behavior: Azure’s scale controller spins up parallel instances to absorb spikes and de-provisions infrastructure when the queue empties.
  • Key Limitation: Subject to cold starts—the initialization latency incurred when spinning up a new container instance from idle.

2. Premium / Flex Consumption Plan

  • Always Ready Instances: Keeps pre-warmed, idle worker nodes active to eliminate cold-start penalties completely.
  • Virtual Network (VNet) Connectivity: Delivers native VNet injection, enabling your C# code to communicate privately with Azure SQL Managed Instances, Azure Key Vaults, and internal subnets.
  • Extended Run Durations: Bypasses default execution timeouts, allowing extended compute operations.

3. Dedicated (App Service) Plan

Deploys your Function App inside a standard, predictable Azure App Service tier (such as Basic, Standard, or Premium VM sizes).

  • Predictable Budgeting: Billed at fixed hourly rates for the underlying virtual machine compute pool, regardless of whether your code executes once or a million times.
  • Best Used For: Environments with predictable, non-bursty traffic, or setups where unused compute capacity on existing App Service plans can host auxiliary function workloads.

Core Building Blocks: Triggers, Input Bindings, and Output Bindings

The defining strength of Azure Functions is its declarative integration engine. Instead of manually writing boilerplate client initialization routines, connection retries, and network listeners, you wire components together using attributes.

The Three Binding Tiers Defined

  1. Triggers (Exactly One per Function): The event source that causes a function to execute. A trigger evaluates the inbound payload, extracts metadata, and initiates process dispatch. Common triggers include HTTP endpoints, Timers (cron schedules), Service Bus topics, and Blob Storage additions.
  2. Input Bindings (Zero or More): Declarative connections that retrieve data from an external resource and supply it directly into your function method signature as a strongly typed C# parameter before your logic runs.
  3. Output Bindings (Zero or More): Declarative definitions that streamline pushing results to downstream infrastructure. Returning a typed model from your C# function automatically routes, serializes, and commits that payload to a downstream target (such as an Azure Queue, Cosmos DB container, or Event Grid topic).

Development Blueprint: Structuring an Isolated C# Function

When authoring Azure Functions in C# using Visual Studio 2022, the isolated application adheres to modern, idiomatic .NET patterns. The solution structure centers around three primary files:

Program.cs: Configuring the Host and Middleware

In the Isolated Worker model, Program.cs manages the bootstrap lifecycle, matching the design of an ASP.NET Core web application:

C#

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication() // Registers ASP.NET Core integration pipeline
    .ConfigureServices(services =>
    {
        // Register Application Insights telemetry
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();

        // Register custom application dependencies (e.g., Business Services)
        // services.AddSingleton<IPaymentGateway, PaymentGateway>();
    })
    .Build();

host.Run();

host.json: Centralized Runtime Directives

The host.json metadata file controls runtime configurations across all functions within the deployed container:

JSON

{
    "version": "2.0",
    "logging": {
        "applicationInsights": {
            "samplingSettings": {
                "isEnabled": true,
                "maxTelemetryItemsPerSecond": 20
            }
        },
        "logLevel": {
            "default": "Information",
            "Function": "Information",
            "Microsoft": "Warning"
        }
    }
}

local.settings.json: Local Environment Variables

Used strictly for local development runtime settings and excluded from Git repositories via .gitignore:

JSON

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
        "ServiceBusConnectionString": "Endpoint=sb://mynamednamespace.servicebus.windows.net/..."
    }
}

Function Triggers: Architectural Comparison Matrix

Different business requirements demand different trigger mechanisms. Selecting the correct trigger requires balancing latency, throughput, and state:

Trigger TypeIngestion ModelExecution StrategyPrimary Enterprise Use Case
HTTP TriggerDirect synchronous pushRequest/Response patternPublic APIs, Webhook receivers, Gateway routes
Timer TriggerInternal CRON schedulerScheduled executionBatch rollups, data pruning, report generation
Service Bus TriggerGuaranteed enterprise queueFIFO or topic subscriptionsOrder processing, payment dispatch, messaging
Cosmos DB TriggerChange feed pull modelIncremental document logReactive sync, materialized view generation
Event Grid TriggerPush-based reactive streamMass fan-out deliveryCloud telemetry events, multi-subscriber pipes

Authorization Levels and Security Controls

When authoring HTTP-triggered Azure Functions, developers must explicitly define how incoming requests are authorized using the AuthorizationLevel attribute property:

Authorization Level Breakdown

  • AuthorizationLevel.Anonymous: Disables built-in API key validation entirely. This is mandatory when delegating authentication to an external reverse proxy (like Azure API Management) or when securing the endpoint using Microsoft Entra ID (Azure AD) via App Service Easy Auth.
  • AuthorizationLevel.Function: Requires the caller to supply a valid API key via a URL query parameter (?code=...) or an HTTP request header (x-functions-key: ...). Each individual function can maintain distinct operational keys.
  • AuthorizationLevel.Admin: Restricts access to callers presenting the master host key. This tier should be reserved strictly for internal operational scripts, health-probe overrides, or administrative workflows.

Production Engineering Best Practices

Transitioning an Azure Function application from local debugging in Visual Studio to enterprise production requires careful configuration across multiple layers.

1. Optimize HTTP Client and Resource Lifecycles

Never instantiate disposable infrastructure clients (such as HttpClient, CosmosClient, or ServiceBusClient) inside the body of a function execution.

Doing so exhausts ephemeral outbound TCP sockets under high concurrency, causing socket exhaustion errors (System.Net.Sockets.SocketException).

  • The Rule: Register external clients as Singletons in your dependency injection container inside Program.cs, or use the IHttpClientFactory abstraction. This preserves connection pools across multiple concurrent function invocations.

2. Implement Resiliency and Idempotency

Because cloud functions operate within an asynchronous, event-driven ecosystem, network drops and timeout retries will happen.

  • Idempotency is Mandatory: A message from an Azure Service Bus queue or Event Grid subscription may be delivered more than once under edge failover scenarios. Design processing routines so executing duplicate events produces the identical business state without double-charging or corrupting records.

3. Centralize Secrets via Azure Key Vault

Never commit access strings or dat