Blazor server apps run.NET code on the server using the common ASP.NET Core application. Similar to ASP.NET Core web applications, these apps allow us to access any.NET library or server-side feature. Utilizing HTTP Client instances to send HTTP requests to external Web APIs is one such feature. I’ll demonstrate various methods for creating HTTP Client instances in this tutorial. Additionally, I’ll demonstrate how to use a third-party API in Blazor Server Apps to fetch and display data.
Overview of Third Party Web API
We will create a Blazor server app that will enable users to enter a country code and a year on a Blazor page component. We will then make a third-party API call to retrieve the country’s list of public holidays for that specific year. We will use the Nager.Date third-party API, which is a global public holidays API.
It is a very simple API and you can easily test this API in Postman by entering the following URL.
The list of public holidays returned by this API is displayed below in JSON format:
Getting Started with Blazor Sever App
In Visual Studio 2019, create a Blazor Server App and a folder called Models. To map the Holidays API request and response depicted above, add the following two model classes to the Models folder.
HolidayRequestModel.cs
public class HolidayRequestModel { public string CountryCode { get; set; } public int Year { get; set; } }
HolidayResponseModel.cs
public class HolidayResponseModel { public string Name { get; set; } public string LocalName { get; set; } public DateTime? Date { get; set; } public string CountryCode { get; set; } public bool Global { get; set; } }
HolidaysExplorer.razor.cs
public partial class HolidaysExplorer { private HolidayRequestModel HolidaysModel = new HolidayRequestModel(); private List<HolidayResponseModel> Holidays = new List<HolidayResponseModel>(); [Inject] protected IHolidaysApiService HolidaysApiService { get; set; } private async Task HandleValidSubmit() { Holidays = await HolidaysApiService.GetHolidays(HolidaysModel); } }
<EditForm Model="@HolidaysModel" OnValidSubmit="@HandleValidSubmit" class="form-inline"> <label class="ml-2">Country Code:</label> <InputText id="CountryCode" @bind-Value="HolidaysModel.CountryCode" class="form-control" /> <label class="ml-2">Year:</label> <InputNumber id="Year" @bind-Value="HolidaysModel.Year" class="form-control" /> <button class="btn btn-primary ml-2" type="submit">Submit</button> </EditForm>
@if (Holidays.Count > 0) { <table class="table table-bordered table-striped table-sm"> <thead> <tr> <th>Date</th> <th>Name</th> <th>Local Name</th> <th>Country Code</th> <th>Global</th> </tr> </thead> <tbody> @foreach (var item in Holidays) { <tr> <td>@item.Date.Value.ToShortDateString()</td> <td>@item.Name</td> <td>@item.LocalName</td> <td>@item.CountryCode</td> <td>@item.Global</td> </tr> } </tbody> </table> }
The complete code of HolidaysExplorer.razor view is shown below.
@page "/" <h3>Holidays Explorer</h3> <br /> <EditForm Model="@HolidaysModel" OnValidSubmit="@HandleValidSubmit" class="form-inline"> <label class="ml-2">Country Code:</label> <InputText id="CountryCode" @bind-Value="HolidaysModel.CountryCode" class="form-control" /> <label class="ml-2">Year:</label> <InputNumber id="Year" @bind-Value="HolidaysModel.Year" class="form-control" /> <button class="btn btn-primary ml-2" type="submit">Submit</button> </EditForm> <br /> @if (Holidays.Count > 0) { <table class="table table-bordered table-striped table-sm"> <thead> <tr> <th>Date</th> <th>Name</th> <th>Local Name</th> <th>Country Code</th> <th>Global</th> </tr> </thead> <tbody> @foreach (var item in Holidays) { <tr> <td>@item.Date.Value.ToShortDateString()</td> <td>@item.Name</td> <td>@item.LocalName</td> <td>@item.CountryCode</td> <td>@item.Global</td> </tr> } </tbody> </table> }
Creating HttpClient using IHttpClientFactory in Blazor Server Apps
HttpClient can be used in Blazor server apps in a variety of ways, so let’s start with a simple example in which we create a HttpClient object using the IHttpClientFactory.
Make the following IHolidaysApiService interface in the project’s Services folder. The interface only has one method, GetHolidays, which returns a list of HolidayResponseModel objects and accepts a HolidayRequestModel as a parameter.
IHolidaysApiService.cs
public interface IHolidaysApiService { Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest); }
HolidaysApiService.cs
public class HolidaysApiService : IHolidaysApiService { private readonly IHttpClientFactory _clientFactory; public HolidaysApiService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest) { var result = new List<HolidayResponseModel>(); var url = string.Format("https://date.nager.at/api/v2/PublicHolidays/{0}/{1}", holidaysRequest.Year, holidaysRequest.CountryCode); var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); var client = _clientFactory.CreateClient(); var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { var stringResponse = await response.Content.ReadAsStringAsync(); result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } else { result = Array.Empty<HolidayResponseModel>().ToList(); } return result; } }
var url = string.Format("https://date.nager.at/api/v2/PublicHolidays/{0}/{1}", holidaysRequest.Year, holidaysRequest.CountryCode);
var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
If the API call is successful, we are reading the response as a string using the following line.
var stringResponse = await response.Content.ReadAsStringAsync();
result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton<IHolidaysApiService, HolidaysApiService>(); services.AddHttpClient(); }
Creating Named HttpClient objects in Blazor Server Apps
The aforementioned example is useful when you want to use IHttpClientFactory to create HttpClient objects in a few methods of an existing application without changing the entire application. Use named HTTP clients if you’re developing a new application or want to centralize the creation of HttpClient objects.
The advantages of designing named HTTP clients are as follows:
- Instead of having configurations scattered throughout the application, we can give each HttpClient a name and specify all HttpClient-related configurations at the application’s startup.
- The named HttpClient can be configured only once and then used repeatedly to call APIs from a specific API provider.
- Depending on how these clients are used throughout the application, we can configure multiple named HttpClient objects with various configurations.
Using the AddHttpClient method’s name, we can specify a named client in the ConfigureServices method of the Startup.cs file.
Startup.cs
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton<IHolidaysApiService, HolidaysApiService>(); services.AddHttpClient("HolidaysApi", c => { c.BaseAddress = new Uri("https://date.nager.at/"); c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); }); }
The client’s name, such as HolidaysApi, must be specified. In addition, we can configure the BaseAddress, DefaultRequestHeaders, and other properties as displayed above.
Once the named HttpClient is set up, we can use the same CreateClient method to create HttpClient objects throughout the application, but this time we must specify which named client, for example, HolidaysApi, we want to create.
HolidaysApiService.cs
public class HolidaysApiService : IHolidaysApiService { private readonly IHttpClientFactory _clientFactory; public HolidaysApiService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest) { var result = new List<HolidayResponseModel>(); var url = string.Format("api/v2/PublicHolidays/{0}/{1}", holidaysRequest.Year, holidaysRequest.CountryCode); var request = new HttpRequestMessage(HttpMethod.Get, url); var client = _clientFactory.CreateClient("HolidaysApi"); var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { var stringResponse = await response.Content.ReadAsStringAsync(); result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } else { result = Array.Empty<HolidayResponseModel>().ToList(); } return result; } }
The name, for instance, HolidaysApi, that we specify in the CreateClient method must match the name that we specify in the Startup.cs file. A new instance of HttpClient is created for us each time a CreateClient method is called.
Additionally, since we already specified the base address in the Startup.cs file, we don’t need to include the API hostname in the Request URL.
The list of public holidays should appear after you rerun the application and enter the country code and year values.
Creating Typed HttpClient objects in Blazor Server Apps
Utilizing Typed clients is the third method for producing and utilizing HttpClient objects. The advantages for these clients are as follows:
- They provide the same capabilities as named clients without the need to use strings as keys.
- They provide IntelliSense and compiler help when consuming clients.
- They provide a single location to configure and interact with a particular HttpClient. For example, we can configure a typed HttpClient specific to a particular endpoint of Facebook API, and that HttpClient can encapsulate all the logic required to use that particular endpoint.
- They work with Dependency Inject (DI) and can be injected where required.
The same AddHttpClient method must be used in Startup.cs to register a typed HTTPClient, but this time we must pass the service name HolidaysApiService as the type.
Startup.cs
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton<IHolidaysApiService, HolidaysApiService>(); services.AddHttpClient<HolidaysApiService>(); }
The HTTP client and our service HolidaysApiService will both be registered as transient clients and services in the code snippet above. As shown in the following code snippet, this will enable us to pass the HttpClient in the service’s constructor. Take note of how the HttpClient is displayed as the service’s public property.
HolidaysApiService.cs
public class HolidaysApiService : IHolidaysApiService { public HttpClient Client { get; } public HolidaysApiService(HttpClient client) { client.BaseAddress = new Uri("https://date.nager.at/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); Client = client; } public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest) { var result = new List<HolidayResponseModel>(); var url = string.Format("api/v2/PublicHolidays/{0}/{1}", holidaysRequest.Year, holidaysRequest.CountryCode); var response = await Client.GetAsync(url); if (response.IsSuccessStatusCode) { var stringResponse = await response.Content.ReadAsStringAsync(); result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } else { result = Array.Empty<HolidayResponseModel>().ToList(); } return result; } }
Instead of in the constructor of the typed client, the configuration for a typed client can be specified during registration in the ConfigureServices method of the Startup.cs file.
Startup.cs
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddHttpClient<IHolidaysApiService, HolidaysApiService>(c => { c.BaseAddress = new Uri("https://date.nager.at/"); c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); }); }
You don’t need to register your service separately if you’re using this method. The following line can be taken out of the ConfigureServices method.
services.AddSingleton<IHolidaysApiService, HolidaysApiService>();
Instead of being made available as a public property, the HttpClient object can be contained within a typed client. Then, we can employ this client internally in any service model.
public class HolidaysApiService : IHolidaysApiService { private readonly HttpClient _httpClient; public HolidaysApiService(HttpClient client) { _httpClient = client; } public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest) { var result = new List<HolidayResponseModel>(); var url = string.Format("api/v2/PublicHolidays/{0}/{1}", holidaysRequest.Year, holidaysRequest.CountryCode); var response = await _httpClient.GetAsync(url); if (response.IsSuccessStatusCode) { var stringResponse = await response.Content.ReadAsStringAsync(); result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } else { result = Array.Empty<HolidayResponseModel>().ToList(); } return result; } }
The list of public holidays should appear after you rerun the application and enter the country code and year values.
Summary
I discussed various methods for building and utilizing HTTP clients in Blazor Server Apps in this tutorial. Due to Blazor Server Apps’ foundation in ASP.NET Core infrastructure, the majority of the techniques discussed here are also applicable to ASP.NET Core applications.