HttpClient and RestSharp are HTTP Client libraries that we can use to consume APIs. Working within the domain of Web Development, we will find ourselves in a situation where we need to consume external APIs. Both HttpClient and RestSharp are tools for implementing communication between APIs.
Let’s start.
What is HttpClient?
HttpClient is a class available within the .NET environment. We use the HttpClient class to send HTTP requests and receive HTTP responses based on the URI. HttpClient can process multiple concurrent requests.
What is RestSharp?
RestSharp is an open-source HTTP Client library that we can use to consume APIs. Based on that, we can install it using NuGet Package Manager. Although RestSharp can call any API using the HTTP protocol, the purpose of RestSharp is to consume the REST APIs. RestSharp supports both synchronous and asynchronous requests.
HttpClient vs RestSharp Benchmark
Now, let’s start by comparing HttpClient and RestSharp using HTTP methods to inspect time performance and memory usage for each tool. Firstly, we will use the BenchmarkDotNet library to get the benchmark results. Secondly, we will use JSONPlaceholder instead of creating a new API from scratch. JSONPlaceholder is a free API that we can use for testing purposes.
The time performance is a valuable factor in comparing HttpClient and RestSharp when consuming APIs. As a result, we can conclude how fast we can execute the identical requests using each tool. In this example, we will display the numbers in milliseconds for each request. We will use GET, POST, PUT and DELETE operations.
First, let’s create a TodoBenchmark
class where we will have all of our methods:
[MemoryDiagnoser] public class TodoBenchmark { private static readonly HttpClient httpClient = new HttpClient(); private static readonly RestClient restClient = new RestClient("https://jsonplaceholder.typicode.com/"); public TodoBenchmark() { httpClient.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/"); } }
In this class, we initialize two static objects. The first one is httpClient
, which we will use to consume the API using the HttpClient tool. We call the second one restClient
for consuming API calls with RestSharp. In both cases, we set the base URI, which is the same for each tool. We use the MemoryDiagnoser
class annotation to get memory allocation results for each API call.
Before testing, we will create a Todo
class to use when calling the API:
public class Todo { public int Id { get; set; } public int UserId { get; set; } public string? Title { get; set; } public bool Completed { get; set; } }
GET Method Memory and Time Performance
To show how we can measure the time and memory performance of the GET request, we are going to add one method for each tool:
[Benchmark] public async Task<List<Todo>?> GetAllTodos_HttpClient() { var response = await httpClient.GetAsync("todos"); var todos = await response.Content.ReadFromJsonAsync<List<Todo>>(); return todos; } [Benchmark] public async Task<List<Todo>?> GetAllTodos_RestSharp() { var request = new RestRequest("todos"); var todos = await restClient.GetAsync<List<Todo>>(request); return todos; }
We create the GetAllTodos_HttpClient
and GetAllTodos_RestSharp
methods and place a Benchmark
annotation on each method so we can get results later using the BenchmarkDotNet library. After setting the base URI in the previous step, we add a relative path in each example to retrieve the list of Todo
objects. After that, we execute an API call and deserialize the return value into a list of Todo
objects called todos
.
In the GetAllTodos_HttpClient
method, we use the GetAsync
and ReadFromJsonAsync
methods to get the response and deserialize the data. There is another approach for fetching and deserializing data with HttpClient. We can use the GetFromJsonAsync
generic method to get the response and deserialize the data in one line. However, our first approach turned out to be slightly faster, though not noticeably so. Based on that, if performance is not extremely important, we can use the second approach to reduce lines of code.
Lastly, let’s execute the benchmark in the main program with the command:
BenchmarkRunner.Run<TodoBenchmark>();
Then, we run the project in the release configuration dotnet run -c Release
, and get the results:
| Method | Mean | Error | StdDev | Median | Allocated | |----------------------- |---------:|---------:|---------:|---------:|----------:| | GetAllTodos_HttpClient | 53.70 ms | 1.556 ms | 4.465 ms | 52.12 ms | 94 KB | | GetAllTodos_RestSharp | 44.56 ms | 2.171 ms | 6.264 ms | 43.42 ms | 346 KB |
The results are similar when comparing the mean time required for call executions. On the other hand, when comparing memory allocation, we can attribute a significant advantage to HttpClient.
POST Method Memory and Time Performance
In this section, we will do the same steps for the POST request to create a Todo
object with HttpClient and RestSharp libraries.
First, we will create a private helper method for getting a new Todo
object:
private static Todo GetTodoForCreation() { return new Todo { Id = 0, UserId = 1, Completed = false, Title = "Wake Up!" }; }
Then, we will add CreateTodo_HttpClient
and CreateTodo_RestSharp
methods:
[Benchmark] public async Task<Todo?> CreateTodo_HttpClient() { var todoForCreation = GetTodoForCreation(); var response = await httpClient.PostAsJsonAsync("todos", todoForCreation); var createdTodo = await response.Content.ReadFromJsonAsync<Todo>(); return createdTodo; } [Benchmark] public async Task<Todo?> CreateTodo_RestSharp() { var todoForCreation = GetTodoForCreation(); var request = new RestRequest("todos").AddJsonBody(todoForCreation); var createdTodo = await restClient.PostAsync<Todo>(request); return createdTodo; }
Nothing new here. Initially, we initialize the todoForCreation
variable using a previously implemented method. Then, we execute the API calls with both libraries. We call the API with the PostAsync
method for RestSharp and the PostAsJsonAsync
method for HttpClient. We use the PostAsJsonAsync
method to serialize and send the request with one command. Once that is done, we deserialize the response into the Todo
object to return.
Lastly, we run the benchmark again to get the results:
| Method | Mean | Error | StdDev | Allocated | |---------------------- |---------:|--------:|--------:|----------:| | CreateTodo_HttpClient | 251.9 ms | 4.88 ms | 5.42 ms | 43 KB | | CreateTodo_RestSharp | 253.1 ms | 4.99 ms | 6.32 ms | 109 KB |
In this case, execution times are almost identical within two milliseconds, while RestSharp still takes significantly more memory.
PUT Method Memory and Time Performance
Next in line is the PUT method. In this case, we will update the Todo
object using HttpClient and RestSharp.
Again, we will create a static method for getting a Todo
object used for the update operation:
private static Todo GetTodoForUpdate() { return new Todo { Id = 2, UserId = 2, Completed = false, Title = "Have Lunch!" }; }
In the next step, let’s implement two methods for updating the Todo
object through the API:
[Benchmark] public async Task<Todo?> UpdateTodo_HttpClient() { var todoForUpdate = GetTodoForUpdate(); var serializedTodo = JsonSerializer.Serialize(todoForUpdate); var requestContent = new StringContent(serializedTodo, Encoding.UTF8, "application/json"); var response = await httpClient.PutAsync($"todos/{todoForUpdate.Id}", requestContent); var updatedTodo = await response.Content.ReadFromJsonAsync<Todo>(); return updatedTodo; } [Benchmark] public async Task<Todo?> UpdateTodo_RestSharp() { var todoForUpdate = GetTodoForUpdate(); var request = new RestRequest($"todos/{todoForUpdate.Id}").AddJsonBody(todoForUpdate); var updatedTodo = await restClient.PutAsync<Todo>(request); return updatedTodo; }
Like in the POST methods, we first retrieve the Todo
object for the update operation and serialize it in JSON format. After that, we call the PutAsync
method and deserialize the return value into the updatedTodo
variable.
Let’s have a look at the output result:
| Method | Mean | Error | StdDev | Allocated | |---------------------- |---------:|--------:|---------:|----------:| | UpdateTodo_HttpClient | 254.0 ms | 3.93 ms | 4.82 ms | 10 KB | | UpdateTodo_RestSharp | 250.1 ms | 4.95 ms | 10.65 ms | 93 KB |
We can see very similar execution times to POST requests we compared before, with a slightly better time for RestSharp in this case. As usual, HttpClient is better at memory allocation.
DELETE Method Memory and Time Performance
Let’s add the last methods to show how we can test a DELETE request performance:
[Benchmark] public async Task DeleteTodo_HttpClient() { await httpClient.DeleteAsync("todos/1"); } [Benchmark] public async Task DeleteTodo_RestSharp() { var request = new RestRequest("todos/1"); await restClient.DeleteAsync(request); }
Both methods are simple when we compare them with the POST and PUT methods from the previous sections. Within the DELETE call, we don’t need to send a JSON object and return deserialized value.
After that, we can check our results:
| Method | Mean | Error | StdDev | Allocated | |---------------------- |---------:|--------:|--------:|----------:| | UpdateTodo_HttpClient | 254.1 ms | 4.78 ms | 8.24 ms | 10 KB | | UpdateTodo_RestSharp | 258.8 ms | 4.87 ms | 8.65 ms | 85 KB |
At last, we can see that the execution time is almost identical on the DELETE operation for both HttpClient and RestSharp.
Benchmark Summary
Among the methods, we notice a pattern that implies that HttpClient and RestSharp are almost identical regarding the execution time. However, when it comes to memory allocation, HttpClient is significantly better.
HttpClient and RestSharp Behind the Scenes
Now, let’s look at the differences between HttpClient and RestSharp in the background. The most obvious difference is that HttpClient is already built into the framework. On the contrary, RestSharp is a third-party library that we need to install before using it.
HttpClient Background
An HttpClient object acts like a session that we can use to send HTTP requests and receive HTTP responses. We can describe it as a collection of settings applied to all of its requests. Furthermore, the HttpClient instance uses its connection pool for request isolation. As opposed to the RestSharp, HttpClient supports only async methods.
HttpClient implements an IDisposable interface. However, we shouldn’t explicitly dispose of it in most cases. When we dispose of the HttpClient, we close the underlying connection. With that, we need to re-open the connection for each subsequent request. Re-opening the connection is a costly process that can cause socket exhaustion. We can prevent this issue with HttpClientFactory to create and manage HttpClient instances.
Since .NET Core 2.1, HttpClient uses SocketsHttpHandler. Before that, HttpClientHandler was used in .NET Framework, .NET Core 2.0, and earlier. With that, SocketsHttpHandler introduced several advantages:
- Performance improvement
- Consistent behavior throughout all .NET platforms
RestSharp Background
Similarly, RestSharp is not overly complicated to use when consuming external APIs. It is a library that is easy to use across multiple platforms. We can say that RestSharp is probably the most popular HTTP client library for .NET among third-party libraries.
Unlike HttpClient, RestSharp supports synchronous and asynchronous methods. Another great feature that RestSharp offers is automatic JSON, XML, and custom serialization and deserialization. A breaking change inside the latest version is that RestSharp stops using the legacy HttpWebRequest class and uses HttpClient instead.
If we dive into the RestSharp RestClient
class implementation, we can find HttpClient
private getter:
private HttpClient HttpClient { get; }
That means that RestSharp is a wrapper around the HttpClient, which explains why RestSharp needs to allocate more memory when performing the API calls. Finally, in the release v107, SimpleJson
class is completely removed from the code base. Instead, RestSharp uses JsonSerializer
, the default serializer in .NET Core. Since RestSharp uses the HttpClient, we should consider similar issues regarding the RestClient
instantiation. With the continuous creation of RestClient
, we will get hanging connections and eventually the socket exhaustion.
With the new version implemented, RestSharp presumably solves a couple of issues:
- Socket exhaustion
- Hanging connections
SimpleJson
serialization issues- HTTP/2 support
HttpClient vs RestSharp Pros and Cons
Finally, let’s have a look at the pros and cons of HttpClient and RestSharp.
HttpClient Pros and Cons
First, we will analyze the HttpClient library:
HttpClient Pros | HttpClient Cons |
---|---|
Already built into the framework | Arguably harder to use when comparing it to the RestSharp |
Better handling of memory allocation | The need for a custom implementation using HttpClientFactory class |
A proven way to consume APIs in .NET | Lack of automatic serialization and deserialization |
Significant improvement in performance and consistency after the introduction of the SocketsHttpHandler class | Lack of support for synchronous API calls |
RestSharp Pros and Cons
Again, let’s have a look at the pros and cons of using RestSharp:
RestSharp Pros | RestSharp Cons |
---|---|
Like most libraries, it is easier to use because the hard work is already done with some frequent issues eliminated | Additional bloat to a project when adding a third-party library |
Similar time performance with HttpClient | Poor memory allocation optimization |
Automatic serialization and deserialization with JsonSerializer | It is based on the HttpClient itself, acting as a wrapper |
Support of both synchronous and asynchronous API calls |
Conclusion
In this article, we’ve learned quite a lot about HttpClient and RestSharp. We’ve inspected memory and time performance for both libraries. Our performance analysis shows that both libraries are similar in time performance. However, HttpClient performs better regarding memory allocation.
The main conclusion is that one is not better than the other, and we shouldn’t compare them since RestSharp is a wrapper around HttpClient. The decision between using one of the two tools depends on the use case and the situation. Not only do they perform similarly, but any difference is also much smaller than the wait on the network, so we cannot go wrong.