!! RefactorHttpClientWithAspNetCore
!! RefactorHttpClientWithAspNetCore
•5
Requirements ............................................................................................................. •8
Code Repository......................................................................................................... •8
WebRequest..............................................................................................................•11
HttpClient.................................................................................................................. •12
HttpClientFactory...................................................................................................... •12
Review...................................................................................................................... •15
Review...................................................................................................................... •18
Review...................................................................................................................... •21
Review...................................................................................................................... •27
Review..................................................................................................................... •35
Review..................................................................................................................... •38
Conclusion.....................................................................................................................•45
4
Introduction
This was the question I asked myself back in 1994 when I first experiencing the Internet.
I was completely blown away by how the network was connecting across vast
distances.
As we all know now, it was using TCP/IP, but I didn't realize it until I dug a little deeper
into how everything was connected (excuse the pun). My research was using books
back then, mind you (read my lips...No Internet).
You can't look in any direction without seeing a connected device using TCP/IP. It is the
life-blood flowing through the network’s “arteries.”
The world as we know it is completely comprised of TCP/IP connections and the world
is now truly "the network as the operating system."
With the vast amount of data on the Internet, users only see the tip of the iceberg: the
website. We don’t see what’s happening under the covers; providing access to websites
through APIs
To make the world your oyster as a developer, you need to know where to look and how
to access the data.
The goal of this book is to show you how to use [Link] Core to harness the power
of the HttpClient class to make a REST-based client API calls.
What kind of book is this?
I’ve been writing for over 10 years on my blog ([Link] and felt it
was time to dig into my first venture as a book writer.
Depending on how this book is received, I already have a number of topics I want to
cover with future books.
I’m a technologist and developer at heart and, while I’m not a publisher by any stretch, I
am a blogger. I love to learn new techniques and turn around and teach new and
improved techniques through my writing.
I want this to be a different kind of book for developers. It should break the tradition of
what a technical book should look like.
This ebook:
• Is not meant to be an over-400-page book. I feel I would lose a lot of people with
so many gigabytes.
• Should expand on ebooks in a different way and see this small book as a series
of blog posts.
As I’m starting to move forward with this endeavor, I wanted to create a brand for these
ebooks.
Pocket Projects are ebooks longer than a blog post series and smaller than
a full-blown ebook. They will also include repositories.
These books are meant to be small in nature, but provide big value.
In the first part, we’ll talk about the history of how server-side API calls were made and
how to use (and not use) an HttpClient.
If you are experienced, you may want to skip the history and improper usage and pick
up where you are “Creating a Reusable HttpClient” on page 16.
In the second half, we refactor the code into HttpClient components where we reduce
the amount of time to writing future APIs to as little as 10 minutes.
In the end, you’ll be able to build easier, maintainable, and quicker server-side APIs.
Most developers are looking to solve a problem. They want to do X and they want to
solve it as fast as possible.
While it is hard to update the paper-bound technical books, it’s far more easier to
update a simple ebook or find it on [Link] (am I right?).
It may not be the entire wealth of knowledge on one topic in one book, but it will be
considered a place for reference and code examples to point developers in the right
direction.
Pocket Projects will allow the developer to open the book to a subtopic and examine the
concepts found in the book.
Requirements
The technology used in this book is specifically [Link] Core 3.1, but does not mean
other versions (including .NET Framework) could not use these techniques in older
versions.
The core concept is to use HttpClient for consuming web APIs. As long as the HttpClient
is available in .NET, a majority of these techniques will work.
▪ JetBrains’ Rider
With [Link] Core, you have the ability to run applications at the command-line so the
preferred IDE for you to use is...
While Visual Studio 2017/2019 and Code (which is free) are Microsoft products,
[Link] is available for most editors.
Code Repository
The finished code is located on Github.
[Link]
Thank You (with feedback)
With all of that out of the way, I want to say thank you for trying out the book and
allowing me to take a “leap of faith” in writing my first book.
This will be my first step towards writing a collection of Pocket Projects if this book takes
off.
I also want to let my readers know they can reach out to me with comments, complaints
or future additions at any time regarding this book.
With the Internet taking off in the mid 90’s, most developers (including myself) didn’t
understand too much about how everything was connecting except we could pull web
pages using something called an HTTP protocol with a port of 80.
While server-side API calls were available, they weren’t always straight-forward and
required some knowledge of how to retrieve data from another server.
With that said, we need to take a step back and look at how server-side client API calls
were made “back in the old days.”
Of course, [Link] didn’t exist yet. Microsoft developers were using Classic ASP.
Classic ASP
By the turn of the century, developers started learning how to build web services.
[Link] [Link]
Set xml = Nothing
%>
(ref: [Link]
We would make a web call to a web page using an ActiveX component and display it in
the web browser.
WebRequest
Then [Link] came along with true object-oriented programming concepts with C#
and [Link].
namespace [Link]
{
public class WebRequestGetExample
{
public static void Main ()
{
// Create a request for the URL.
WebRequest request = [Link] ("[Link]
[Link]");
// If required by the server, set the credentials.
[Link] = [Link];
// Get the response.
HttpWebResponse response = (HttpWebResponse)[Link]
();
// Display the status.
[Link] ([Link]);
// Get the stream containing content returned by the server.
Stream dataStream = [Link] ();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader (dataStream);
// Read the content.
string responseFromServer = [Link] ();
// Display the content.
[Link] (responseFromServer);
// Cleanup the streams and the response.
[Link] ();
[Link] ();
[Link] ();
}
}
}
If you were a .NET developer, WebRequest was the class to use for API calls and the
only time you saw clean code was on the site holding the documentation. Code back in
the 2000s had a “wild west” feel to it.
Separation of concerns wasn’t even thought of yet and wasn’t even considered as a
good practice.
Be honest. Back then, we all had our big ball of mud to play with.
In [Link], making an API call was definitely easier than Classic ASP, but still lacked
the flexibility of setting headers, authentication, and retrieving specific types of data. It
only knew how to deliver HTML.
HttpClient
With the popularity of .NET rising, WebRequest was getting a little long in the tooth and
required flexibility and a more modular design.
HttpClient was introduced in the .NET Framework 4.5 and was built to replace
WebRequest in it’s design.
With the HttpClient, developers were able to call web APIs relatively easy with the
HttpWebRequest and HttpWebResponse classes.
The modular aspect of HttpClient provides simple calls with the capability of bolting on
additional functionality like authentication and header manipulation.
While WebRequest is still available in the .NET Framework and .NET Core, Microsoft
recommends using HttpClient instead of WebRequest.
HttpClientFactory
When [Link] Core was released, Microsoft decided to create a better way of making
client-side API calls.
This fix was called HttpClientFactory.
There’s a part of me that still thinks the HttpClientFactory was created based on bad
usage of the HttpClient class, but after further investigation, I was right.
Since introduced in 2012 with .NET Framework, HttpClient has been used in various
ways to make API calls to other websites.
For example, you’ve probably seen how to build a simple API with the code below.
Product product;
HttpResponseMessage response = await [Link]("api/products/1");
if ([Link])
{
product = await [Link]<Product>();
}
return View(product);
}
A Better Approach?
From this point on, most developers decided to wrap the code with a using statement so
the HttpClient would automatically dispose of itself.
14
MediaTypeWithQualityHeaderValue("application/json"));
Product product;
HttpResponseMessage response = await [Link]("api/products/1");
if ([Link])
{
product = await [Link]<Product>();
}
return View(product);
}
}
Remember in the last section when I said how the WebRequest object was created with
a static Create method instead of using the new to create a new object?
When this action is called in an MVC project, we are creating another instance of an
HttpClient object.
With the HttpClient class, this approach causes TCP port exhaustion when continually
active. One of the most talked-about links is the [Link] Monsters example where this
occurred (see Appendix A: Resources)
When using HttpClient, it’s documented where only a single instance of an HttpClient
object should be instantiated throughout the entire application.
If you create multiple instances of an HttpClient in multiple locations, you are hindering
your performance and scaling opportunities for your application.
When creating your HttpClient, it should follow a singleton pattern approach. If the
object doesn’t exist, create one and return it. If it was created and does exist, just return
the instance.
Review
We reviewed some bad approaches developers use when creating HttpClients.
So what is the correct approach and how could we fix this issue to create a single
instance of our HttpClient?
15
Creating a Reusable HttpClient
Applications usually have more than one API call making the code easy to locate and
manage. However, if there are multiple API calls in your application, more than likely,
they are decentralized throughout the application.
This section will take your decentralized API code and turn it into a maintainable and
extendable REST-based code.
So let’s get started with our existing code from above. How would we refactor this code?
To make our HttpClient easier to work with, we need a class to wrap around the
HttpClient object.
public abstract class BaseHttpClient
{
private static HttpClient _client = new HttpClient();
}
This simple class provides a lot of punch and protects us from a number of issues. So
let’s break it down a bit.
We want to create a single class to use only one HttpClient object. Keep in mind, we will
be making multiple calls to a LOT of APIs (I’ll explain later why this is important) and
reusing some of the specific features of an HttpClient.
Speaking of the HttpClient, the next line creates a static instance of the HttpClient. This
allows our HttpClient to always be available in memory.
16
{
private static HttpClient _client = new HttpClient();
BaseAddress and BasePath are absolutely required. BaseAddress would be the domain
([Link]) and the BasePath property would contain full path of the API after the
domain (/api/products/1).
We also create a private method called GetHttpClient() for our own purposes to confirm
we don’t create unnecessary HttpClients on the fly.
Using HttpClientFactory
With the release of [Link] Core, an additional class was introduced to help with
managing HttpClient instances called the HttpClientFactory.
We can easily replace the HttpClient in our code with an HttpClientFactory class making
this singleton even simpler (a simpleton?).
17
Out of the box, [Link] Core uses dependency injection and should always be
implemented in the [Link] in the root of your project.
Lucky for us, all we need to do is add one line to make our code work.
[Link]();
}
With this one line, it does two things for you. It adds the implementation of an HttpClient
to your library and automatically sets up an IHttpClientFactory/HttpClientFactory pair
through constructor injection.
Review
We’ve taken an HttpClient call and isolated it into a reusable API class. Even though it is
a simple class, it gives us a solid foundation moving forward.
18
A Better HttpRequestMessage
There are a number of methods you can use with an HttpClient. You can call the
following on HttpClients:
• DeleteAsync()
• GetAsync()
• PostAsync()
• PutAsync()
• PatchAsync()
• SendAsync()
While all of these are most commonly-used API verbs, the one that should interest you
is the last one, SendAsync().
Creating HttpRequestBuilder
If you’ve ever deifned an HttpRequestMessage, you know it’s a little tedious and
requires the left-to-right assignment for all properties required for our message.
Why not make it like a programmatic sentence? Why not use a Builder design pattern to
create our API message?
A builder design pattern takes a complex object and creates construction methods
making it easier to define and build.
19
private string _baseAddress;
public HttpRequestBuilder()
{
_request = new HttpRequestMessage();
}
return this;
}
20
When we instantiate our builder, we immediately create an internal
HttpRequestMessage.
From this point forward, we expose certain methods to internally build the
HttpRequestMessage, create methods to hide the implementation details of our internal
object (_request), and return the instance of the builder which gives us our fluent
syntax.
In our code example from before, our refactored request now looks like this:
var message = new HttpRequestBuilder("[Link]
.HttpMethod([Link])
.Headers(headers =>
[Link](new MediaTypeWithQualityHeaderValue("application/json")))
.GetHttpMessage();
This returns our constructed message we can pass into our API.
Review
We took the tedious process of performing a left-to-right assignment of an
HttpRequestMessage and refactored it into a builder design pattern.
Next, we need a way to manipulate the URL passed into the HttpRequestBuilder.
21
Url Management
In our last section, we passed in a URL to our HttpRequestBuilder, but there are a
couple of issues with this approach.
While the Uri class is helpful, it needs a little more. We can actually apply the same
builder pattern to a Uri builder.
However, if you’ve done your homework, you know there is a UriBuilder already
available.
Why not wrap the UriBuilder class with an ApiBuilder class so we can manage the Uri at
an API level?
public class ApiBuilder
{
private readonly string _fullUrl;
private UriBuilder _builder;
22
_builder.Port = port;
return this;
}
_builder.Query = [Link]();
23
return this;
}
With this new ApiBuilder class, we can now modify our HttpRequestBuilder to make our
Url management a little easier.
24
public HttpRequestBuilder Headers(Action<HttpRequestHeaders> funcOfHeaders)
{
funcOfHeaders(_request.Headers);
return this;
}
return this;
}
return this;
}
return this;
}
return this;
}
return this;
}
return this;
}
Now we can dynamically add parameters to our APIs without worrying about breaking
our Url.
This tells us we are making an API call to the [Link] website, setting the path to
/api/products/1, making it an HTTP GET, and returning back Json.
26
Review
We created an ApiBuilder to simplify the management of our Urls when building our
HttpRequestMessage.
Now that we have our HttpRequestMessage, we need to send the actual request.
27
Sending the Request
If you remember, the simplest way to send this message is to use the SendAsync() and
let the message define the functionality of the HttpClient request.
However, every API call doesn’t return the same class type every single time. We need
a way to tell the API we are expecting a specific class type so we can work with the
results.
This is where generics come into play. Generics allow various classes to be used from
an abstract level.
The caller specifies the type to be returned from the API. The API makes the call and
creates an instance of that type and returns it from the method. The ability to use
generics in your code makes it easier to reuse components.
It’s much easier to show what I mean in our BaseHttpClientFactory class than explain
(changes in bold).
public abstract class BaseHttpClientWithFactory
{
private readonly IHttpClientFactory _factory;
T result = null;
28
[Link]();
if ([Link])
{
result = await [Link]<T>(GetFormatters());
}
return result;
}
The SendRequest method signature is loaded with a lot of details so let’s down the
method.
With every SendRequest called, we may want to perform some additional processing on
returning the data, so making it virtual allows you to override the method for further
processing.
Since async gives us the performance we look for in quick websites, this call is requires
an async operator.
Next in line is the generic (<T>). In our caller, we can now add a class type on our
SendRequest and the API will return an instance of our populated object.
var products = await [Link]<List<Product>>(message);
This will return a list of Products from our API request. Using generics, we can make
this a centralized, single point-of-entry where an API returns any type from this method.
The GetFormatters() method is also made override-able just in case you want to use
XML instead of Json as your return type.
We grab our single instance of an HttpClient from our factory and make a call to our
API. We confirm our call was successful with the EnsureSuccessStatusCode() method.
If it wasn’t, it will return the appropriate error to the browser or calling program.
If our API call was was successful, we read the content based on the content format and
return the deserialized object.
29
Review
We added a SendRequest method to our HttpRequestBuilder allowing us to retrieve
results from all of our hard work.
With our foundation coded, you may be wondering how everything ties together. In the
next section, we look at putting all of the pieces together to make a simple client-side
API call.
Along with an example, we’ll be moving a little faster throughout these sections bolting
on additional features to expand on our existing codebase.
30
Building a Client-Side API Call
With the infrastructure in place and isolated, we can now create our first example of an
API call with our code.
Let’s take an example of an existing API out there: The Postman Echo service.
This call is merely to see if our client can call the service. The service is located at:
[Link]
31
SECRET SPEED TIP
If you have an entire Json response for an API, you can use Visual Studio 2017/2019 to
build your classes fast.
1. Copy the entire Json result of your API. It can usually be found in the documentation.
This class will encapsulate all of the API calls to the postman-echo service specifically. If
you have other APIs in your system, you would create another BasexxxxxApi for your
calls.
32
Essentially, for every domain where you make an API call, you will
public class BasePostmanEchoApi : BaseHttpClientWithFactory
{
private const string baseAddress = "[Link]
Very small and very simple. This class should contain the basics for creating a simple
API request to the postman-echo service and it returns an HttpRequestBuilder.
Look at it this way. When creating your API call with the HttpRequestBuilder, determine
the most common settings you will make for each API call? This will include the base
address, authorization, and header settings to name a few.
The Postman Echo Service gives us our top level access into our Postman API calls.
public class PostmanEchoApi : BasePostmanEchoApi, IPostmanEchoApi
{
public PostmanEchoApi(IHttpClientFactory factory) : base(factory)
{
BasePath = "/get";
}
Notice how we only have one method called GetEcho with a single parameter? This
allows us to create multiple methods for the GET service. We’ll cover this later with
another API example.
33
Usage of our Example API
To use our PostmanEchoApi, we dependency-inject it into our controller and, just like a
repository, we make the call to retrieve the data from the API.
public class HomeController : Controller
{
private readonly IPostmanEchoApi _postmanApi;
return View(echo);
}
}
Again, notice the IPostmanEchoApi as the only parameter? Don’t forget to set up your
dependency injection in your [Link].
[Link]<IPostmanEchoApi, PostmanEchoApi>();
You may be wondering what the folder structure looks like with this type of project.
While the infrastructure piece is small and self-contained, it’s suitable to place all of your
API code in a separate folder.
34
The API folder (ApiCollection in this example) contains all of our APIs, Models, and
implementations of our API Service calls.
Again, for every domain, you would create a new folder containing all of your response
models and API code. This gives a nice centralized location for your API code.
Review
In our first example, we created a quick API to call the Postman Echo API to test our
infrastructure.
As we look over the implementation, you can see it’s very lightweight, can be extended
further, and creates a simple, stable, and organized way to build your APIs.
35
Example: Rick and Morty API
With our new HttpClient able to handle a majority of APIs out there, there was one API I
wanted to try out.
Since Rick and Morty are so popular, I was able to find a Rick and Morty API. This API
doesn’t require a token or key and is public.
36
]
}
37
.AddToPath(path)
.HttpMethod([Link])
.Headers(headers =>
[Link](
new MediaTypeWithQualityHeaderValue("application/json")));
}
}
If we look at the documentation, they also mention how to get multiple characters using
comma-delimited id numbers.
For example, to grab Rick and Morty data, they are 1 and 2 respectively. You add the id
list to the end of the URL (see GetRickAndMorty)
Anything dealing with the /character API is an quick and easy method to create.
Review
We took a simple REST-based API and made it easy to create additional methods.
38
Example: Marvel API
Who doesn’t love a good Marvel story, but did you know there is an API for everything
Marvel?
While the Marvel API is REST-based, you can only make API calls by registering and
receiving a private key.
39
Create the Model
Our Marvel model is a bit beefy compared to the previous ones.
public class MarvelComic
{
public int code { get; set; }
public string status { get; set; }
public string copyright { get; set; }
public string attributionText { get; set; }
public string attributionHTML { get; set; }
public string etag { get; set; }
public Data data { get; set; }
}
40
{
public string resourceURI { get; set; }
public string name { get; set; }
}
Now that we have our model, we need to create our base API.
41
Create the Base API
Our BaseMarvelApi works a little bit different. Once you register, you get your private
key.
Personally, I really like the way they provide access to their API through hashing.
When you register, they present you with a public and private key. When you make an
API call, they require three pieces of data:
• Timestamp (ts)
• A Hashed Key - Comprised of the Date/Time from above along with the
publicKey and privateKey combined.
Since this is a repetitive task every time a Marvel API is called, we will place this into the
BaseMarvelApi class.
public class BaseMarvelApi : BaseHttpClientWithFactory
{
private const string baseAddress = "[Link]
private const string version = "v1";
private const string publicKey = "public key goes here";
private const string privateKey = "private key goes here";
42
public string GetApiKey(string ts)
{
using (MD5 md5 = [Link]())
{
return GetHash(md5, ts + privateKey + publicKey);
}
}
The GetQueryString() is the first thing to calculate based on the Date/Time and public
and private key.
Once the collection is created, we can fill out all of the important base information for
the API including what version of the API we want to hit.
The GetApiKey() only requires a timestamp and we receive a hash key to pass to
Marvel to validate our request.
43
public class MarvelComicApi : BaseMarvelApi, IMarvelComicApi
{
private NameValueCollection SearchTerms { get; set; }
The first method allows us to return a lot of comics (it’s paged and you have certain
limitations on how much you can retrieve) whereas the second method allows you to
search for comics.
Since the search is query-string based, it’s easy to append it to the URL and retrieve the
results.
With the Marvel API finished, we can search the entire Marvel catalog for Wolverine
titles.
public async Task<IActionResult> Index()
{
var comics = await _marvel.GetComicsByTitle("Wolverine");
return View(comics);
}
With a vast amount of Marvel APIs available, all of them are querystring based. You
could easily create a builder pattern for each Marvel API alone.
44
Conclusion
With the amount of APIs available today, this infrastructure gives you more flexibility with
various web services across the Internet.
I hope this book has made HttpClient classes easier to work with again.
The question is how will you evolve your application with all of the APIs available on the
Internet?
45
Appendix A: Resources
[Link] Core Diagnostic Scenarios (Github) - Async Guidance from David Fowler
[Link]
You’re (probably still) using HttpClient wrong and it is destabilizing your software
([Link])
[Link]
46