Lesson 35 · Web API & HttpClient

Web API & HttpClient

Fetch live data from REST APIs, parse JSON responses into typed VB classes, handle HTTP errors gracefully, and build a real-time weather or currency dashboard.

Key Takeaway: Use a single static HttpClient instance shared across the application — creating a new one per request exhausts sockets. Call Await _http.GetStringAsync(url) to fetch JSON, then JsonSerializer.Deserialize(Of T)(json) to parse it into a typed VB class. Always check response.IsSuccessStatusCode before reading the body and wrap calls in Try/Catch for network failures. Combine with Async/Await from Lesson 34 so the UI stays responsive while the request is in flight.
HttpClient
System.Net.Http
The .NET HTTP client. Declare once as Shared/Static — never create per-request.
GetStringAsync(url)
HttpClient method
Await to fetch URL as string. Returns JSON/HTML body. Throws on network failure.
GetAsync(url)
HttpClient method
Returns HttpResponseMessage. Check .IsSuccessStatusCode before reading content.
PostAsJsonAsync
HttpClient extension
POST an object as JSON body. Await response.Content.ReadAsStringAsync() for result.
DefaultRequestHeaders
HttpClient property
Add persistent headers: _http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token).
HttpStatusCode
Enum
200=OK, 401=Unauthorized, 404=Not Found, 429=Too Many Requests, 500=Server Error.
JsonPropertyName
Attribute
<JsonPropertyName("temp_c")> — maps JSON snake_case field to VB PascalCase property.
BaseAddress
HttpClient property
Set once: _http.BaseAddress = New Uri("https://api.example.com/"). Then use relative URLs.

35.1 Setting Up HttpClient

Declare HttpClient as a module-level or shared field — never inside a loop or method. It is thread-safe and designed to be reused. Set a BaseAddress and default headers once at startup.

HttpSetup.vb — Visual Basic 2026
Imports System.Net.Http
Imports System.Text.Json

' ✓ CORRECT — single instance, module-level
Private Shared _http As New HttpClient() With {
    .BaseAddress = New Uri("https://api.openweathermap.org/data/2.5/"),
    .Timeout     = TimeSpan.FromSeconds(10)
}

' Add default headers once
Shared Sub New()
    _http.DefaultRequestHeaders.Add("Accept", "application/json")
    _http.DefaultRequestHeaders.Add("User-Agent", "VBStudentApp/1.0")
End Sub

' ✗ WRONG — creates a new socket on every call (socket exhaustion)
Private Async Sub btnLoad_Click(...)
    Using http As New HttpClient()   ' never do this!
        ...
    End Using
End Sub

35.2 GET Request → Parse JSON

The typical pattern: Await GetStringAsync(url)JsonSerializer.Deserialize(Of T)(json). Define a VB class matching the JSON structure. Use <JsonPropertyName> when API field names differ from VB conventions.

WeatherAPI.vb — Visual Basic 2026
' --- Response class matching the API JSON structure ---
Public Class WeatherResponse
    <JsonPropertyName("name")>
    Public Property CityName As String
    <JsonPropertyName("main")>
    Public Property Main As WeatherMain
    <JsonPropertyName("weather")>
    Public Property Weather As WeatherCondition()
End Class
Public Class WeatherMain
    <JsonPropertyName("temp")>     Public Property Temp       As Double
    <JsonPropertyName("humidity")> Public Property Humidity   As Integer
End Class
Public Class WeatherCondition
    <JsonPropertyName("description")> Public Property Description As String
End Class

' --- Fetch and display ---
Private Async Sub btnGetWeather_Click(...) Handles btnGetWeather.Click
    Dim city = txtCity.Text.Trim()
    If city = "" Then Return
    btnGetWeather.Enabled = False
    Try
        Dim url  = $"weather?q={Uri.EscapeDataString(city)}&appid={API_KEY}&units=metric"
        Dim json = Await _http.GetStringAsync(url)
        Dim data = JsonSerializer.Deserialize(Of WeatherResponse)(json)
        lblCity.Text   = data.CityName
        lblTemp.Text   = $"{data.Main.Temp:F1} °C"
        lblHumid.Text  = $"{data.Main.Humidity}% humidity"
        lblDesc.Text   = data.Weather(0).Description
    Catch ex As HttpRequestException
        MessageBox.Show($"Network error: {ex.Message}")
    Catch ex As TaskCanceledException
        MessageBox.Show("Request timed out.")
    Catch ex As JsonException
        MessageBox.Show($"Unexpected response format: {ex.Message}")
    Finally
        btnGetWeather.Enabled = True
    End Try
End Sub

35.3 POST, Error Handling, and Status Codes

Not all requests are GET. Use PostAsJsonAsync to send data. Always inspect response.EnsureSuccessStatusCode() or check response.StatusCode explicitly — a 404 or 500 response still returns without throwing unless you check it.

HTTPPOST.vb — Visual Basic 2026
' --- POST: send JSON body ---
Private Async Sub PostStudentAsync(s As Student)
    Try
        Dim response = Await _http.PostAsJsonAsync("api/students", s)
        response.EnsureSuccessStatusCode()   ' throws on 4xx/5xx
        Dim created = JsonSerializer.Deserialize(Of Student)(
            Await response.Content.ReadAsStringAsync())
        lblStatus.Text = $"Created ID={created.StudentID}"
    Catch ex As HttpRequestException When ex.StatusCode = Net.HttpStatusCode.Conflict
        lblStatus.Text = "Duplicate — student already exists (409)"
    Catch ex As HttpRequestException When ex.StatusCode = Net.HttpStatusCode.Unauthorized
        lblStatus.Text = "Auth failed — check API key (401)"
    Catch ex As HttpRequestException
        lblStatus.Text = $"HTTP error {ex.StatusCode}: {ex.Message}"
    End Try
End Sub

' --- Manual status check (without EnsureSuccessStatusCode) ---
Dim response2 = Await _http.GetAsync("api/students/999")
Select Case response2.StatusCode
    Case Net.HttpStatusCode.OK
        Dim json = Await response2.Content.ReadAsStringAsync()
        ' deserialise…
    Case Net.HttpStatusCode.NotFound
        MessageBox.Show("Student 999 not found.")
    Case Net.HttpStatusCode.TooManyRequests
        MessageBox.Show("Rate limit exceeded — wait before retrying.")
    Case Else
        MessageBox.Show($"Unexpected: {response2.StatusCode}")
End Select
Free APIs to Practice With

No API key needed for these beginner-friendly REST endpoints:

  • JSONPlaceholderhttps://jsonplaceholder.typicode.com/users — fake users/posts/todos
  • Open-Meteohttps://api.open-meteo.com/v1/forecast?latitude=3.14&longitude=101.68¤t=temperature_2m — free weather (no key)
  • ExchangeRate-APIhttps://open.er-api.com/v6/latest/USD — currency rates (free tier)
  • PokeAPIhttps://pokeapi.co/api/v2/pokemon/pikachu — Pokémon data, great for learning nested JSON
Try It — Simulation 35.1: REST API Explorer

Choose an endpoint, configure parameters, and click Send — the simulation shows the HTTP request/response lifecycle, the raw JSON, and the parsed result grid. Error scenarios show the exact VB exception handling.

HttpClient — Mock REST API Explorer
API endpoint:
GET https://api.example.com/api/users
Raw JSON response:
— awaiting request —
— parsed result —

Try It — Simulation 35.2: Live Currency Dashboard

Simulate the ExchangeRate-API pattern — a base currency GET request that returns a dictionary of rates. Click Refresh to simulate a new API call with slightly varied rates.

Currency Dashboard — ExchangeRate-API mock
Base currency:

35.3 GitHub Copilot — Weather Dashboard

GitHub Copilot — complete weather dashboard
You typed: ' Build a weather dashboard form that uses HttpClient + Open-Meteo API (no key needed) to fetch current temperature, humidity, and wind speed for a user-entered city name. Geocode the city first using the Open-Meteo geocoding endpoint, then fetch weather. Show results in labelled panels with emoji icons.'
Private Shared _http As New HttpClient()
Private Const GEO_URL    = "https://geocoding-api.open-meteo.com/v1/search?name="
Private Const WEATHER_URL = "https://api.open-meteo.com/v1/forecast"

Private Async Sub btnSearch_Click(...) Handles btnSearch.Click
    Dim city = txtCity.Text.Trim()
    If city = "" Then Return
    btnSearch.Enabled = False
    lblStatus.Text = "🔍 Geocoding city..."
    Try
        ' Step 1 — geocode city name → lat/lon
        Dim geoJson = Await _http.GetStringAsync(GEO_URL & Uri.EscapeDataString(city) & "&count=1")
        Dim geoResp  = JsonSerializer.Deserialize(Of GeoResponse)(geoJson)
        If geoResp.Results Is Nothing OrElse geoResp.Results.Length = 0 Then
            lblStatus.Text = "City not found." : Return
        End If
        Dim loc = geoResp.Results(0)
        lblStatus.Text = $"📍 {loc.Name}, {loc.Country} — fetching weather..."

        ' Step 2 — fetch weather by lat/lon
        Dim weatherUrl = $"{WEATHER_URL}?latitude={loc.Latitude}&longitude={loc.Longitude}¤t=temperature_2m,relative_humidity_2m,wind_speed_10m&timezone=auto"
        Dim wJson = Await _http.GetStringAsync(weatherUrl)
        Dim w     = JsonSerializer.Deserialize(Of WeatherResponse2)(wJson)
        Dim c     = w.Current

        ' Step 3 — update UI (safe: Await already returned to UI thread)
        lblCity.Text  = $"🌍 {loc.Name}, {loc.Country}"
        lblTemp.Text  = $"🌡 {c.Temperature:F1} °C"
        lblHumid.Text = $"💧 {c.Humidity}% humidity"
        lblWind.Text  = $"💨 {c.WindSpeed:F1} km/h"
        lblStatus.Text = $"Updated {DateTime.Now:HH:mm:ss}"
    Catch ex As HttpRequestException
        lblStatus.Text = $"Network error: {ex.Message}"
    Catch ex As TaskCanceledException
        lblStatus.Text = "Request timed out — check your connection."
    Finally
        btnSearch.Enabled = True
    End Try
End Sub

Lesson Summary

  • Declare one static HttpClient instance per application. Creating a new one per request exhausts available sockets ("socket exhaustion"). Set BaseAddress and DefaultRequestHeaders once at startup.
  • Use Await _http.GetStringAsync(url) for simple GET requests. Combine with JsonSerializer.Deserialize(Of T)(json) to parse the response into a typed VB class.
  • Define response classes that mirror the JSON structure. Use <JsonPropertyName("field_name")> to map snake_case JSON fields to PascalCase VB properties.
  • For POST/PUT: use PostAsJsonAsync or build a StringContent with JsonSerializer.Serialize. Call response.EnsureSuccessStatusCode() to throw on 4xx/5xx, or check response.StatusCode explicitly with Select Case.
  • Catch HttpRequestException for network/HTTP errors, TaskCanceledException for timeouts, and JsonException for malformed responses — separately, in that order.
  • Always Async/Await all HttpClient calls (Lesson 34 pattern): disable the button, Await the request, update UI, re-enable in Finally. The UI stays responsive throughout.

Exercises

Exercise 35.1 — JSONPlaceholder CRUD

  • Use https://jsonplaceholder.typicode.com/users. GET all users and display in a DataGridView (ID, Name, Email, City from address.city nested JSON).
  • POST a new user object and show the server-assigned ID in a MessageBox.
  • DELETE a user by ID (DELETE /users/{id}) and refresh the grid.
  • Copilot challenge: "Extend with a retry helper: Async Function GetWithRetryAsync(url As String, maxRetries As Integer) that retries on HttpRequestException with exponential backoff (Await Task.Delay(2^attempt * 500))"

Exercise 35.2 — Currency Converter

  • Use https://open.er-api.com/v6/latest/USD (free, no key). Deserialise the rates dictionary into Dictionary(Of String, Double).
  • Two ComboBoxes (From/To currency), a NumericUpDown for amount. Compute and display the converted amount live on selection change.
  • Cache the rates in a module-level Dictionary. Refresh only if the data is older than 60 minutes (compare DateTime.Now to last-fetched timestamp).
  • Copilot challenge: "Add a rate history chart: store the last 10 refresh snapshots in a List(Of RateSnapshot) and draw a simple GDI+ line chart on a PictureBox"

Next: Lesson 36 — Conclusion & Next Steps

Recap all 35 lessons, best-practices checklist, project ideas, NuGet ecosystem, and VB→C# comparison.

Continue »

Related Resources


Featured Books

VB Programming With Code Examples

VB Programming With Code Examples

by Dr. Liew Voon Kiong

Network programming, HTTP, and REST API integration with VB.NET.

View on Amazon →