Previously, we mapped the component
lifecycle and learned that OnInitializedAsync is the place to load data
— today we finally give ClinicApp some data worth loading, by calling a real web API across the
internet.
Real apps talk to the outside world
So far, all of ClinicApp's patients have lived inside a PatientService — cozy, but a
little lonely. Real apps rarely work like that. They fetch weather from a weather API, products
from a store API, appointments from a clinic server. The tool for that conversation in .NET is
HttpClient: a built-in class whose whole job is making web requests and handing you
back the response.
To practice safely, we'll use JSONPlaceholder — a free, fake online API made exactly for learners like us. It serves pretend users, posts, and comments that we can fetch all day long without signing up for anything. Perfect.
Step 1: register HttpClient
Remember from Part 9 how services get
registered in Program.cs before anyone can inject them? HttpClient is no
different. In current Blazor (.NET 10), the simplest road is one line:
builder.Services.AddScoped(sp => new HttpClient());
Translation: "whenever a component asks for an HttpClient, hand it one." That's it —
the doors to the internet are open.
Note: Bigger production apps usually register clients through
IHttpClientFactory (via AddHttpClient), which manages connections
more carefully. We're deliberately taking the simple road here — it works great while you
learn, and the factory will make perfect sense later.
Step 2: inject it and fetch
In a component, we ask for the client with @inject HttpClient Http, then fetch inside
OnInitializedAsync — exactly where Part 10 taught us data-loading belongs:
users = await Http.GetFromJsonAsync<User[]>(
"https://jsonplaceholder.typicode.com/users");
One line, and a lot happens: the request travels to JSONPlaceholder, the server replies with JSON, and Blazor converts that JSON into an array of C# objects for us. All we need is a class whose shape matches the data:
public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}
Notice we didn't write any parsing code. Property names map from the JSON fields automatically —
the JSON's "name" flows into our Name, "email" into
Email, and fields we don't care about are simply ignored. If JSON itself still feels
mysterious, take a quick detour through
JSON explained for beginners and come right back.
Tip: GetFromJsonAsync is really two jobs in one line — it
downloads the response and parses the JSON into your objects. Doing this manually
takes half a page of code. One line. Enjoy it.
When things go wrong (because sometimes they will)
The internet is not a promise. Servers nap, Wi-Fi drops, URLs get typo'd. If our fetch fails and we
do nothing, the user sees a broken page — not a great look for a clinic. The fix is a
try/catch plus one more piece of state: a friendly error message. Here's the complete
page, loading pattern and all:
@page "/directory"
@rendermode InteractiveServer
@using System.Net.Http.Json
@inject HttpClient Http
<h2>Team directory</h2>
@if (loading)
{
<p>Loading users…</p>
}
else if (errorMessage is not null)
{
<p>@errorMessage</p>
}
else if (users is not null)
{
<ul>
@foreach (var user in users)
{
<li><strong>@user.Name</strong> — @user.Email</li>
}
</ul>
}
@code {
private User[]? users;
private bool loading = true;
private string? errorMessage;
protected override async Task OnInitializedAsync()
{
try
{
users = await Http.GetFromJsonAsync<User[]>(
"https://jsonplaceholder.typicode.com/users");
}
catch (Exception)
{
errorMessage = "Sorry — we couldn't load the directory. Please try again in a moment.";
}
loading = false;
}
public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}
}
Walk through the three branches and you'll see the whole story of a network request: first we're
waiting (loading), then either something went wrong (errorMessage) or we
have data (users). If the request throws — no connection, server error, anything — the
catch swaps in a human-friendly message instead of a crash. Run it, then turn off your
Wi-Fi and refresh to watch the error branch earn its keep.
Wait — was that REST?
Yes! What we just did — sending a GET request to a URL like /users and receiving JSON
back — is the everyday rhythm of a REST API. The same pattern extends to creating,
updating, and deleting data with PostAsJsonAsync and friends. If you'd like the big
picture of how these APIs are organized (with a pizza shop instead of a clinic), read
REST APIs explained with a pizza shop —
it pairs beautifully with today's code.
And with that, ClinicApp can talk to the outside world. Take a moment — that's a genuinely big
milestone. Only one part remains: in
Part 12, the series finale, we finally
unravel the mystery we've been carrying since day one — what
@rendermode InteractiveServer actually means, what the other render modes are, and
where your Blazor journey goes from here. See you at the finish line!