- Home>
- Software Development>
- Enhancing ASP.NET Core/Blazor App Security and Reusability with HttpMessageHandler and Named HttpClient
In this post, I share an example of how I use a HttpMessageHandler
to automatically attach a bearer token when calling a web API from an ASP.NET Core/Blazor application.
HttpClient and HttpMessageHandler together allow you to improve code reusability. For instance, suppose your web app needs to call a few different web APIs, each with different base URLs. When making a request, instead of having to enter a full URL every time, you can set up a custom http client and use it in your service.
public static WebApplicationBuilder AddHttpClients(this WebApplicationBuilder builder) { // Define a default timeout duration 120 seconds var defaultTimeout = TimeSpan.FromSeconds(120); var studioApiOptions = builder.Configuration.GetSection(OptionNames.DataManagementApi).Get<StudioApiOptions>(); // Setup named HttpClient instances builder.Services.AddHttpClient(HttpClientNames.DataManagementApi, client => { client.BaseAddress = new Uri(studioApiOptions!.BaseUrl!); client.Timeout = defaultTimeout; }); var aiApiOptions = builder.Configuration.GetSection(HttpClientNames.AIServicesApi).Get<AIServicesApiOptions>(); builder.Services.AddHttpClient(HttpClientNames.AIServicesApi, client => { client.BaseAddress = new Uri(aiApiOptions!.BaseUrl); client.Timeout = defaultTimeout; }); return builder; }
In the above example, I setup two named http clients and added them into the container so that I could use them in services. With the clients setup, I don’t have to specify the base URLs when making calls to the two web APIs. Below shows an example of how I use the clients in my service class.
public class DataExchangeService : IDataExchangeService { private readonly IHttpClientFactory _httpClientFactory; public DataExchangeService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task<ICollection<DocumentDto>?> GetDocuments(int dataExchangeId) { var url = $ "./v2.1/documents/{dataExchangeId}"; using (var httpClient = DataManagementClient()) { return await httpClient.GetFromJsonAsync<ICollection<DocumentDto>>(url); } } private HttpClient DataManagementClient() { var client = _httpClientFactory.CreateClient(HttpClientNames.DataManagementApi); return client; } private HttpClient AiServicesClient() { return _httpClientFactory.CreateClient(HttpClientNames.AIServicesApi); } public async Task UploadDocument(int dataExchangeId, Stream fileStream, string documentName) { var uploadUrlPath = $ "./v2.1/data_exchange/{dataExchangeId}/embed_pdf?document_name={documentName}"; using var request = new HttpRequestMessage(HttpMethod.Post, uploadUrlPath) { // codes omitted for brevity }; using (var httpClient = AiServicesClient()) { var response = await httpClient.SendAsync(request); } response.EnsureSuccessStatusCode(); } } }
In the above code snippets, notice how I don’t have to specify the full URLs. Also, notice how I use HttpClientFactory to create an http client by specifying the name of the client. My app needs to call out to two different web APIs, and hence I setup the two clients, named “DataManagementApi” and “AIServicesApi”. Each client has a different base URL than the other. By using named http clients, you can put the plumbing logic of configuring the clients once and reuse them throughout the app.
Each of the web APIs are protected and required a bearer token for access. However, notice that I don’t have to specify the logic of obtaining the bearer token in the service. That’s because I use a custom HttpMessageHandler and configure the clients to use the handler.
public class AuthorizationHeaderMessageHandler : DelegatingHandler { private readonly ITokenAcquisition _tokenAcquisition; private readonly string[] _scopes; private readonly IUserService _userService; public AuthorizationHeaderMessageHandler(ITokenAcquisition tokenAcquisition, string[] scopes, IUserService userService) { _tokenAcquisition = tokenAcquisition; _scopes = scopes; _userService = userService; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { await CheckAppendAccessToken(request); return await base.SendAsync(request, cancellationToken); } private async Task<string?> GetAccessToken() { if (_userService.IsUserAuthenticated()) { try { var authenticationResult = await _tokenAcquisition.GetAuthenticationResultForUserAsync(_scopes); var accessToken = authenticationResult.AccessToken; return accessToken; } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } return null; } private async Task CheckAppendAccessToken(HttpRequestMessage request) { if (_userService.IsUserAuthenticated()) { var accessToken = await GetAccessToken(); if (accessToken != null) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } } }
Below code snippets show how I create and configure the client to use the handler.
builder.Services.AddHttpClient(HttpClientNames.DataManagementApi, client => { client.BaseAddress = new Uri(studioApiOptions!.BaseUrl!); client.Timeout = defaultTimeout; }).AddHttpMessageHandler( _ => new AuthorizationHeaderMessageHandler(tokenAcquisition!, studioApiOptions!.Scopes, userService!)); var aiApiOptions = builder.Configuration.GetSection(HttpClientNames.AIServicesApi).Get<AIServicesApiOptions>(); builder.Services.AddHttpClient(HttpClientNames.AIServicesApi, client => { client.BaseAddress = new Uri(aiApiOptions!.BaseUrl); client.Timeout = defaultTimeout; }).AddHttpMessageHandler(_ => new AuthorizationHeaderMessageHandler(tokenAcquisition!, aiApiOptions!.Scopes, userService!));
In conclusion, leveraging named HttpClient
instances and a custom HttpMessageHandler
not only simplifies API calls but also enhances code reusability and maintains a clean separation of concerns. I encourage you to explore and integrate these practices into your projects, improving code organization and promoting a more secure and scalable architecture. Happy coding!
Make HTTP requests using IHttpClientFactory in ASP.NET Core | Microsoft Learn
ASPNETCORE Outgoing Request Middleware
HttpMessageHandler Class (System.Net.Http) | Microsoft Learn
Building a fully multitenant system using Microsoft Identity Framework and SQL Row Level Security
How to auto start and keep an ASP.NET core web application running on IIS
Should You Use Shared Models or Separate Sets of Models?
Understanding Message Flow in Microsoft Teams Bot Development.