Skip to main content

Azure OpenAI C# Tutorial: Integrate GPT-4o in .NET (2026)

Learn Azure OpenAI with C# step by step: set up GPT-4o in .NET, stream responses, use function calling and follow best practices. Start building today.

If you have been searching for a practical Azure OpenAI C# tutorial, this guide walks you through integrating GPT-4o into a .NET application from scratch. You will learn how to authenticate against your Azure OpenAI resource, send chat completions, stream responses token by token, use function calling, and apply the best practices that separate a demo from a production-grade integration. Every example uses the official Azure.AI.OpenAI SDK and targets .NET 8 or later, so the code runs as-is.

Why Use Azure OpenAI with C# Instead of the Public OpenAI API?

Both services expose the same GPT-4o model, so why pick Azure? For most enterprise .NET teams, the answer comes down to three things:

  • Data residency and compliance. Your prompts and completions stay inside your Azure subscription and region. Microsoft does not use your data to train models, and the service inherits your existing Azure compliance boundaries (SOC 2, HIPAA, ISO 27001, UK G-Cloud, and so on).
  • Enterprise identity. You can authenticate with Microsoft Entra ID (formerly Azure AD) using managed identities, which means no API keys sitting in configuration files.
  • Predictable networking and quotas. Private endpoints, VNet integration, and per-deployment token-per-minute quotas make capacity planning far simpler than a shared public endpoint.

The trade-off is a slightly different model: on Azure you talk to a deployment (a named instance of a model you create in the portal) rather than a model name. Once you understand that, the SDK feels almost identical to the public OpenAI client because it is built on the same OpenAI .NET library.

Prerequisites

  • An Azure subscription with access to Azure OpenAI Service.
  • An Azure OpenAI resource with a GPT-4o deployment (for example, named gpt-4o). Create it in Azure AI Foundry under Deployments.
  • .NET 8 SDK or later and Visual Studio 2022 / VS Code / Rider.
  • Your resource endpoint (e.g. https://my-resource.openai.azure.com/) and either an API key or Entra ID access.

Step 1: Install the Azure OpenAI SDK for .NET

Create a console project and add the official packages. Azure.Identity is optional but strongly recommended so you can use keyless authentication later.

dotnet new console -n AzureOpenAiDemo
cd AzureOpenAiDemo
dotnet add package Azure.AI.OpenAI
dotnet add package Azure.Identity
dotnet add package Microsoft.Extensions.Configuration.UserSecrets

Never hard-code keys. Store them with .NET user secrets during development:

dotnet user-secrets init
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://my-resource.openai.azure.com/"
dotnet user-secrets set "AzureOpenAI:ApiKey" "your-key-here"
dotnet user-secrets set "AzureOpenAI:Deployment" "gpt-4o"

Step 2: Your First GPT-4o Chat Completion in C#

The core type is AzureOpenAIClient. From it you get a ChatClient bound to a specific deployment. Here is a complete, runnable Program.cs:

using Azure;
using Azure.AI.OpenAI;
using Microsoft.Extensions.Configuration;
using OpenAI.Chat;

var config = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .Build();

var endpoint = new Uri(config["AzureOpenAI:Endpoint"]!);
var apiKey = config["AzureOpenAI:ApiKey"]!;
var deployment = config["AzureOpenAI:Deployment"]!;

AzureOpenAIClient azureClient = new(endpoint, new AzureKeyCredential(apiKey));
ChatClient chatClient = azureClient.GetChatClient(deployment);

var messages = new List<ChatMessage>
{
    new SystemChatMessage("You are a concise assistant for .NET developers."),
    new UserChatMessage("Explain the difference between IEnumerable and IQueryable in two sentences.")
};

ChatCompletion completion = await chatClient.CompleteChatAsync(messages);

Console.WriteLine(completion.Content[0].Text);
Console.WriteLine($"\nTokens used: {completion.Usage.TotalTokenCount}");

Notice three things. First, the SystemChatMessage sets behaviour for the whole conversation and is the cheapest way to control tone and format. Second, Content is a collection because GPT-4o is multimodal and can return multiple content parts. Third, always log Usage: token counts drive your bill and your quota, and you want that visibility from day one.

Step 3: Tune the Request with ChatCompletionOptions

Defaults are fine for experiments, but production code should be explicit about temperature, output length, and format.

var options = new ChatCompletionOptions
{
    Temperature = 0.2f,          // lower = more deterministic, good for code and extraction
    MaxOutputTokenCount = 500,   // hard cap on cost and latency
    TopP = 0.95f,
    ResponseFormat = ChatResponseFormat.CreateJsonObjectFormat()
};

messages.Add(new UserChatMessage(
    "Return a JSON object with keys 'language' and 'yearReleased' for C#."));

ChatCompletion jsonCompletion = await chatClient.CompleteChatAsync(messages, options);
Console.WriteLine(jsonCompletion.Content[0].Text);

Why this matters: when you request JSON mode you must also mention the word "JSON" in a prompt, or the API rejects the call. And capping MaxOutputTokenCount is the single most effective guard against runaway costs when a prompt accidentally invites a long essay.

Step 4: Streaming GPT-4o Responses in .NET

Waiting several seconds for a full answer feels broken to users. Streaming sends tokens as they are generated, which is what makes chat UIs feel instant. The SDK exposes this as an IAsyncEnumerable:

await foreach (StreamingChatCompletionUpdate update in
    chatClient.CompleteChatStreamingAsync(messages, options))
{
    foreach (ChatMessageContentPart part in update.ContentUpdate)
    {
        Console.Write(part.Text);
    }
}
Console.WriteLine();

In ASP.NET Core, you can forward these updates over Server-Sent Events or SignalR. Just remember to pass the request's CancellationToken through so a user closing the browser tab stops billing you for tokens nobody will read:

app.MapGet("/chat", async (string q, ChatClient chat, HttpContext ctx) =>
{
    ctx.Response.ContentType = "text/event-stream";
    var msgs = new ChatMessage[] { new UserChatMessage(q) };

    await foreach (var update in chat.CompleteChatStreamingAsync(msgs, cancellationToken: ctx.RequestAborted))
    {
        foreach (var part in update.ContentUpdate)
        {
            await ctx.Response.WriteAsync($"data: {part.Text}\n\n", ctx.RequestAborted);
            await ctx.Response.Body.FlushAsync(ctx.RequestAborted);
        }
    }
});

Step 5: Function Calling (Tools) with GPT-4o in C#

Function calling lets the model ask your code to run a method, such as looking up an order or checking the weather, and then incorporate the result. This is the foundation of agents and RAG-style applications. The model never executes anything; it returns structured arguments and you decide what to do.

using System.Text.Json;

ChatTool getOrderStatusTool = ChatTool.CreateFunctionTool(
    functionName: "get_order_status",
    functionDescription: "Get the shipping status of a customer order by its ID.",
    functionParameters: BinaryData.FromString("""
    {
        "type": "object",
        "properties": {
            "orderId": { "type": "string", "description": "The order ID, e.g. ORD-1001" }
        },
        "required": ["orderId"]
    }
    """));

var toolOptions = new ChatCompletionOptions { Tools = { getOrderStatusTool } };

var conversation = new List<ChatMessage>
{
    new SystemChatMessage("You are a support assistant."),
    new UserChatMessage("Where is my order ORD-1001?")
};

ChatCompletion result = await chatClient.CompleteChatAsync(conversation, toolOptions);

while (result.FinishReason == ChatFinishReason.ToolCalls)
{
    conversation.Add(new AssistantChatMessage(result));

    foreach (ChatToolCall call in result.ToolCalls)
    {
        if (call.FunctionName == "get_order_status")
        {
            using var args = JsonDocument.Parse(call.FunctionArguments);
            string orderId = args.RootElement.GetProperty("orderId").GetString()!;

            string status = GetOrderStatus(orderId); // your real business logic
            conversation.Add(new ToolChatMessage(call.Id, status));
        }
    }

    result = await chatClient.CompleteChatAsync(conversation, toolOptions);
}

Console.WriteLine(result.Content[0].Text);

static string GetOrderStatus(string orderId) =>
    orderId == "ORD-1001" ? "Shipped on 24 Aug 2026, arriving 28 Aug." : "Order not found.";

The while loop is essential. GPT-4o may request several tools in one turn or chain calls across turns, so treat tool handling as a loop that ends only when FinishReason is Stop. Always validate the arguments the model sends; they are model output, not trusted input.

Step 6: Keyless Authentication with Managed Identity

API keys leak. In Azure-hosted apps (App Service, Container Apps, Functions, AKS) you should use a managed identity and the Cognitive Services OpenAI User role. The only code change is the credential:

using Azure.Identity;

AzureOpenAIClient client = new(endpoint, new DefaultAzureCredential());

DefaultAzureCredential uses your Visual Studio or Azure CLI login locally and the managed identity in the cloud, so the same code runs everywhere with zero secrets. Assign the role at the resource level with az role assignment create --role "Cognitive Services OpenAI User" --assignee <principal-id> --scope <resource-id>.

Azure OpenAI C# Best Practices for Production

Register the client as a singleton

AzureOpenAIClient is thread-safe and manages its own HttpClient pipeline. Creating it per request wastes sockets. In ASP.NET Core:

builder.Services.AddSingleton(sp =>
{
    var cfg = sp.GetRequiredService<IConfiguration>();
    return new AzureOpenAIClient(new Uri(cfg["AzureOpenAI:Endpoint"]!), new DefaultAzureCredential());
});

builder.Services.AddSingleton(sp =>
    sp.GetRequiredService<AzureOpenAIClient>()
      .GetChatClient(builder.Configuration["AzureOpenAI:Deployment"]!));

Handle rate limits (HTTP 429) deliberately

The SDK retries transient failures automatically with exponential backoff, but a sustained 429 means your deployment's tokens-per-minute quota is too low. Catch ClientResultException, inspect Status, and surface a friendly message rather than a stack trace. For bursty workloads, consider a Provisioned Throughput deployment or multiple deployments behind Azure API Management.

try
{
    var response = await chatClient.CompleteChatAsync(messages, options);
}
catch (ClientResultException ex) when (ex.Status == 429)
{
    logger.LogWarning("Azure OpenAI throttled: {Message}", ex.Message);
    // return a degraded response or queue the work
}

Manage conversation history and token budget

GPT-4o has a 128k-token context window, but sending the entire chat history every turn is slow and expensive. Keep the system prompt, summarise old turns, and trim to a budget. Even a naive "keep the last N messages" strategy dramatically cuts costs for chat apps.

Pin an API version and monitor deprecations

Azure OpenAI versions its REST API. The SDK defaults to a tested version, but you can override it via AzureOpenAIClientOptions if you need a newer feature. Watch the retirement dates for both the API version and the model version of your deployment; GPT-4o snapshots do retire.

Add content-safety and prompt-injection defences

Azure applies content filters by default, and blocked requests throw with a 400 status describing the filter category. Handle that gracefully. Never let user input reach the system prompt unescaped, and treat tool-call arguments and retrieved documents as untrusted, especially in RAG scenarios.

Common Pitfalls When Integrating GPT-4o with .NET

  • Using the model name instead of the deployment name. GetChatClient("gpt-4o") works only if your deployment is literally named gpt-4o. A 404 "DeploymentNotFound" error almost always means this.
  • Mixing up the two OpenAI packages. Azure.AI.OpenAI (Azure) depends on OpenAI (the base library). Message and option types live in the OpenAI.Chat namespace; the Azure package only supplies the client and authentication.
  • Blocking on async calls. Calling .Result on CompleteChatAsync deadlocks in UI and legacy ASP.NET contexts. Use await end to end.
  • Forgetting to add the assistant's tool-call message back to history. If you send a ToolChatMessage without the preceding AssistantChatMessage the API returns a 400 error.
  • Ignoring cancellation. Long streams without a CancellationToken keep consuming quota after the client disconnects.
  • Committing API keys. Use user secrets locally, Key Vault or managed identity in production.

Conclusion: Key Takeaways for Azure OpenAI in C#

Integrating Azure OpenAI with C# is straightforward once you understand the deployment model and the Azure.AI.OpenAI SDK. To recap:

  • Install Azure.AI.OpenAI and Azure.Identity; create one AzureOpenAIClient and reuse it as a singleton.
  • Call GetChatClient(deploymentName) and use CompleteChatAsync for simple requests, CompleteChatStreamingAsync for responsive UIs.
  • Be explicit with ChatCompletionOptions: temperature, output token cap, and JSON mode when you need structured output.
  • Use function calling in a loop, validate every argument, and keep your business logic in your own code.
  • Prefer DefaultAzureCredential and managed identity over API keys, handle 429s and content-filter errors, and always pass cancellation tokens.

With these patterns in place you can move from a proof of concept to a production .NET application that uses GPT-4o safely and cost-effectively. From here, explore embeddings for semantic search, Azure AI Search for retrieval-augmented generation, or Semantic Kernel if you want a higher-level orchestration framework on top of the same SDK.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

.NET MAUI Tutorial 2026: Build Cross-Platform Apps in C#

Learn .NET MAUI in 2026 to build iOS, Android, Windows & Mac apps from one C# codebase. Start this cross-platform tutorial with code examples today. .NET MAUI (Multi-platform App UI) is Microsoft's framework for building native iOS, Android, Windows, and macOS apps from a single C# codebase . If you've ever wanted to ship a mobile app without learning Swift, Kotlin, and Win32 separately, this .NET MAUI tutorial for 2026 is your starting point. In this guide you'll learn what .NET MAUI is, why it matters for cross-platform app development in C#, and how to build your first working app — with runnable code examples and the best practices senior engineers actually use in production. What Is .NET MAUI and Why Use It in 2026? .NET MAUI is the evolution of Xamarin.Forms, fully integrated into the modern .NET runtime. With one project and one language — C# — you target four platforms. The framework compiles to native UI controls on each device, so a button on iOS...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...