A chatbot is the “hello world” of AI apps — and Blazor is a natural fit because you can build the whole thing, UI and model calls, in C#. This guide builds a working AI chat component in Blazor: a message list, an input box, streamed responses that appear token-by-token, and conversation state held across turns. No JavaScript required.
It assumes you can already make a model call — if not, start with how to call the OpenAI API from C#.
The shape of the app
A chat UI needs three things: a list of messages to render, a way to send a new message, and a way to stream the reply in as it generates. We’ll use Microsoft.Extensions.AI’s IChatClient so the same component works with OpenAI, Azure OpenAI, or a local model via Ollama.
Register the chat client in Program.cs:
builder.Services.AddSingleton<IChatClient>(sp =>
new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))
.GetChatClient("gpt-4o-mini")
.AsIChatClient());
The chat component
Here’s the core of a Chat.razor component — a message list, an input, and a send handler that streams the response into the last message as tokens arrive:
@page "/chat"
@inject IChatClient ChatClient
@using Microsoft.Extensions.AI
<div class="messages">
@foreach (var msg in _messages)
{
<div class="msg @msg.Role.ToString().ToLower()">@msg.Text</div>
}
</div>
<input @bind="_input" @bind:event="oninput" @onkeyup="OnKey" placeholder="Ask something..." />
<button @onclick="Send" disabled="@_busy">Send</button>
@code {
private readonly List<ChatMessage> _messages = new();
private string _input = "";
private bool _busy;
private async Task Send()
{
if (string.IsNullOrWhiteSpace(_input) || _busy) return;
_busy = true;
_messages.Add(new(ChatRole.User, _input));
_input = "";
var assistant = new ChatMessage(ChatRole.Assistant, "");
_messages.Add(assistant);
// Stream the reply straight into the assistant message
await foreach (var update in ChatClient.GetStreamingResponseAsync(_messages))
{
assistant.Text += update.Text;
StateHasChanged(); // re-render as each chunk arrives
}
_busy = false;
}
private async Task OnKey(KeyboardEventArgs e)
{
if (e.Key == "Enter") await Send();
}
}
The key move is StateHasChanged() inside the streaming loop — that re-renders the component on each chunk, so users watch the answer type itself out. Passing the whole _messages list each turn is what gives the bot memory of the conversation.
Why this is clean in Blazor
Notice what’s not here: no fetch, no JSON wrangling, no separate API endpoint, no client-side JavaScript. The component calls the model directly in C#, streams into a field, and Blazor’s rendering handles the UI. For Blazor Server, the streaming updates flow to the browser over the existing SignalR circuit automatically. (For a browser-to-server streaming pattern in other setups, see streaming responses to a web UI.)
Making it production-ready
A demo chatbot becomes a real feature when you add:
- A system prompt — seed
_messageswith aChatRole.Systemmessage to set the bot’s behaviour and scope. - Tools — let the bot do things, not just talk, by turning it into a tool-using agent.
- History limits — cap or summarize old turns so the prompt doesn’t grow unbounded (see agent memory and cost control).
- Auth and guardrails — a chatbot that calls tools is an attack surface; see securing AI agents.
Note:
Microsoft.Extensions.AImethod names (GetStreamingResponseAsync,AsIChatClient) are still settling; verify against the current .NET AI docs for your package version. The pattern — injectIChatClient, stream into a message,StateHasChangedper chunk — is the durable part.
Takeaway
Blazor makes an AI chatbot refreshingly simple: inject an IChatClient, render a message list, and stream the reply into the last message with a StateHasChanged() on each chunk — all in C#, no JavaScript. Build it on IChatClient and you can point it at OpenAI, Azure, or a local model unchanged. Add a system prompt, tools, and history limits, and your “hello world” chatbot becomes a real product feature.
Have a correction or a topic you want covered? Email mani.bc72@gmail.com.