/fetch-and-send-data
Call APIs, load data into components, and handle the async lifecycle in Blazor. USE FOR fetching data from a backend, submitting data to an API, displaying loading/error states, registering HttpClient, building service abstractions for Auto/WebAssembly render modes. DO NOT USE
Claude Code
$ npx -y skills add dotnet/skills --skill fetch-and-send-data --agent claude-code
Codex
$ npx skills add dotnet/skills --skill fetch-and-send-data -a codex -y
Cursor
$ npx skills add dotnet/skills --skill fetch-and-send-data -a cursor -y
Gemini CLI
$ npx skills add dotnet/skills --skill fetch-and-send-data -a gemini-cli -y
GitHub Copilot
$ npx skills add dotnet/skills --skill fetch-and-send-data -a github-copilot -y
OpenCode
$ npx skills add dotnet/skills --skill fetch-and-send-data -a opencode -y
Windsurf
$ npx skills add dotnet/skills --skill fetch-and-send-data -a windsurf -y
SKILL.md
fetch-and-send-data.SKILL.mdlicense: MIT
name: fetch-and-send-data
description: Call APIs, load data into components, and handle the async lifecycle in Blazor. USE FOR fetching data from a backend, submitting data to an API, displaying loading/error states, registering HttpClient, building service abstractions for Auto/WebAssembly render modes. DO NOT USE for form validation (see collect-user-input), prerendering persistence (see support-prerendering), or project scaffolding (see create-blazor-project).
Fetch and Send Data
Step 1 — Read AGENTS.md
Check **Interactivity Mode** and **Scope**:
| Mode | Data access | |------|-------------| | None (Static SSR) | Server-side: inject services/`DbContext`. Use `[StreamRendering]` for loading UX. | | Server | Server-side: inject services/`DbContext`. Guard prerender with `??=` + `[PersistentState]`. | | WebAssembly | Browser-side: `HttpClient` only. No direct server access. | | Auto | Both server and browser. Always go through an API. |
Step 2 — Register HttpClient
Only needed when calling external APIs from Server, or always for WebAssembly/Auto. Server components accessing their own database should inject `DbContext` or a service directly.
// Named client — requires Microsoft.Extensions.Http NuGet
builder.Services.AddHttpClient("CatalogAPI", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
});
// Typed client
builder.Services.AddHttpClient<CatalogClient>(client =>
client.BaseAddress = new Uri("https://api.example.com/"));For WebAssembly/Auto with prerendering, register in **both** server and `.Client` `Program.cs`.
Step 3 — Fetch Data
Simple load
@page "/products"
@inject CatalogClient Catalog
@if (products is null)
{
<p>Loading…</p>
}
else
{
@foreach (var p in products)
{
<p>@p.Name — @p.Price.ToString("C")</p>
}
}
@code {
private Product[]? products;
protected override async Task OnInitializedAsync()
{
products = await Catalog.GetProductsAsync();
}
}No error handling needed in the simplest case — wrap the component usage in `<ErrorBoundary>` at the parent/layout level to catch unhandled exceptions.
Static SSR — StreamRendering
Without `[StreamRendering]`, the user sees nothing until `OnInitializedAsync` completes:
@attribute [StreamRendering]
Only affects Static SSR. No effect on interactive components.
Prerendering guard
Prerendering calls `OnInitializedAsync` twice. Skip the duplicate:
[PersistentState] private Product[]? products;
protected override async Task OnInitializedAsync()
{
products ??= await Catalog.GetProductsAsync();
}See the `support-prerendering` skill for details.
Step 4 — Handle Errors
Use `<ErrorBoundary>` as the default error strategy. It provides a consistent error experience across all components without any per-component catch logic. Wrap component usage at the layout or parent level:
<ErrorBoundary>
<ChildContent>
<ProductList />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger">Something went wrong. Please refresh.</div>
</ErrorContent>
</ErrorBoundary>Non-cancellation exceptions (`HttpRequestException`, etc.) propagate to `ErrorBoundary` automatically — no catch blocks needed in the component.
Cancellation is special
`ComponentBase` silently swallows **all** `OperationCanceledException` — both self-initiated (disposal, parameter change) and external (HttpClient timeout). `ErrorBoundary` never sees them. This means:
- Self-cancellation → silently ignored. Correct behavior, no action needed.
- External cancellation (timeout) → also silently swallowed. Component gets stuck in loading state. Usually acceptable — timeouts are rare.
When to add in-component error handling
Only add catch blocks when the component needs behavior `ErrorBoundary` can't provide — typically **retries** or **timeout-specific messages**. Even then, only catch what you need:
// Catch only external cancellation (timeouts) — everything else flows to ErrorBoundary
catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
{
Logger.LogWarning(ex, "Request timed out for category {CategoryId}", CategoryId);
error = "The request timed out. Please try again.";
}If the component also needs to handle general errors with a retry button instead of letting `ErrorBoundary` take over:
catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
{
Logger.LogWarning(ex, "Request timed out for category {CategoryId}", CategoryId);
error = "The request timed out. Please try again.";
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to load products for category {CategoryId}", CategoryId);
error = "Unable to load products. Please try again.";
}Rules
- **Never display `exception.Message`** — it may contain PII, connection strings, or internal details. Use hardcoded user-friendly messages.
- **Always log through `ILogger`** — the real exception goes to the logging pipeline.
- **Services must accept `CancellationToken`** — pass it to every async call so work stops when the component cancels.
Step 5 — Parameter-Driven Reloading
When data depends on a route or query parameter that changes (e.g., navigating between `/products/1` and `/products/2`), use `OnParametersSetAsync` with a guard to skip reloads for parameters that don't affect data.
Pattern: cancel-and-reload with stale data overlay
@page "/products/{CategoryId:int}"
@implements IAsyncDisposable
@inject ProductService ProductService
@inject ILogger<Products> Logger
@if (error is not null)
{
<div class="alert alert-danger">
<p>@error</p>
<button @onclick="LoadAsync">Retry</button>
</div>
}
else if (products is null)
{
<p>Loading…</p>
}
else
{
@if (isLoading)
{
<p><em>Refreshing…</em></p>
}Read more
license: MIT name: fetch-and-send-data description: Call APIs, load data into components, and handle the async lifecycle in Blazor. USE FOR fetching data from a backend, submitting data to an API, displaying loading/error states, registering HttpClient, building service abstractions for Auto/WebAssembly render modes. DO NOT USE for form validation (see collect-user-input), prerendering persistence (see support-prerendering), or project scaffolding (see create-blazor-project).
Fetch and Send Data
Step 1 — Read AGENTS.md
Check **Interactivity Mode** and **Scope**:
| Mode | Data access | |------|-------------| | None (Static SSR) | Server-side: inject services/`DbContext`. Use `[StreamRendering]` for loading UX. | | Server | Server-side: inject services/`DbContext`. Guard prerender with `??=` + `[PersistentState]`. | | WebAssembly | Browser-side: `HttpClient` only. No direct server access. | | Auto | Both server and browser. Always go through an API. |
Step 2 — Register HttpClient
Only needed when calling external APIs from Server, or always for WebAssembly/Auto. Server components accessing their own database should inject `DbContext` or a service directly.
// Named client — requires Microsoft.Extensions.Http NuGet
builder.Services.AddHttpClient("CatalogAPI", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
});
// Typed client
builder.Services.AddHttpClient<CatalogClient>(client =>
client.BaseAddress = new Uri("https://api.example.com/"));For WebAssembly/Auto with prerendering, register in **both** server and `.Client` `Program.cs`.
Step 3 — Fetch Data
Simple load
@page "/products"
@inject CatalogClient Catalog
@if (products is null)
{
<p>Loading…</p>
}
else
{
@foreach (var p in products)
{
<p>@p.Name — @p.Price.ToString("C")</p>
}
}
@code {
private Product[]? products;
protected override async Task OnInitializedAsync()
{
products = await Catalog.GetProductsAsync();
}
}No error handling needed in the simplest case — wrap the component usage in `<ErrorBoundary>` at the parent/layout level to catch unhandled exceptions.
Static SSR — StreamRendering
Without `[StreamRendering]`, the user sees nothing until `OnInitializedAsync` completes:
@attribute [StreamRendering]
Only affects Static SSR. No effect on interactive components.
Prerendering guard
Prerendering calls `OnInitializedAsync` twice. Skip the duplicate:
[PersistentState] private Product[]? products;
protected override async Task OnInitializedAsync()
{
products ??= await Catalog.GetProductsAsync();
}See the `support-prerendering` skill for details.
Step 4 — Handle Errors
Use `<ErrorBoundary>` as the default error strategy. It provides a consistent error experience across all components without any per-component catch logic. Wrap component usage at the layout or parent level:
<ErrorBoundary>
<ChildContent>
<ProductList />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger">Something went wrong. Please refresh.</div>
</ErrorContent>
</ErrorBoundary>Non-cancellation exceptions (`HttpRequestException`, etc.) propagate to `ErrorBoundary` automatically — no catch blocks needed in the component.
Cancellation is special
`ComponentBase` silently swallows **all** `OperationCanceledException` — both self-initiated (disposal, parameter change) and external (HttpClient timeout). `ErrorBoundary` never sees them. This means:
- Self-cancellation → silently ignored. Correct behavior, no action needed.
- External cancellation (timeout) → also silently swallowed. Component gets stuck in loading state. Usually acceptable — timeouts are rare.
When to add in-component error handling
Only add catch blocks when the component needs behavior `ErrorBoundary` can't provide — typically **retries** or **timeout-specific messages**. Even then, only catch what you need:
// Catch only external cancellation (timeouts) — everything else flows to ErrorBoundary
catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
{
Logger.LogWarning(ex, "Request timed out for category {CategoryId}", CategoryId);
error = "The request timed out. Please try again.";
}If the component also needs to handle general errors with a retry button instead of letting `ErrorBoundary` take over:
catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
{
Logger.LogWarning(ex, "Request timed out for category {CategoryId}", CategoryId);
error = "The request timed out. Please try again.";
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to load products for category {CategoryId}", CategoryId);
error = "Unable to load products. Please try again.";
}Rules
- **Never display `exception.Message`** — it may contain PII, connection strings, or internal details. Use hardcoded user-friendly messages.
- **Always log through `ILogger`** — the real exception goes to the logging pipeline.
- **Services must accept `CancellationToken`** — pass it to every async call so work stops when the component cancels.
Step 5 — Parameter-Driven Reloading
When data depends on a route or query parameter that changes (e.g., navigating between `/products/1` and `/products/2`), use `OnParametersSetAsync` with a guard to skip reloads for parameters that don't affect data.
Pattern: cancel-and-reload with stale data overlay
@page "/products/{CategoryId:int}"
@implements IAsyncDisposable
@inject ProductService ProductService
@inject ILogger<Products> Logger
@if (error is not null)
{
<div class="alert alert-danger">
<p>@error</p>
<button @onclick="LoadAsync">Retry</button>
</div>
}
else if (products is null)
{
<p>Loading…</p>
}
else
{
@if (isLoading)
{
<p><em>Refreshing…</em></p>
}This repository contains the .NET team's curated set of portable skills and host-specific custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io.
Other skills on skills.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about fetch-and-send-data.
- Which coding agents can use fetch-and-send-data?
- fetch-and-send-data installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

