{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/fetch-and-send-data",
  "version": "1.0.0",
  "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).",
  "system_prompt_fragment": "# Fetch and Send Data\n\n## Step 1 — Read AGENTS.md\n\nCheck **Interactivity Mode** and **Scope**:\n\n| Mode | Data access |\n|------|-------------|\n| None (Static SSR) | Server-side: inject services/`DbContext`. Use `[StreamRendering]` for loading UX. |\n| Server | Server-side: inject services/`DbContext`. Guard prerender with `??=` + `[PersistentState]`. |\n| WebAssembly | Browser-side: `HttpClient` only. No direct server access. |\n| Auto | Both server and browser. Always go through an API. |\n\n## Step 2 — Register HttpClient\n\nOnly 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.\n\n```csharp\n// Named client — requires Microsoft.Extensions.Http NuGet\nbuilder.Services.AddHttpClient(\"CatalogAPI\", client =>\n{\n    client.BaseAddress = new Uri(\"https://api.example.com/\");\n});\n\n// Typed client\nbuilder.Services.AddHttpClient<CatalogClient>(client =>\n    client.BaseAddress = new Uri(\"https://api.example.com/\"));\n```\n\nFor WebAssembly/Auto with prerendering, register in **both** server and `.Client` `Program.cs`.\n\n## Step 3 — Fetch Data\n\n### Simple load\n\n```razor\n@page \"/products\"\n@inject CatalogClient Catalog\n\n@if (products is null)\n{\n    <p>Loading…</p>\n}\nelse\n{\n    @foreach (var p in products)\n    {\n        <p>@p.Name — @p.Price.ToString(\"C\")</p>\n    }\n}\n\n@code {\n    private Product[]? products;\n\n    protected override async Task OnInitializedAsync()\n    {\n        products = await Catalog.GetProductsAsync();\n    }\n}\n```\n\nNo error handling needed in the simplest case — wrap the component usage in `<ErrorBoundary>` at the parent/layout level to catch unhandled exceptions.\n\n### Static SSR — StreamRendering\n\nWithout `[StreamRendering]`, the user sees nothing until `OnInitializedAsync` completes:\n\n```razor\n@attribute [StreamRendering]\n```\n\nOnly affects Static SSR. No effect on interactive components.\n\n### Prerendering guard\n\nPrerendering calls `OnInitializedAsync` twice. Skip the duplicate:\n\n```csharp\n[PersistentState] private Product[]? products;\n\nprotected override async Task OnInitializedAsync()\n{\n    products ??= await Catalog.GetProductsAsync();\n}\n```\n\nSee the `support-prerendering` skill for details.\n\n## Step 4 — Handle Errors\n\nUse `<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:\n\n```razor\n<ErrorBoundary>\n    <ChildContent>\n        <ProductList />\n    </ChildContent>\n    <ErrorContent>\n        <div class=\"alert alert-danger\">Something went wrong. Please refresh.</div>\n    </ErrorContent>\n</ErrorBoundary>\n```\n\nNon-cancellation exceptions (`HttpRequestException`, etc.) propagate to `ErrorBoundary` automatically — no catch blocks needed in the component.\n\n### Cancellation is special\n\n`ComponentBase` silently swallows **all** `OperationCanceledException` — both self-initiated (disposal, parameter change) and external (HttpClient timeout). `ErrorBoundary` never sees them. This means:\n\n- Self-cancellation → silently ignored. Correct behavior, no action needed.\n- External cancellation (timeout) → also silently swallowed. Component gets stuck in loading state. Usually acceptable — timeouts are rare.\n\n### When to add in-component error handling\n\nOnly 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:\n\n```csharp\n// Catch only external cancellation (timeouts) — everything else flows to ErrorBoundary\ncatch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)\n{\n    Logger.LogWarning(ex, \"Request timed out for category {CategoryId}\", CategoryId);\n    error = \"The request timed out. Please try again.\";\n}\n```\n\nIf the component also needs to handle general errors with a retry button instead of letting `ErrorBoundary` take over:\n\n```csharp\ncatch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)\n{\n    Logger.LogWarning(ex, \"Request timed out for category {CategoryId}\", CategoryId);\n    error = \"The request timed out. Please try again.\";\n}\ncatch (Exception ex)\n{\n    Logger.LogError(ex, \"Failed to load products for category {CategoryId}\", CategoryId);\n    error = \"Unable to load products. Please try again.\";\n}\n```\n\n### Rules\n\n- **Never display `exception.Message`** — it may contain PII, connection strings, or internal details. Use hardcoded user-friendly messages.\n- **Always log through `ILogger`** — the real exception goes to the logging pipeline.\n- **Services must accept `CancellationToken`** — pass it to every async call so work stops when the component cancels.\n\n## Step 5 — Parameter-Driven Reloading\n\nWhen 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.\n\n### Pattern: cancel-and-reload with stale data overlay\n\n```razor\n@page \"/products/{CategoryId:int}\"\n@implements IAsyncDisposable\n@inject ProductService ProductService\n@inject ILogger<Products> Logger\n\n@if (error is not null)\n{\n    <div class=\"alert alert-danger\">\n        <p>@error</p>\n        <button @onclick=\"LoadAsync\">Retry</button>\n    </div>\n}\nelse if (products is null)\n{\n    <p>Loading…</p>\n}\nelse\n{\n    @if (isLoading)\n    {\n        <p><em>Refreshing…</em></p>\n    }\n    @foreach (var p in products)\n    {\n        <p>@p.Name — @p.Price.ToString(\"C\")</p>\n    }\n}\n\n@code {\n    [Parameter] public int CategoryId { get; set; }\n    [SupplyParameterFromQuery] public string? ViewMode { get; set; } // UI-only\n\n    private CancellationTokenSource? cts;\n    private int? loadedCategoryId;\n    private List<Product>? products;\n    private bool isLoading;\n    private string? error;\n\n    protected override async Task OnParametersSetAsync()\n    {\n        if (CategoryId == loadedCategoryId)\n        {\n            return; // Only ViewMode changed — no reload\n        }\n\n        loadedCategoryId = CategoryId;\n        await LoadAsync();\n    }\n\n    private async Task LoadAsync()\n    {\n        if (cts is not null)\n        {\n            await cts.CancelAsync();\n            cts.Dispose();\n        }\n\n        cts = new CancellationTokenSource();\n        var cancellationToken = cts.Token; // Capture locally before await\n\n        error = null;\n        isLoading = true;\n\n        try\n        {\n            var result = await ProductService.GetByCategoryAsync(CategoryId, cancellationToken);\n            products = result;\n        }\n        catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)\n        {\n            Logger.LogWarning(ex, \"Timed out loading category {CategoryId}\", CategoryId);\n            error = \"The request timed out. Please try again.\";\n        }\n        finally\n        {\n            isLoading = false;\n        }\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (cts is not null)\n        {\n            await cts.CancelAsync();\n            cts.Dispose();\n        }\n    }\n}\n```\n\nKey details:\n- **Guard with tracked value**: `loadedCategoryId` skips reloads when only UI parameters change.\n- **Capture the token locally** before the await — the CTS field may be replaced by a concurrent parameter change.\n- **Don't null out `products`** on subsequent loads — keep existing data visible with an `isLoading` overlay.\n- **`IAsyncDisposable`** cancels pending work when the user navigates away.\n\n## Step 6 — Send Data\n\n```csharp\nvar response = await http.PostAsJsonAsync(\"products\", newProduct);\nresponse.EnsureSuccessStatusCode();\n\nvar response = await http.PutAsJsonAsync($\"products/{id}\", updated);\nresponse.EnsureSuccessStatusCode();\n\nvar response = await http.DeleteAsync($\"products/{id}\");\nresponse.EnsureSuccessStatusCode();\n```\n\nDisable the submit button while saving to prevent duplicate requests. Show a saving indicator.\n\n## Step 7 — Service Abstraction for Auto or WebAssembly with Prerendering\n\nWhen components run in both server and browser (Auto mode, or WebAssembly with prerendering), abstract data access behind an abstract base class:\n\n```csharp\npublic abstract class ProductServiceBase\n{\n    public abstract Task<Product[]> GetAllAsync(CancellationToken ct = default);\n}\n\n// Server — direct database access\npublic class ServerProductService(AppDbContext db) : ProductServiceBase\n{\n    public override async Task<Product[]> GetAllAsync(CancellationToken ct = default) =>\n        await db.Products.ToArrayAsync(ct);\n}\n\n// Client — calls API\npublic class ClientProductService(HttpClient http) : ProductServiceBase\n{\n    public override async Task<Product[]> GetAllAsync(CancellationToken ct = default) =>\n        await http.GetFromJsonAsync<Product[]>(\"api/products\", ct) ?? [];\n}\n```\n\nRegister the appropriate implementation in each project's `Program.cs`. Components inject the abstract base class.\n\n## Don'ts\n\n- **Don't call APIs in constructors** — use `OnInitializedAsync`.\n- **Don't use `OnParametersSetAsync` unless data depends on a changing parameter.** Use `OnInitializedAsync` for initial loads.\n- **Don't inject `DbContext` in WebAssembly/Auto components** — no database in the browser.\n- **Don't call your own server via `HttpClient`** — inject the service directly.\n- **Don't display `exception.Message` to users** — PII risk. Log it, show a generic message.\n- **Don't catch `OperationCanceledException` for self-cancellation** — `ComponentBase` handles it.",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/fetch-and-send-data"
  ],
  "tags": [
    "dotnet-blazor",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-blazor/skills/fetch-and-send-data/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-blazor/skills/fetch-and-send-data/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}