In this post, I will introduce a new library, called System.Net.Http.Json, which has been added to .NET in the last few days. We’ll look at the problem which this library helps to solve. We’ll then explore some examples of how to use it in your code today.
WARNING: This library is currently available as a pre-release package from NuGet. While I expect the public API surface to remain relatively stable, some details may change based on feedback. It’s always worth checking for the latest available package if you begin using this in your applications.
Options Before System.Net.Http.Json
JSON is a widespread and popular serialisation format for data sent to and from modern web APIs. I often find myself making external HTTP calls using HttpClient to endpoints where I expect JSON content in the response. To handle the response manually, I will typically validate the status code on the response, check the content is not null and then attempt to deserialised from the content Stream when the content type is “application/json”.
Using Newtonsoft.Json, the code would look something like this:
It’s not a tremendous amount of code, but it’s something that we need to write whenever we receive JSON data from an external service. In a microservices environment, this may be in multiple places, across many individual services.
It’s also possible and tempting to access the JSON as a string using GetStringAsync
on the HttpClient or ReadAsStringAsync
on the HttpContent. Strings can be deserialised directly by both Newtonsoft.Json and System.Text.Json. The issue with this approach is that the string allocation may be quite significant as it represents the entire JSON payload. This is a wasted allocation since the data already exists as bytes in a Stream which, as I’ve shown above, can be used for deserialization.
By using the stream, it’s also possible to further improve performance, as I wrote about in my post, Using HttpCompletionOption to Improve HttpClient Performance.
If you’ve worked with HttpClient in the past and dealt with endpoints which return JSON, you may have utilised the Microsoft.AspNet.WebApi.Client library. I’ve used this in the past as it provides useful extension methods to support efficient JSON deserialization from the content stream on a HttpResponseMessage. This library depends on Newtonsoft.Json and uses its stream-based APIs to support efficient deserialization of data. This is a handy library which I’ve used for a few years.
With this library included in a project, the above code can be reduced.
More recently in .NET, the team introduced a built-in JSON library, System.Text.Json. This library was built from the ground up to make use of the latest .NET performance features such as Span<T>. For low overhead, rapid serialisation and deserialization, this is now my preferred library. It’s included as part of the BCL (Base Class Library) since .NET Core 3.0, so you do not need to reference an additional package to use the library.
Today, I tend to prefer the use of System.Text.Json, mainly when working with a Stream. The code is a little more concise when compared to the first Newtonsoft.Json example above.
Since it reduces the number of third-party dependencies required in my project and should be more performant, I prefer System.Text.Json. However, while this code is now pretty straightforward, there’s still some boilerplate which I am required to write. From a concise code perspective, the best option so far is to use the Microsoft.AspNet.WebApi.Client extension methods.
Introducing System.Net.Http.Json
I’ve been watching the progress of this new library since February when the design and issue first appeared on GitHub. These document the requirements and the proposed API surface. A summary of the problem statement and objectives is included in the design document.
Serializing and deserializing JSON payloads from the network is a very common operation for clients, especially in the upcoming Blazor environment. Right now, sending a JSON payload to the server requires multiple lines of code, which will be a major speed bump for those customers. We’d like to add extension methods on top of HttpClient that allows doing those operations with a single method call.
You can read the full requirements in the design, but a few highlights are that the team required the library to work on .NET Standard 2.1, but 2.0 would be preferred. The team wanted to “Build a pit-of-success for HttpClient and System.Text.Json”. The initial release target is to ship this as a standalone NuGet package at Build, alongside Blazor, which will utilise the APIs.
The initial work has now been completed by David Cantu at Microsoft and has been merged, ready to the upcoming Blazor release. It is expected to be included as part of the BCL in an upcoming .NET 5 preview. So why am I mentioning it now?
Well, you can grab the preview package today from NuGet and begin using it in your .NET Standard 2.0 projects. I’ve already pulled it down, and in the remainder of this blog post, I’ll explore a few of the main APIs and usage scenarios which it supports.
Sending and Receiving JSON Content with HttpClient in .NET
I’ve put together some basic sample code which I’ve uploaded to a GitHub repository. I’ll share most of the code below as snippets.
This first step is to add the package to your project. You can achieve this using the NuGet Package Manager or via a command line with the following command.
dotnet add package System.Net.Http.Json --version 3.2.0-preview3.20175.8
NOTE: A newer version may be available by the time you are reading this post!
In your classes, you can add a using directive to gain access to the extension methods from the library.
using System.Net.Http.Json;
Requesting JSON via HttpClient
Let’s first look an extension method on HttpClient, which is pretty straightforward.
On line 5, we call GetFromJsonAsync passing a type argument of the Type we expect to deserialize the JSON response into. The method accepts the Uri to request data from. And that’s all we need! In a single line, we have issued an HTTP Get request to an endpoint and deserialized the content into a User instance. That’s quite a simplification on the earlier code that I showed.
The sample above is made more verbose by the exception handling code. Various exceptions may be thrown under different conditions. Here I handle the most likely exceptions, each in their own catch block. This could be simplified if you only require more generic logging of the operation failing.
The library takes care of most of the earlier requirements. It will ensure that the status code is a success using EnsureSuccessStatusCode. This will cause a HttpRequestException to be thrown when the response is not in the 200-299 status code range.
The library code will also check for the presence of a valid media type such as “application/json”. If the media type is missing or invalid, a NotSupportedException will be thrown. The check here is more complete than in my manual sample code. If the media type is anything other than “application/json” some Span<T> based parsing of the value will take place. This allows for a media types confirming to this format “application/<something>+json” to be considered valid.
This format is in use today; an example of which can be found in the problem details standard. RFC7159 defines a way to carry machine-readable details of errors in an HTTP response and used the media type “application/problem+json”. My manual code would not have matched this, but the System.Net.Http.Json library takes care of this for us.
Internally, the ResponseHeadersRead HttpCompletionOption is used for efficiency. I describe how this works in my recent post. The library code takes care of proper disposal of the HttpResponseMessage, which is required when this option is used.
Transcoding
One final implementation detail of this library is that it includes support for transcoding the data if it is not returned as UTF-8. UTF-8 should be the standard in a vast majority of cases. However, if the charset included with the content-type header identifies a different encoding, a TranscodingStream will be used to try and encode the bytes to UTF-8 before deserialisation takes place.
Handling JSON from HttpContent
The above code is perfect if and very straightforward when all of the defaults it applies are suitable for your application. In some cases, you may want to send custom headers on the request. Or perhaps you want to inspect the response headers before deserialisation. This is also possible using extensions from System.Net.Http.Json.
In the preceding code, we have responsibility for creating and sending the HttpRequestMessage. In this sample, we’re able to customise the HttpRequestMessage to include an additional header. We can now use the SendAsync method on HttpClient to issue the request. Having confirmed that the response returned a success status code, we call the ReadFromJsonAsync extension method on the HttpContent.
We can still handle the NotSupportedException and JsonException which may be thrown if the content is not valid for JSON deserialisation.
Posting JSON Data
The final sample we’ll look at concerns sending JSON data as part of a POST request. Let’s look at two approaches to achieve this.
This first method uses the PostAsJsonAsync extension method on the HttpClient. It accepts the URI to POST the data to, and an object which we expect to be serialised to JSON. Internally it will build a HttpRequestMessage and serialise the object to the content stream.
In situations where you are manually creating a HttpRequestMessage, perhaps to include custom headers, you may create JsonContent directly.
In the above code, we use the Create factory method to create a JsonContent instance, passing in an object to be serialised. JsonContent is a new type, added by System.Net.Http.Json, which subclasses HttpContent. Internally it handles object serialisation using System.Text.Json.
Summary
In this post, we reviewed some of the traditional approaches that could be used to deserialise content from a HttpResponseMessage into an object. We saw that the when manually calling APIs to parse the JSON, it required us to consider things like first ensuring the response was a success, and that the response is an expected media type.
We looked at the ReadAsAsync method provided by the Microsoft.AspNet.WebApi.Client library. Internally the library uses Newtonsoft.Json for efficient, stream-based deserialisation.
We concluded by introducing the new System.Net.Http.Json library, which added supports for JSON content, serialised and deserialised using System.Text.Json. This avoids a third-party dependency on Newtonsoft.Json and should be more performant in many cases, due to its Span<T> optimisations.
We then used various extension methods provided by System.Net.Http.Json to send and receive JSON data via HttpClient. In common cases, this can reduce your code down to only a few lines, and ensures consistent checking of things like valid media types.
As a reminder, you can grab the code for these samples from my GitHub repository.
Have you enjoyed this post and found it useful? If so, please consider supporting me: