Marcin Jahn | Dev Notebook
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects

HttpClient

HttpClient uses implementations of HttpMessageHandler to do its job. The default handler is SocketsHttpHandler.

There may be a chain of handlers (delegating handler pattern). Messages go down throug handlers and responses go up to the HttpClient. Any handler can decide not to pass message down and return a response earlier (i.e. caching).

HttpClient configuration

_httpClient.BaseAddress = "https://some-api.com";
_httpClient.Timeout = new TimeSpan(0, 0, 30);
//Headers used by ALL requests:
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);

Specifying Accept header is crucial for the client to be reliable. Server may default to other formats, like XML, and our app cannot just expect to get JSON. It needs to explicitly state that in an Accept header. Even if some endpoint returns empty content it is still a good practice to set Accept header, in case when an error is returned.

HttpRequestMessage

Requests can be sent with “shortcut” methods of HttpClient like GetAsync, but if more customization is required (like custom headers) an HttpRequestMessage should be used.

Headers

request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Content

The base type of content is HttpContent. It is abstract. Derived types:

  • StringContent
  • ObjectContent
  • ByteArrayContent
  • StreamContent
  • …

Example:

request.Content = new StringContent("abc");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

Streams

Streams will help with memory usage. However, time might not always improve. Still, memory usage is often of higher priority.

Responses

It is recommended to ALWAYS use streams when reading data. When reading the response’s content, we can use ReadAsStringAsync(). However, it creates unnecessary memory allocation for the whole content string, which we need only for deserialization in most cases. It’s a better idea to use ReadAsStreamAsync() and deserialize the data from the stream directly.

//using will dispose the stream. When not using streams, it doesn't do anything
using var response = await _httpClient.GetAsync("url");
var stream = await response.Content.ReadAsStreamAsync();
var data = JsonSerializer.DeserializeAsync<MyModel>(stream);

There is additional perfmance improvement. By default, HttpClient will return the response only after the whole content is fetched from the server. We can instead get the stream while it’s still being fetched:

var request new HttpRequestMessage(...);
//return as soon as the response headers arrive, instead of the whole content
using var response = await _httpClient.SendAsync(request, HttpCompletionOptions.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
var data = JsonSerializer.DeserializeAsync<MyModel>(stream);

Sending data

StreamContent can be used to send body from a stream. Us it when posting large amounts of data.

Compression

It makes sense to use compression when getting data from servers (gzip, deflate). HttpClient has built-in support for it, so that the data gets decompressed automatically when it is received.

Custom HttpMessageHandlers

To implement delegating handlers pattern, we should create a class inheriting from DelegatingHandler. Example

public class RetryPolicyDelegatingHandler : DelegatingHandler
{
private readonly int _maxRetries = 3;
public RetryPolicyDelegatingHandler(int maxRetries)
: base()
{
_maxRetries = maxRetries;
}
public RetryPolicyDelegatingHandler(HttpMessageHandler innerHandler, int maxRetries)
:base(innerHandler)
{
_maxRetries = maxRetries;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
for (var i =0; i < _maxRetries; i++)
{
response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessfulStatusCode)
{
return response;
}
}
return response;
}
}

Registration of custom handler:

services.AddHttpClient<MyService>()
.AddHttpMessageHandler(handler => new RetryPolicyDelegatingHandler(2));

We added just our custom handler, but there is always a default HttpClientHandler in the end of the pipeline. If we want, we can configure it additionally:

services.AddHttpClient<MyService>()
.AddHttpMessageHandler(handler => new RetryPolicyDelegatingHandler(2))
.ConfigurePrimaryHttpMessageHandler(handler => {
new HttpClientHandler()
{
AutomaticDecompression = System.Net.DecompressionMethods.GZip
}
});

Now, first our custom handler will be invoked, and then the primary one with custom configuration.

How Async Works  →
© 2023 Marcin Jahn | Dev Notebook | All Rights Reserved. | Built with Astro.