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.
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.
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.
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.
' --- 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.
' --- 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
No API key needed for these beginner-friendly REST endpoints:
- JSONPlaceholder —
https://jsonplaceholder.typicode.com/users— fake users/posts/todos - Open-Meteo —
https://api.open-meteo.com/v1/forecast?latitude=3.14&longitude=101.68¤t=temperature_2m— free weather (no key) - ExchangeRate-API —
https://open.er-api.com/v6/latest/USD— currency rates (free tier) - PokeAPI —
https://pokeapi.co/api/v2/pokemon/pikachu— Pokémon data, great for learning nested JSON
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.
| — parsed result — |
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.
35.3 GitHub Copilot — Weather Dashboard
' 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
BaseAddressandDefaultRequestHeadersonce at startup. - Use
Await _http.GetStringAsync(url)for simple GET requests. Combine withJsonSerializer.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
PostAsJsonAsyncor build aStringContentwithJsonSerializer.Serialize. Callresponse.EnsureSuccessStatusCode()to throw on 4xx/5xx, or checkresponse.StatusCodeexplicitly withSelect 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 fromaddress.citynested 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 theratesdictionary intoDictionary(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.Nowto 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"
Related Resources
← Lesson 34
Async Programming — Await, Task, IProgress.
Lesson 36 →
Conclusion & Next Steps.
MS Docs — HttpClient
Official HttpClient best-practices guide.
Open-Meteo API
Free weather API, no key required.
Featured Books
VB Programming With Code Examples
Network programming, HTTP, and REST API integration with VB.NET.
View on Amazon →