C#
.NET 4.5
HttpClient
GetAsync
Callbacks

How do I create a callback in C .NET 4.5 for HttpClient.GetAsyncURI?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In .NET 4.5, HttpClient.GetAsync is task based, but many codebases still need callback style APIs for legacy integration points. You can support callbacks safely by keeping async and await in the core implementation, then adapting completion into success and error delegates. This avoids duplicated logic and preserves proper exception handling.

Start from an Async Core Method

Build one reliable task returning method first. It should handle status checks, read content, and let callers decide how to consume it.

csharp
1using System.Net.Http;
2using System.Threading;
3using System.Threading.Tasks;
4
5public static class HttpFetcher
6{
7    private static readonly HttpClient Client = new HttpClient();
8
9    public static async Task<string> GetBodyAsync(string uri, CancellationToken token)
10    {
11        using (var response = await Client.GetAsync(uri, token).ConfigureAwait(false))
12        {
13            response.EnsureSuccessStatusCode();
14            return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
15        }
16    }
17}

This method is easy to unit test and can be reused by both callback and fully async callers.

Add a Callback Adapter Layer

Now expose callback semantics for legacy consumers.

csharp
1using System;
2using System.Threading;
3
4public static class CallbackApi
5{
6    public static void GetWithCallback(
7        string uri,
8        Action<string> onSuccess,
9        Action<Exception> onError,
10        CancellationToken token)
11    {
12        var task = HttpFetcher.GetBodyAsync(uri, token);
13
14        task.ContinueWith(t =>
15        {
16            if (t.IsCanceled)
17            {
18                onError?.Invoke(new OperationCanceledException("Request was canceled."));
19                return;
20            }
21
22            if (t.IsFaulted)
23            {
24                onError?.Invoke(t.Exception.GetBaseException());
25                return;
26            }
27
28            onSuccess?.Invoke(t.Result);
29        }, TaskScheduler.Default);
30    }
31}

This keeps callback compatibility without rewriting networking logic.

Keep UI Thread Rules Explicit

If callback consumers update WinForms or WPF controls, marshal to UI thread. Continuations may run on thread pool threads depending on context and ConfigureAwait usage.

WinForms pattern:

csharp
1// inside a Form class
2CallbackApi.GetWithCallback(
3    "https://example.com",
4    body => this.BeginInvoke(new Action(() => resultTextBox.Text = body)),
5    ex => this.BeginInvoke(new Action(() => statusLabel.Text = ex.Message)),
6    CancellationToken.None
7);

Without UI marshaling, cross thread exceptions are likely.

Include Cancellation and Timeouts

Callback contracts should expose cancellation so callers can cancel stale requests.

csharp
1using System;
2using System.Threading;
3
4var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
5CallbackApi.GetWithCallback(
6    "https://example.com/data",
7    body => Console.WriteLine(body),
8    ex => Console.WriteLine("Error: " + ex.Message),
9    cts.Token
10);

Differentiate timeout from server errors in your error callback messaging. This improves operational diagnostics.

Design a Stronger Callback Result Contract

For larger systems, passing only body text in success callback is limiting. Consider a simple result object.

csharp
1public sealed class HttpCallbackResult
2{
3    public int StatusCode { get; set; }
4    public string Body { get; set; }
5    public string Uri { get; set; }
6}

Then callback receives richer data without requiring downstream parsing of exceptions for routine control flow.

Avoid Creating Many HttpClient Instances

In .NET 4.5 applications, repeatedly creating and disposing HttpClient per request can lead to socket exhaustion patterns. Prefer one long lived shared instance.

csharp
1private static readonly HttpClient Client = new HttpClient
2{
3    Timeout = TimeSpan.FromSeconds(15)
4};

Reuse this instance in your async core method.

Testing Callback Behavior

Test both success and failure paths. You can wrap callback completion with TaskCompletionSource in tests.

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5public static Task<string> InvokeCallbackApiAsTask(string uri)
6{
7    var tcs = new TaskCompletionSource<string>();
8
9    CallbackApi.GetWithCallback(
10        uri,
11        body => tcs.TrySetResult(body),
12        ex => tcs.TrySetException(ex),
13        CancellationToken.None
14    );
15
16    return tcs.Task;
17}

This makes callback APIs testable with normal async test methods.

Common Pitfalls

A common pitfall is writing all logic inside ContinueWith, which hides exceptions and creates nested async complexity. Keep networking logic in one async method and callbacks as adapters.

Another issue is swallowing TaskCanceledException and reporting it as unknown failure. Distinguish cancellation clearly so callers can react correctly.

Teams also forget thread affinity for UI updates. Always marshal callbacks that touch UI controls.

Summary

  • In .NET 4.5, GetAsync is task based and should remain the core implementation.
  • Add callbacks as a thin adapter for legacy integration points.
  • Handle success, failure, and cancellation explicitly in callback flow.
  • Marshal callbacks to UI thread when updating controls.
  • Reuse a shared HttpClient and keep callback contracts clear and testable.

Course illustration
Course illustration

All Rights Reserved.