Sending HTTP requests might seem like one of the most trivial tasks while developing applications — it’s something that you find yourself doing quite often, any programming language can do it fairly easy, and it doesn’t seem to be different than calling a method from another class. What tends to be neglected is the performance aspect that comes with sending these requests — something that can be hard to spot during the development & testing phases, but that can hurt the live application.
In this article, I will present some measures that can be taken to boost your ASP.NET Core application’s performance when it comes to communicating with (multiple) other APIs. You can find the code from this article on GitHub as a class library, and you can use it in any of your projects.
Using a single HttpClient object
One of the main sources of trouble regarding HTTP requests is the fact that some developers tend to create a new HttpClient object each time they want a new request to be sent. Taking a look at what the HttpClient class has to offer makes it clear that it is intended to be used for multiple requests — it contains, for example, the DefaultRequestHeaders, which are most likely the same for each request you send to the same API.
In an ASP.NET scenario where you need to send HTTP requests in multiple actions, something that you might be tempted to do is to instantiate the HttpClient in the constructor, and then use it to send requests from whichever action you need to, as in the following example:
While this seems like a normal approach, it runs into a simple problem: ASP.NET controllers are instantiated upon each request. The outcome is the same as if you were creating the HttpClient object in each action. So the solution is to either make the HttpClient object static or to separate it from the controllers.
Delegating the responsibility to another class
A more elegant approach is to delegate the responsibility of sending requests to another class, along with the responsibility of creating the HttpClient object. This clears up the code and lets you do operations such as serialization/deserialization needed in the process of request sending in the same class. In this article, I will call this class RequestSender. Now, you have two options regarding how you handle the uniqueness of the HttpClient object. You can either have it as a static field in the RequestSender class, which will result in the same instance of HttpClient being used to send all the requests (methods such as GetAsync, PostAsync etc. are thread-safe, so you won’t have to worry about using a static object), or you can inject a RequestSender object from the Startup class, which will result in the same instance of RequestSender being used across your application.
I will present the first method in this article, since my next example derives from it, but you can read more about dependency injection in ASP.NET here.
The constructor of this class only instantiates the HttpClient field if it hasn’t been previously instantiated (similarly to the Singleton pattern). You can also use it to set any particular options for the HttpClient, such as request headers. I also included an example of a GET method, which reads and returns the content of the response as a string. You can use the same principle to develop POST, PUT or DELETE methods.
Handling multiple APIs
Another issue rises when you intend to send request to multiple APIs from the same application. In this case, the dependency injection approach that I mentioned earlier would not be as easy to implement (and any implementation would be quite awkward).
The goal here is to keep the number of HttpClient objects to a minimum. Naturally, a different object is needed for each API, so we need to keep track of them and choose the right when a new request is being sent. This will result in a bit of a structural change to the RequestSender class, but its API and usage will remain unchanged.
In this version of the RequestSender, the class acts as a sort of wrapper for the HttpClient. Additionally, all the HttpClient object are kept in a Dictionary, where the key is represented by their base URI. Every time a new Requestsender object is created, the constructor checks whether an HttpClient object with that base address already exists, and assigns it to this object’s field. Otherwise, it creates a new object and adds it to the dictionary. Apart from this, the functionality of the class remains the same.
Additional functionality for the RequestSender class
As I previously mentioned, separating the request sending functionality from the controllers lets you add (de)serialization logic in this class. This helps you make your code much more readable and avoids the code duplication that comes with sending requests in each action. You can develop several methods for different use cases, such as getting/sending the response in a certain format, as you can see in the following example:
The main idea of this article is that you should be careful about the way you do network communication. It might seem like a pretty innocent thing to do, but it might come with a few surprises if you don’t think twice about the code you write. Research how your classes work and research how your environment works so that you can draw educated conclusions regarding the performance of your application.