HttpWebRequest
.NET
asynchronous programming
C#
async HttpWebRequest

How to use HttpWebRequest .NET asynchronously?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

HttpWebRequest supports asynchronous operations in .NET through the GetResponseAsync() and GetRequestStreamAsync() methods, which integrate with async/await. However, HttpWebRequest is a legacy API — Microsoft recommends HttpClient for all new code because it is simpler, supports connection pooling by default, and handles common patterns like timeouts and cancellation more cleanly. This article covers both the legacy HttpWebRequest async pattern and the modern HttpClient approach.

Async GET with HttpWebRequest

csharp
1using System;
2using System.IO;
3using System.Net;
4using System.Threading.Tasks;
5
6public static async Task<string> GetAsync(string url)
7{
8    var request = (HttpWebRequest)WebRequest.Create(url);
9    request.Method = "GET";
10    request.Timeout = 10000; // 10 seconds
11
12    using var response = (HttpWebResponse)await request.GetResponseAsync();
13    using var stream = response.GetResponseStream();
14    using var reader = new StreamReader(stream);
15    return await reader.ReadToEndAsync();
16}
17
18// Usage
19string html = await GetAsync("https://example.com");
20Console.WriteLine(html);

GetResponseAsync() returns a Task<WebResponse> that completes when the server responds. The calling thread is not blocked while waiting.

Async POST with HttpWebRequest

csharp
1public static async Task<string> PostAsync(string url, string jsonBody)
2{
3    var request = (HttpWebRequest)WebRequest.Create(url);
4    request.Method = "POST";
5    request.ContentType = "application/json";
6
7    // Write the request body asynchronously
8    using (var requestStream = await request.GetRequestStreamAsync())
9    using (var writer = new StreamWriter(requestStream))
10    {
11        await writer.WriteAsync(jsonBody);
12    }
13
14    // Read the response asynchronously
15    using var response = (HttpWebResponse)await request.GetResponseAsync();
16    using var stream = response.GetResponseStream();
17    using var reader = new StreamReader(stream);
18    return await reader.ReadToEndAsync();
19}
20
21// Usage
22string result = await PostAsync(
23    "https://api.example.com/data",
24    "{\"name\": \"Alice\"}"
25);

Both the request body upload and the response download happen asynchronously without blocking threads.

Error Handling with HttpWebRequest

csharp
1public static async Task<string> SafeGetAsync(string url)
2{
3    try
4    {
5        var request = (HttpWebRequest)WebRequest.Create(url);
6        request.Method = "GET";
7        request.Timeout = 10000;
8
9        using var response = (HttpWebResponse)await request.GetResponseAsync();
10        using var stream = response.GetResponseStream();
11        using var reader = new StreamReader(stream);
12        return await reader.ReadToEndAsync();
13    }
14    catch (WebException ex) when (ex.Response is HttpWebResponse errorResponse)
15    {
16        // Server returned a non-2xx status code
17        int statusCode = (int)errorResponse.StatusCode;
18        using var stream = errorResponse.GetResponseStream();
19        using var reader = new StreamReader(stream);
20        string body = await reader.ReadToEndAsync();
21        throw new Exception($"HTTP {statusCode}: {body}");
22    }
23    catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
24    {
25        throw new TimeoutException($"Request to {url} timed out");
26    }
27}

HttpWebRequest throws WebException for non-2xx status codes, which makes error handling verbose compared to HttpClient.

Legacy APM Pattern (BeginGetResponse / EndGetResponse)

Before async/await, .NET used the Asynchronous Programming Model (APM) with Begin/End methods:

csharp
1// Old APM pattern — avoid in new code
2var request = (HttpWebRequest)WebRequest.Create("https://example.com");
3
4request.BeginGetResponse(ar =>
5{
6    var response = (HttpWebResponse)request.EndGetResponse(ar);
7    using var stream = response.GetResponseStream();
8    using var reader = new StreamReader(stream);
9    string body = reader.ReadToEnd();
10    Console.WriteLine(body);
11}, null);

This callback-based pattern is harder to read and maintain. Use async/await with GetResponseAsync() instead.

csharp
1using System.Net.Http;
2using System.Text;
3using System.Text.Json;
4
5// Create once and reuse — HttpClient is designed for reuse
6private static readonly HttpClient _client = new HttpClient
7{
8    Timeout = TimeSpan.FromSeconds(10)
9};
10
11// GET
12public static async Task<string> GetAsync(string url)
13{
14    var response = await _client.GetAsync(url);
15    response.EnsureSuccessStatusCode();
16    return await response.Content.ReadAsStringAsync();
17}
18
19// POST with JSON
20public static async Task<string> PostJsonAsync(string url, object data)
21{
22    var json = JsonSerializer.Serialize(data);
23    var content = new StringContent(json, Encoding.UTF8, "application/json");
24    var response = await _client.PostAsync(url, content);
25    response.EnsureSuccessStatusCode();
26    return await response.Content.ReadAsStringAsync();
27}
28
29// With cancellation token
30public static async Task<string> GetWithCancellationAsync(
31    string url, CancellationToken token)
32{
33    var response = await _client.GetAsync(url, token);
34    response.EnsureSuccessStatusCode();
35    return await response.Content.ReadAsStringAsync(token);
36}

HttpClient with IHttpClientFactory (.NET Core+)

csharp
1// In Startup.cs or Program.cs
2builder.Services.AddHttpClient("api", client =>
3{
4    client.BaseAddress = new Uri("https://api.example.com/");
5    client.Timeout = TimeSpan.FromSeconds(30);
6    client.DefaultRequestHeaders.Add("Accept", "application/json");
7});
8
9// In a service class
10public class ApiService
11{
12    private readonly HttpClient _client;
13
14    public ApiService(IHttpClientFactory factory)
15    {
16        _client = factory.CreateClient("api");
17    }
18
19    public async Task<string> GetDataAsync()
20    {
21        var response = await _client.GetAsync("/data");
22        response.EnsureSuccessStatusCode();
23        return await response.Content.ReadAsStringAsync();
24    }
25}

IHttpClientFactory manages HttpMessageHandler lifetimes, preventing socket exhaustion in long-running applications.

Common Pitfalls

  • Creating a new HttpClient per request: HttpClient is designed for reuse. Creating one per request causes socket exhaustion (SocketException) under load because disposed HttpClient instances leave sockets in TIME_WAIT state. Use a static instance or IHttpClientFactory.
  • Forgetting that HttpWebRequest throws on non-2xx responses: Unlike HttpClient, which returns the response regardless of status code, HttpWebRequest.GetResponseAsync() throws WebException for 4xx and 5xx responses. You must catch WebException and read the error response from ex.Response.
  • Blocking on async calls with .Result or .Wait(): Calling GetResponseAsync().Result on a UI thread or ASP.NET synchronization context causes deadlocks. Always use await instead of .Result.
  • Not disposing responses and streams: Both HttpWebResponse and response streams are IDisposable. Failing to dispose them leaks connections. Use using statements for all disposable objects.
  • Ignoring HttpWebRequest.Timeout limitations with async: The Timeout property on HttpWebRequest does not apply to async operations (GetResponseAsync()). For async timeouts, wrap the call in Task.WhenAny with Task.Delay or use a CancellationTokenSource with a timeout.

Summary

  • Use GetResponseAsync() and GetRequestStreamAsync() for async HttpWebRequest operations
  • Prefer HttpClient over HttpWebRequest for all new .NET code — it is simpler and more robust
  • Use IHttpClientFactory in ASP.NET Core to manage client lifetimes and prevent socket exhaustion
  • Never block on async calls with .Result or .Wait() — always use await
  • Handle WebException when using HttpWebRequest, as it throws on non-2xx status codes
  • Always dispose responses and streams with using statements

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.