ASP.NET Core in .NET 5 – sending a request

Sending a request in ASP.NET Core in .NET 5 is a standard operation that can be achieved pretty easily. However, details matter in this case and I’ll show you the best practice available. We will also take a look at some advanced features to get the full scope.

Using a real available API

In this article, I will be using 3rd party free service for fetching weather forecasts – http://weatherstack.com. To be able to use it, just register on their website and you can use it as well. 1000 requests in a month are available for a free account and that should be more than enough to fulfill our needs.

First, let’s have a look at the requests we are going to make. To test the API, I’m using a Postman app, which is very powerful, yet intuitive. I strongly encourage you to read my article about it here: Postman the right way

Here is how a request to fetch current weather in Poznań looks like:

This is a GET request to http://api.weatherstack.com/current with two parameters:

  • access_key which you get when registering on the website
  • query that can be a city name

The response that we got is a 200 OK with JSON content.

 To make this request, I’ll create a WeatherStackClient class.

using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace PrimeHotel.Web.Clients
{
    public class WeatherStackClient : IWeatherStackClient
    {
        private const string AccessKey = "3a1223ae4a4e14277e657f6729cfbdef";
        private const string WeatherStackUrl = "http://api.weatherstack.com/current";

        private HttpClient _client;
        private readonly ILogger<WeatherStackClient> _logger;

        public WeatherStackClient(HttpClient client, ILogger<WeatherStackClient> logger)
        {
            _client = client;
            _logger = logger;
        }
    }
}

There are a few things to notice here:

  • AccessKey which is hardcoded for now, but in a real-life API should be moved to configuration
  • IWeatherStackClient interface that is introduced for Dependency Injection support
  • HttpClient class is passed in a constructor. It will be automatically created and maintained by the framework

Now let’s create the logic.

    public async Task<WeatherStackResponse> GetCurrentWeather(string city)
    {
        try
        {
            using var responseStream = await _client.GetStreamAsync(GetWeatherStackUrl(city));
            var currentForecast = await JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream);
            
            return currentForecast;
        }
        catch (Exception e)
        {
            _logger.LogError(e, $"Something went wrong when calling WeatherStack.com");
            return null;
        }
    }

    private string GetWeatherStackUrl(string city)
    {
        return WeatherStackUrl + "?"
                + "access_key=" + AccessKey
                + "&query=" + city;
    }

Let’s go through this code and explain what’s going on:

  • _client.GetStreamAsync is an asynchronous method that takes a URL an returns a stream. There are more methods, like: GetAsync, PostAsync, PutAsync, PatchAsync, DeleteAsync for all CRUD operations. There is also GetStringAsync that serializes a response content to string – just like GetStreamAsync does
  • GetWeatherStackUrl is merging a service URL with query parameters, returning a full URL address
  • JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream) deserializes a stream and format output as a WeatherStackResponse class

The WeatherStackResponse class looks like this:

using System.Text.Json.Serialization;

namespace PrimeHotel.Web.Clients
{
    public class WeatherStackResponse
    {
        [JsonPropertyName("current")]
        public Current CurrentWeather { get; set; }

        public class Current
        {
            [JsonPropertyName("temperature")]
            public int Temperature { get; set; }

            [JsonPropertyName("weather_descriptions")]
            public string[] WeatherDescriptions { get; set; }

            [JsonPropertyName("wind_speed")]
            public int WindSpeed { get; set; }

            [JsonPropertyName("pressure")]
            public int Pressure { get; set; }

            [JsonPropertyName("humidity")]
            public int Humidity { get; set; }

            [JsonPropertyName("feelslike")]
            public int FeelsLike { get; set; }
        }
    }
}

Notice that I used JsonPropertyName attribute to identify what JSON property is each property matching. Here is the structure that we are going to map.

One last thing – we need to register our WeatherStackClient in a Dependency Injection container. In order to do so, we need to go to Startup class and add the following line in ConfigureServices method.

services.AddHttpClient<IWeatherStackClient, WeatherStackClient>();

We are using a dedicated method for registering classes using HttpClient. Underneath it’s using IHttpClientFactory that helps to maintain the pooling and lifetime of clients. You have a limited number of HTTP connections that you can maintain on your machine and if you create too much clients each blocking a connection, you will end up failing some of your requests. It also adds a configurable logging experience (via ILogger) for all requests sent through. All in all, it makes a developer’s life easier and allows you to do some smart stuff too.

Does it work? Yes it does! The response was correctly mapped into my class and I can return it.

Do you wonder what was logged when making this request? Let’s have a quick look.

As I mentioned earlier IHttpClientFactory also provides a logging mechanism, so that every request is logged. Here you can see that not only address was logged, but also HTTP method and time of execution. This can be pretty useful for debugging.

Adding a retry mechanism

In a micro-services world, every micro-service can have a bad day once in a while. Therefore, we need to have a retry mechanism for services we call and we know that they fail from time to time. In ASP.NET Core for .NET 5 there is a third-party library integrated just that purpose – it’s Polly. Polly is a comprehensive resilience and transient fault-handling library for .NET. It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

For our scenario let’s add a retry mechanism, that will call WeatherStack service and retry 3 times after an initial failure. With Polly, a number of retries and delays between then can be easily set. Let’s have a look at the example – it’s in the Startup method where we configure DI container.

    services.AddHttpClient<IWeatherStackClient, WeatherStackClient>()
        .AddTransientHttpErrorPolicy(
            p => p.WaitAndRetryAsync(new[]
            {
                TimeSpan.FromSeconds(1),
                TimeSpan.FromSeconds(5),
                TimeSpan.FromSeconds(10)
            }));

With this code we will retry the same request after 1, 5, and 10 seconds delay. There is no additional code needed. Polly will do everything for us. We will see logs that something failed, only after all retries will fail, we will get the exception.

Adding a cancellation token

A cancellation token is a mechanism that can stop the execution of an async call. Let’s say that our request shouldn’t take more than 3 seconds, because if it does, we know that something isn’t right and there is no point to wait.

To implement that we need to create a cancellation token and provide that when making a call with an HTTP client.

    public async Task<WeatherStackResponse> GetCurrentWeatherWithAuth(string city)
    {
        try
        {
            using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3));

            using var responseStream = await _client.GetStreamAsync(GetWeatherStackUrl(city), cancellationTokenSource.Token);
            var currentForecast = await JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream);

            return currentForecast;
        }
        catch (TaskCanceledException ec)
        {
            _logger.LogError(ec, $"Call to WeatherStack.com took longer then 3 seconds and had timed out ");
            return null;
        }
        catch (Exception e)
        {
            _logger.LogError(e, $"Something went wrong when calling WeatherStack.com");
            return null;
        }
    }

If the request takes too long, we will receive a TaskCancelledException, which we can catch and react to it differently, that when getting unexpected exception.

Provide authorization

Basic authorization is definitely the simplest and one of the most popular ones used. The idea is that every request to the specific service needs to be authorized, so that along with our content, we need to send authorization info. With basic authorization, we need to pass a user and password encoded as base64 string and put in a request header. Let’s see how that can be accomplished. 

    private const string ApiKey = "3a1223ae4a4e14277e657f6729cfbdef";
    private const string Username = "Mik";
    private const string Password = "****";

    public WeatherStackClient(HttpClient client, ILogger<WeatherStackClient> logger)
    {
        _client = client;
        _logger = logger;

        var authToken = Encoding.ASCII.GetBytes($"{Username}:{Password}");
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "Basic",
            Convert.ToBase64String(authToken));
    }

In this way every call from WeatherStackClient will have authorization info and we do not need to add anything when we make requests. The only place we need to put additional code is a constructor.

Note that authorization is not needed to call weatherstack.com and is added only to show how it can be done.

This article did not cover all of the possibilities of IHttpClientFactory so if you want to know more, just go to this Microsoft article.

Hope you like this post, all code posted in this article is available at my GitHub account:

https://github.com/mikuam/PrimeHotel

 

2 thoughts on “ASP.NET Core in .NET 5 – sending a request

    1. Michał Białecki Post author

      Thanks Thomas,
      Actually… I’m using System.Text.Json here:) But it’s a good point to have it in mind, that in most cases we no longer need Newtonsoft.Json. System.Text.Json that we have available in ASP.NET Core is enough and we do not need to add an external dependency.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *