用 Blazor AI 组件构建 Agentic UI:不止于聊天框
I Love .NET Blazor. Now It Can Build Agentic UI, Not Just Chat Boxes
微软新推出的实验性 Blazor AI 组件可构建 Agentic UI,让 AI 以工具卡片、前端操作、人工审批和共享状态等形式参与应用,而非只返回文本。
Introduction
I love Blazor so much. I have used it to build more than 10 projects this year.
One thing I enjoy is that I can build interactive web applications with C#. I can reuse familiar types, validation rules, and components instead of switching languages for every part of the application.
So I am happy to see Blazor keep improving, especially for the AI era.
Recently, I read Daniel Roth's Microsoft article, Build Agentic UI with the new Blazor AI components.
My first thought was:
"Nice. But can we build something more useful than another chat box?"
A chat box is useful. However, many applications need more than text replies.
We want the AI to show a tool result as a card. We want it to understand the document we are editing. We want it to suggest changes without quietly replacing our work. And before it performs an important action, we want a real approval button.
That is the idea behind Agentic UI.
In this article, I will explain the new Blazor AI building blocks through a single Blazor demo application. We will look at streaming chat, typed tool cards, frontend actions, human approval, shared state, document review, and a live checklist.
The important part is not that the AI can do everything.
The AI can help with the work. The application defines the rules. The user stays in control.
What Is Agentic UI?
A normal chatbot often follows this flow:
User asks a question
-> model returns text
-> application displays the text
An agentic application can go further:
User asks for help
-> agent reads the current workspace
-> agent requests an allowed tool
-> application validates the request
-> user approves when required
-> tool returns a result or a state update
-> Blazor renders the result
For example, "make this recipe vegan" does not have to produce a separate recipe in a chat message. It can update a recipe editor that the user and agent share.
Similarly, "improve this document" does not have to overwrite the document immediately. It can produce a proposed revision with Accept and Reject buttons.
This is still a normal application. We still write the components, define the tools, validate the data, and decide which actions are allowed.
The model does not get permission to run arbitrary C# or generate executable UI code.
What We Are Building
My demo uses one Interactive Server Blazor application.
Browser
-> Interactive Server Blazor UI
-> UIAgent and server-side AG-UI client
-> /agents/{scenario} in the same application
-> Microsoft Agent Framework
-> Ollama at localhost:11434
-> selected cloud model
There is no separate agent server, Aspire host, database, or real calendar integration in this demo.
The eight pages are:
- Streaming chat
- Backend weather tool rendering
- Frontend accent-color tool
- Meeting approval
- Shared recipe state
- Predictive document review
- Live plan and activities
- Reasoning capability information
The weather and meeting tools are simulated. The plan is a demo checklist, not proof that real-world tasks have been completed.
A Preview Warning Before We Start
The Microsoft article introduces experimental Blazor AI components. This demo targets net11.0 and uses the .NET 11 RC1 SDK.
These APIs can change. This is a learning project, not a production-ready application or a stable API promise.
The versions below are the versions used by this demo. If you read this article later, check package availability and the current Microsoft guidance before starting a new project.
Step 1: Install .NET 11
Install the .NET 11 RC1 SDK, not only the runtime. The SDK is required to restore and build the application.
Use the official .NET download page and select the installer that matches your operating system and architecture. For this Windows workspace, I used Visual Studio 2026 with the ASP.NET and web development workload and preview SDK support enabled.
After installing, open a new terminal:
dotnet --list-sdks
dotnet --version
The demo pins this SDK in the solution root's global.json:
{
"sdk": {
"version": "11.0.100-rc.1.26425.128",
"rollForward": "disable",
"allowPrerelease": true
}
}
This pin makes everyone use the same SDK. Run dotnet --version from the repository folder to check it. Because rollForward is disabled, another .NET 11 preview is not enough: install the exact version shown above to reproduce this demo.
The application project targets:
<TargetFramework>net11.0</TargetFramework>
If Visual Studio does not recognize the SDK, enable preview SDK support in its settings and restart the IDE.
Step 2: Add the AI Building Blocks
The demo needs packages for three jobs: showing the UI, running tools, and communicating with the model. These references are already in BlazorApp1_Demo.csproj:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.AI" Version="0.1.0-preview.1.26466.103" />
<PackageReference Include="Microsoft.Extensions.AI" Version="10.10.0" />
<PackageReference Include="OllamaSharp" Version="5.5.0" />
<PackageReference Include="Microsoft.Agents.AI.Hosting.AGUI.AspNetCore" Version="1.23.0-preview.260928.1" />
<PackageReference Include="AGUI.Client" Version="1.0.0" />
<PackageReference Include="Markdig" Version="0.44.0" />
</ItemGroup>
You do not need to learn every package at once:
- UI: Blazor AI components provide the conversation and tool cards. Markdig helps the demo process Markdown for safe display.
-
Agent and tools:
Microsoft.Extensions.AIprovidesIChatClient, the interface for sending messages to a model. Microsoft Agent Framework coordinates the agent and its tools. - Communication: OllamaSharp connects to Ollama. AG-UI carries messages, tool results, and state updates between the agent and the UI.
The Microsoft article's quick-start command uses --prerelease. This project pins exact versions so the example is easier to reproduce.
The AI package also supplies a stylesheet. In Components/App.razor, the demo includes:
<link rel="stylesheet" href="@Assets["_content/Microsoft.AspNetCore.Components.AI/ai-chat.css"]" />
Without the package styles, the components may not look the way you expect.
Step 3: Configure Ollama
This demo uses gemma4:31b-cloud through Ollama. The Ollama service runs locally, but this model runs in the cloud. You need an account and an internet connection; prompts and workspace content are sent to that service, and usage may have quotas or costs.
Install and start Ollama, then sign in and prepare the model:
ollama signin
ollama pull gemma4:31b-cloud
ollama show gemma4:31b-cloud
This demo uses exactly gemma4:31b-cloud. It does not silently switch to another model.
The Ollama section in appsettings.json is:
"Ollama": {
"Endpoint": "http://localhost:11434",
"Model": "gemma4:31b-cloud",
"TimeoutSeconds": 120,
"MaxToolIterations": 8,
"AgentBaseAddress": "http://localhost:5052"
}
This is a section inside the full JSON configuration file, not a standalone JSON document.
Do not mix up the two addresses:
-
Endpointpoints to Ollama, on port11434. -
AgentBaseAddresspoints to this Blazor app, on port5052, where its agent endpoints are hosted.
Do not put private documents into this demo. Keep authentication out of source control and out of the browser.
Step 4: Start With ChatPage and UIAgent
Start with two building blocks: ChatPage displays the conversation, and UIAgent connects it to a chat client.
For a standalone chat, the markup is small:
<ChatPage Agent="_agent" Placeholder="Ask the agent…" />
ChatPage provides the input and streamed messages, so we do not have to build them from scratch. Our demo uses a typed agent to share workspace data as well:
private UIAgent<WorkspaceState>? _agent;
WorkspaceState holds the recipe, document, checklist, and their revision numbers.
Here is a shortened excerpt of the setup:
_agent = new UIAgent<WorkspaceState>(_client, options =>
{
options.AddGeneratedToolBlocks();
}, _session.Snapshot());
AddGeneratedToolBlocks enables the tool cards we will use next, and _session.Snapshot() supplies the initial workspace state. The full setup is in Components/Pages/Demo.razor.
Sharing the Conversation With a Toolbar
Our demo also has Try example, Cancel, and Reset controls outside the message list. To make those controls work with the displayed conversation, we use the lower-level components instead of ChatPage:
<AgentBoundary Agent="_agent" @key="_agent">
<ConversationScope ContextChanged="ObserveContext">
@* The toolbar and scenario editors are also inside this scope. *@
<section class="demo-chat sc-ai-root">
@* The full page also has a conversation heading and role key. *@
<MessageList>
@* Register the typed BlockRenderer components here. *@
</MessageList>
<div class="demo-chat-composer">
<MessageInput Placeholder="Write a message to the agent…" />
</div>
</section>
</ConversationScope>
</AgentBoundary>
Three details matter here:
-
AgentBoundaryprovides oneAgentContext: the shared conversation state for messages, status, and cancellation.ChatPagenormally creates this boundary for you. -
ConversationScopeis a small demo helper, not a package component. It gives the toolbar access to that same context. Do not create a separate context for the toolbar, or its requests will belong to a different conversation.@keygives Reset a fresh boundary when the agent changes. -
sc-ai-rootapplies the package's styling variables when usingMessageListandMessageInputdirectly.demo.cssthen customizes the bubbles and layout.
The result is one conversation with clearly labeled user messages, agent replies, and tool cards.
How a Message Reaches Ollama
In Program.cs, two demo extension methods register the provider and agent services:
builder.Services.AddDemoOllama(builder.Configuration);
builder.Services.AddDemoAgents();
AddDemoOllama configures the Ollama chat client. AddDemoAgents registers the agents and tools; app.MapDemoAgents() exposes their /agents/{scenario} endpoints. Registration alone does not send a prompt or test connectivity.
When you send a message or click Try example:
-
UIAgentsends the conversation request to the app's agent endpoint through AG-UI. - Microsoft Agent Framework calls Ollama, which contacts the selected cloud model.
- If the model requests a tool, the application validates it and asks for approval when required.
- Text, tool results, and state updates stream back to the Blazor UI.
Because this is Interactive Server Blazor, the AG-UI client and provider calls run on the server. The browser does not connect directly to Ollama. The following steps show how the UI handles each kind of result.
Step 5: Turn a Tool Result Into a Typed Card
Instead of asking the model to format a weather report, we can display a C# tool result in a card.
Try this on /demo/weather:
"Compare the demo weather in Seattle and Paris."
The readings are simulated, not live forecasts. Here is how the result reaches the card.
Register the Tool
First, expose the backend function as a tool the model can request:
AIFunctionFactory.Create(
GetWeather,
name: "get_weather",
description: "Get deterministic SIMULATED demo weather for a city. Not a real forecast.")
The application validates the arguments, runs the function, and returns a typed result. The model requests the work; application code performs it.
Map the Result to a Block
In Components/ToolBlocks.cs, a tool block connects the request and result to properties the UI can render:
using BlazorApp1_Demo.AI;
using Microsoft.AspNetCore.Components.AI;
namespace BlazorApp1_Demo.Components;
[ToolBlock("get_weather")]
public partial class WeatherBlock : FunctionInvocationContentBlock
{
[ToolParameter(Name = "location")]
public string? Location { get; set; }
[ToolResult]
public WeatherResult? Weather { get; set; }
}
Here is what these attributes mean:
-
ToolBlockconnects the block to the tool name. -
ToolParametermaps the function argument to a C# property. -
ToolResultmaps the returned data to a typed result property. - The partial class works with the package's generated mapping support.
The result travels through AG-UI as JSON. Explicit JSON property names keep the backend result and the generated block mapper in agreement:
public sealed record WeatherResult(
[property: System.Text.Json.Serialization.JsonPropertyName("location")] string Location,
[property: System.Text.Json.Serialization.JsonPropertyName("temperatureC")] int TemperatureC,
[property: System.Text.Json.Serialization.JsonPropertyName("conditions")] string Conditions,
[property: System.Text.Json.Serialization.JsonPropertyName("source")] string Source = "Simulated demo data; not a live forecast");
These names matter because the preview mapper does not automatically use the backend's JSON settings. The card reads the tool result, not the assistant's description of it.
Render the Card
Place a renderer inside MessageList. This shortened example handles loading, success, and failure:
<BlockRenderer TBlock="WeatherBlock" Context="weather">
<section class="tool-card">
<h3>Demo weather · @weather.Location</h3>
@if (!weather.HasResult)
{
<p>Fetching simulated weather…</p>
}
else if (weather.Weather is { } result)
{
<p>@result.TemperatureC °C · @result.Conditions</p>
}
else
{
<p>The demo weather tool failed.</p>
}
</section>
</BlockRenderer>
This is normal Razor markup: we control the layout and messages. The AI chooses an allowed tool; it does not generate executable UI code.
Step 6: Let a Tool Change the Blazor UI
Tools can change the UI too. On /demo/accent, try:
"Change the accent color to teal."
Here, a frontend tool means a function owned by the UI component. In Interactive Server Blazor it still runs on the server, through the user's Blazor circuit-not as model-generated JavaScript.
The accent scenario registers SetAccent as set_accent_color. Its implementation is:
private async Task<string> SetAccent(string color, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var selected = AccentPalette.Validate(color);
_session?.RecordTool("set_accent_color", cancellationToken);
await InvokeAsync(() => { _accent = selected; StateHasChanged(); });
return $"Accent set to {color} on the Blazor UI thread.";
}
The key points are simple:
- Validate the requested color before changing anything.
- Accept only named colors from
AccentPalette, such asteal,blue, orpurple. Raw CSS and hex values are not allowed. - Use
InvokeAsyncto update the component through Blazor's dispatcher. - Return a result after the change happens.
The demo's FunctionInvokingChatClient executes the registered function and limits tool iterations. Registering the function makes it available; this client handles its invocation.
No arbitrary CSS. No model-generated script. Just a small function with clear rules.
Step 7: Ask the Human Before an Important Action
Some actions should not happen just because a model requested them.
The meeting tool is wrapped with ApprovalRequiredAIFunction:
new ApprovalRequiredAIFunction(
AIFunctionFactory.Create(
BookMeeting,
name: "book_meeting",
description: "Book a SIMULATED demo meeting, only after user approval. Provide title and ISO 8601 time with Z or timezone offset. No real calendar is accessed."))
The UI renders a FunctionApprovalBlock with the meeting title, time, and two choices:
- Approve simulated booking allows the matching request to proceed.
- Reject declines the request without booking it.
Typing "ok" in chat does not replace approval. The server checks consent for the exact request, rejects expired or canceled approvals, and prevents duplicate execution. Approvals expire after two minutes. These checks live in AI/MeetingApprovals.cs, not just in the buttons.
This is a simulated booking. No real calendar is accessed.
Try this on /demo/approval:
"Book a demo meeting called Review at 2026-12-01T14:00:00Z."
Use a future ISO 8601 time when trying this later.
Step 8: Share State With the Agent
A conversation is more useful when the agent can work with the recipe in your editor, rather than return an unrelated recipe in chat.
On /demo/recipe, edit the title or ingredients, then ask:
"Make the current recipe vegan, preserving my edits."
The tool returns a complete WorkspaceState. The endpoint sends it to the UI as a snapshot: the state at that point in time.
endpoint.WithMetadata(
new AGUIStreamOptions()
.MapResultAsStateSnapshot("generate_recipe"));
The UI uses that snapshot to update its typed state. To protect your edits, the recipe also has a revision number, which increases when it changes. An agent update must match the current revision:
Agent reads recipe revision 3
-> user edits the recipe to revision 4
-> agent returns a change based on revision 3
-> application rejects the stale update
Without that check, a slow AI response could overwrite a newer user edit.
Recipe text is supplied as data, not instructions. Text inside an ingredient or title cannot grant the agent permission to change the application's rules.
Step 9: Preview a Document Change Before Saving It
Unlike the recipe editor, the document scenario lets you review a proposed change before applying it.
On /demo/document, try:
"Make this document clearer and more concise."
The review flow is:
Current document
-> agent proposes a revision
-> user compares the original, proposal, and highlighted differences
-> user accepts or rejects
-> application commits only an accepted, current proposal
The document scenario registers a UI action:
options.RegisterUIAction(
AIFunctionFactory.Create(
ApplyDocumentDecision,
name: "propose_document",
description: "Propose a complete document for explicit user review. Supply expectedRevision from current documentRevision; do not set accepted yourself."));
A UI action produces a UIActionBlock, where the user can accept or reject the proposal. Predictive state means showing the suggested content without replacing the saved workspace version yet. Acceptance checks the revision again before applying it.
The preview receives the original and proposed text through Razor expressions:
<DocumentPreview Before="@(_session!.Snapshot().Document)" After="@(proposal.Document)" />
Keep the @ expressions: these are string parameters, so omitting @ would display the expression names as literal text.
Editing the committed document while a proposal is pending invalidates that review. Canceling or resetting also rejects unresolved proposals.
This is my favorite part of the demo:
AI can suggest a better version without taking ownership of my document.
Step 10: Show a Live Plan and Real Tool Activity
The plan scenario turns an agent's checklist into visible, editable UI rather than leaving it buried in a chat reply.
Try this on /demo/plan:
"Create a three-step demo plan and mark the first step completed."
Two tools drive the updates through AG-UI:
-
create_planreturns a snapshot containing the complete checklist. -
update_plan_stepreturns a delta that changes one step's status.
Each row shows a checkbox, a description, and a status: Pending, In progress, Completed, or Failed. You can also change checklist items yourself.
To keep updates consistent, the server allows only one plan tool call per model response. Extra calls are deferred so the model can review fresh state before deciding the next change. Revision checks reject stale writes instead of letting them overwrite newer user edits.
The activity panel records actual tool invocations-not the model's private reasoning. A deferred call is not shown as executed.
Checklist progress is demo state, not proof of real-world work. Marking a step completed does not mean a trip was booked or an external task was performed.
What About Reasoning Summaries?
The Microsoft article also discusses reasoning summaries when the provider supports them.
This demo's Ollama adapter/model contract does not expose a verified reasoning-summary channel. The reasoning page says that clearly instead of making up a summary.
Raw model thinking is filtered and is not shown as an activity feed.
These are different things:
- A tool activity is an actual application event.
- A supported reasoning summary is provider-supplied summary content.
- Raw internal thinking is not something this demo displays.
Not every provider supports every feature. A useful UI should explain that limit honestly.
Step 11: Run the Demo
With the pinned SDK installed and Ollama configured, run these commands from the repository root:
dotnet restore BlazorApp1_Demo.slnx
dotnet build BlazorApp1_Demo.slnx
dotnet run --project BlazorApp1_Demo/BlazorApp1_Demo.csproj --launch-profile http
Then:
- Open
http://localhost:5052and choose a scenario. - Use Check Ollama to inspect model metadata and tool capability. This is not an inference test or a guarantee that a request will succeed.
- When you are ready to send content to the cloud model, click Try example or type your own prompt.
Prefer Visual Studio? Select BlazorApp1_Demo as the startup project and use the http launch profile. If you change the app's port, update Ollama:AgentBaseAddress to match.
Useful controls to try:
- Try example to send the page's sample request
- Cancel to stop an active turn
- Retry after an error
- Reset scenario to start a new isolated session
If something fails, start with the relevant check:
-
Build fails: confirm that
dotnet --versionin the repository matchesglobal.json. -
Model request fails: check that Ollama is running, you are signed in, and
gemma4:31b-cloudis available to your account. -
Agent endpoint is unreachable: confirm that
AgentBaseAddressmatches the running app's address.
Limitations and Next Steps
This is an experimental local demo.
Before building a production application, I would add:
- Real user authentication and per-user authorization
- Durable conversation and workspace storage
- Auditing for sensitive actions
- Production secret management
- Browser, keyboard, screen-reader, and reconnection tests
- Monitoring and model usage controls
- A deployment and security review
Final Thoughts
I already liked Blazor because it lets me build interactive applications with C#.
These new AI components give me another reason to stay excited about it.
The useful change is not simply putting a chat box on a page. It is connecting conversation to a real workspace with typed results, visible progress, shared state, and clear user decisions.
My preferred flow is:
AI suggests
-> application validates
-> user reviews when needed
-> allowed tool executes
-> Blazor shows the result
More help from AI. Less guessing about what it actually did.
And when an important change needs approval, the final decision still belongs to the user.
References
- Daniel Roth, Microsoft .NET Blog: Build Agentic UI with the new Blazor AI components
- Official .NET 11 download page
This article is my practical walkthrough of the local demo, inspired by Microsoft's introduction.
Love C# & AI!
来源:Google AI:DEV 作者专属(RSS) · dev.to






