Azure Functions Dependency Injection And Proper HttpClient Usage

Azure Functions Dependency Injection And Proper HttpClient Usage
Share:

About

In this code snippet, I will show you how to properly use the C# Http Client in Azure Functions using dependency injection(the same concept applies to other C# .NET technologies). I’ll also show you how to add request caching to improve performance.

The HttpClient implements the IDisposable interface so you would think you can just create a client use it within a try/catch and then dispose of it or use it with the using statementHowever, if you are doing this a lot it might lead to websocket exhaustion as a websocket will not get released immideatly after the client is disposed of.

To fix this you can create an Http Client(preferably by using the HttpClientFactory) and reuse it by dependency injecting it into the functions/methods/modules making the requests.

Another thing you should consider when using an Http Client is implementing software design patterns/mechanisms/concepts such as Retry, Circuit Breaker, Hedging, Timeout, Rate Limiter, and Fallback. This will improve the reliability of your software as Http requests can be flaky a times. I described how to do this using a library called Polly in this post.

Let’s have a look at the code below to see how to work with the HttpClient.

Prerequisites:

Before getting started you need to install the Caching.Memory NuGet package into your project: Microsoft.Extensions.Caching.Memory and add the following using statements at the top of your code file.
using Microsoft.Extensions.Caching.Memory;
using System.Security.Cryptography;

Program.cs File

Add the following services into your Program.cs file.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices((context, services) =>
    {
        //Add memory cache service. 
        services.AddMemoryCache();
        //Add http client service.
        services.AddHttpClient();

        //You can also create a named HttpClient and set it up for a specific request. This way you don't have set it up every time in code. 
        //Register named HttpClient as a singleton service. More about named HttpClients: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0#named-and-typed-clients
        services.AddHttpClient("PlaceholderJsonClient", client =>
        {
            //Set the url the client will call.
            client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/todos/1");
            //Set the default request headers that will be added to every request.
            //See the code extra example code to see how to set one-time headers for a specific request.
            client.DefaultRequestHeaders.Add("Content-Type", "text/plain; charset=utf-8");
        });
    })
    .Build();

host.Run();

Code

The Http Client Factory gets injected in the constructor of the function where it is used to create an Http Client. Any non-static function can now access the client. Additionally, we create an instance of the hashing algorithm we’ll later use to create a hash used for caching request contents.
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Caching.Memory;
using System.Text;
using System.Security.Cryptography;

namespace DemoFunctionApp
{
    public class Function1
    {
        private readonly ILogger _logger;
        private readonly HttpClient _client;
        private readonly IMemoryCache _cache;
        private readonly SHA256 sha256Hash;

        //Constructor that injects the logger and the HttpClient and the Cache into the instance.
        public Function1(ILoggerFactory loggerFactory, IHttpClientFactory httpClientFactory, IMemoryCache cache)
        {
            _logger = loggerFactory.CreateLogger<Function1>();
            _client = httpClientFactory.CreateClient();
            //Use the named client we registered.
            //_client = httpClientFactory.CreateClient("PlaceholderJsonClient");
            _cache = cache;
            sha256Hash = SHA256.Create();
        }

        //This is just an example/demo Azure Functions HTTP endpoint we'll call to run our code.
        [Function("Function1")]
        public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
        {
            //Inject the client into the request making function instead of creating it every time.
            //We'll do the same with the cache, and hashing function.
            string message = await MakeRequestToTestAPI(_client, _cache, sha256Hash);

            //Create and return a response.
            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
            response.WriteString(message);

            return response;
        }

        private  async Task<string> MakeRequestToTestAPI(HttpClient client, IMemoryCache cache, SHA256 sha256Hash)
        {
            //Here I'll just hardcode the URL and the request body for the sake of the example.
            string url = "https://jsonplaceholder.typicode.com/posts";
            string requestBody = "{\"title\": \"foo\", \"body\": \"barr\", \"userId\": 1}";

            //Create a key for the cache from the URL and body. If your request doesn't have a body(GET) you can just use the URL with its parameters.
            //Or you can even just use only certain parameters.
            string key = url + requestBody;

            //Reduce the key to a hash.
            byte[] keyHash = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(requestBody));

            //Check if the response is already cached. If so return it.
            string cachedValue = cache.Get<string>(keyHash);
            if (cachedValue is not null)
                return cachedValue;


            //Else make the request ...
            //I'm using the PostAsync method here but you can use any other method you need.
            var response = await client.PostAsync(url, new StringContent(requestBody, Encoding.UTF8, "application/json"));
            response.EnsureSuccessStatusCode();
            string responseContent = await response.Content.ReadAsStringAsync();

            #region Separately defined request with one-time headers ////////////////////////////////////////

            //This is just an example of a different way to make a request(with added one time-headers).
            HttpRequestMessage request = new HttpRequestMessage()
            {
                RequestUri = new Uri(url),
                Method = HttpMethod.Post,
                Content = new StringContent(requestBody, Encoding.UTF8, "application/json"),
                //This is how you can set one-time headers for this specific request.
                //Unlike the default headers that are set on the client and are added to every request.
                Headers = { { "Content-Type", "application/json" } }
            };

            var response2 = await client.SendAsync(request);
            response2.EnsureSuccessStatusCode();
            string responseContent2 = await response2.Content.ReadAsStringAsync();

            #endregion ////////////////////////////////////////////////////////////////////////////////////

            //... cache the response and return it.
            cache.Set(keyHash, responseContent, TimeSpan.FromMinutes(60));

            return responseContent;
        }
    }
}
Share:

Leave a Reply

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

The following GDPR rules must be read and accepted:
This form collects your name, email and content so that we can keep track of the comments placed on the website. For more info check our privacy policy where you will get more info on where, how and why we store your data.

Advertisment ad adsense adlogger