
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 namedgpt-4o. A 404 "DeploymentNotFound" error almost always means this. - Mixing up the two OpenAI packages.
Azure.AI.OpenAI(Azure) depends onOpenAI(the base library). Message and option types live in theOpenAI.Chatnamespace; the Azure package only supplies the client and authentication. - Blocking on async calls. Calling
.ResultonCompleteChatAsyncdeadlocks in UI and legacy ASP.NET contexts. Useawaitend to end. - Forgetting to add the assistant's tool-call message back to history. If you send a
ToolChatMessagewithout the precedingAssistantChatMessagethe API returns a 400 error. - Ignoring cancellation. Long streams without a
CancellationTokenkeep 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.OpenAIandAzure.Identity; create oneAzureOpenAIClientand reuse it as a singleton. - Call
GetChatClient(deploymentName)and useCompleteChatAsyncfor simple requests,CompleteChatStreamingAsyncfor 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
DefaultAzureCredentialand 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.
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment