API interaction

🟨 Slightly different to Blazor Server

Knowing how to interact with APIs is a crucial skill for working with Blazor WebAssembly. This involves sending requests to an API server and processing the resulting responses. In this tutorial, you will learn:

  • The importance of registering HttpClient
  • How to register HttpClient for a single API
  • How to register HttpClient for multiple APIs
  • Interfere HttpClient
  • How to send requests and handle responses
  • Key differences between Blazor WebAssembly and Blazor Server
You can download the example code used in this topic on GitHub.

The importance of registering HttpClient

In Blazor WebAssembly projects, it's essential to register HttpClient in order to perform server operations such as reading or writing files, accessing databases, and more. These operations must be performed at the API level, using technologies like ASP.NET Web API or ASP.NET gRPC Service. To use an API in a Blazor WebAssembly project, HttpClient registration is required.

The process for registering APIs in Blazor WebAssembly can vary depending on how the API is designed. Factors to consider include whether there is a single API server or multiple servers, how authorization is handled at the API level, and how errors are handled in the API. By understanding these factors, you can determine the appropriate method for registering APIs in your Blazor WebAssembly project.


How to register HttpClient for a single API

If your website has only one API, you can add HttpClient as a scoped service in the Program.cs file. To do this, assuming your API endpoint is http://localhost:30911, use the following code:

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://localhost:30911") });

Once registered, you can inject the HttpClient into any razor component to make a request. For example:

@inject HttpClient HttpClient

This will allow you to interact with your API from within your Blazor WebAssembly application.


How to register HttpClient for multiple APIs

When working with multiple APIs in Blazor WebAssembly, there are several ways to register HttpClient, each with its own advantages and disadvantages. The three most common approaches are:

  • Using a derived class of HttpClient
  • Using a named HttpClient
  • Using an HttpClient wrapper

Each approach provides everything you need to interact with multiple APIs in your application, but the best approach will depend on the specific requirements of your project.

Using a derived class of HttpClient

HttpClient is a class that provides a simple API for sending and receiving HTTP requests and responses. In most scenarios, you can use HttpClient directly to interact with remote services and APIs.

However, in some cases, you may need to customize the behavior of HttpClient, such as adding custom headers, handling retries, or implementing a custom message handler. In these situations, you can derive a new class from HttpClient and add your custom logic to it.

Deriving a new class from HttpClient allows you to extend the functionality of HttpClient while still using its core features and avoiding the need to write all the low-level HTTP code yourself. Your derived class can inherit all the methods and properties of HttpClient and override or add new behavior as needed.

To create a derived class, follow these steps:

  1. Define a new class that extends the HttpClient class and sets the BaseAddress property in its constructor. For example:
public class CustomHttpClient : HttpClient
{
    public CustomHttpClient()
    {
        BaseAddress = new("http://localhost:30911");
    }
}
  1. Register the class as a scoped service in the Program.cs file, using the AddScoped method of the IServiceCollection interface. For example:
builder.Services.AddScoped<CustomHttpClient>();
  1. Whenever you need to make a request, you can inject the CustomHttpClient class using the @inject directive. For example:
@inject CustomHttpClient CustomHttpClient

Using a named HttpClient

Named HttpClient is a feature that allows you to create and manage multiple instances of the HttpClient class or derived HttpClient classes, each with its own set of configurations and settings, and distinguish between them using a unique name.

This is useful when your application needs to interact with multiple remote services or APIs, each with different requirements and characteristics, such as different timeouts, headers, base addresses, or authentication credentials. By giving each HttpClient instance a name, you can easily refer to it when making HTTP requests, and the IHttpClientFactory will take care of creating and configuring the appropriate client for you.

To create named HttpClient instances, you can follow these steps:

  1. Register your HttpClient instances in the Program.cs file and provide a unique name for each instance. For example:
builder.Services.AddHttpClient("First API", httpClient => httpClient.BaseAddress = new("http://localhost:30911"));
builder.Services.AddHttpClient("Second API", httpClient => httpClient.BaseAddress = new("http://localhost:30912"));
  1. When making a request, inject the IHttpClientFactory interface and create an instance of HttpClient by specifying its name. For example:
@inject IHttpClientFactory HttpClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        var httpClient = HttpClientFactory.CreateClient("First API");
        ...
    }
}

HttpClient wrapper is a design pattern used to wrap the HttpClient class in a custom class or interface to provide additional functionality or simplify its usage.

Using an HttpClient wrapper

By wrapping HttpClient, you can encapsulate its implementation details and provide a simpler, more focused API that is easier to use and test. This can be especially useful when you need to add common headers or handle errors in a consistent way across multiple requests.

To create an HttpClient wrapper, you can follow these steps:

  1. Create a wrapper class that takes an instance of HttpClient as a dependency, like this:
public class SecondApiHttpClientWrapper
{
    private readonly HttpClient _httpClient;

    public SecondApiHttpClientWrapper(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
}
  1. Register the wrapper class with the dependency injection container in your Program.cs, like this:
builder.Services.AddHttpClient<SecondApiHttpClientWrapper>(httpClient => httpClient.BaseAddress = new("http://localhost:30912"));
  1. In your code, inject an instance of the wrapper class and use its methods to make requests, like this:
@inject SecondApiHttpClientWrapper SecondApiHttpClientWrapper

Interfere HttpClient

Certain APIs require additional measures to interact with, such as protected JWT tokens, specific request headers, or loading indicators for requests and responses. In these cases, interfacing with HttpClient can be beneficial. There are several methods to interface with HttpClient:

  • HttpClient middleware.
  • HttpClient wrapper.
  • HttpClient extension.

HttpClient middleware

The HttpClient middleware technique is built on the chain of responsibility pattern, where multiple middleware can be applied to an HTTP request and response in a specific order. The middleware chain starts with the first middleware and ends with the last middleware, processing the request and response respectively. 

httpclient-middleware-chain.png

Follow these steps to create and use HttpClient middleware:

  1. Create a middleware class that extends the DelegatingHandler class and overrides the Send/SendAsync method. Interfering code can be added before and after sending the request.
public class FirstMiddleware : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Interfering code before sending the request
        var response = await base.SendAsync(request, cancellationToken);
        // Interfering code after sending the request

        return response;
    }
}
It's important to call base.Send() or base.SendAsync() when overriding the Send or SendAsync method respectively, to ensure the middleware chain continues without being broken.
  1. Register all the middleware as transient services in the Program.cs file.
builder.Services.AddTransient<FirstMiddleware>();
builder.Services.AddTransient<SecondMiddleware>();
  1. Register the HttpClient by using named HttpClient or HttpClient wrapper technique, then add the middleware chain in your desired sequence.
builder.Services.AddHttpClient("HttpClient with Middleware", httpClient => httpClient.BaseAddress = new("http://localhost:30911"))
       .AddHttpMessageHandler<FirstMiddleware>()
       .AddHttpMessageHandler<SecondMiddleware>();

builder.Services.AddHttpClient<SecondApiHttpClientWrapper>(httpClient => httpClient.BaseAddress = new("http://localhost:30912"))
       .AddHttpMessageHandler<FirstMiddleware>()
       .AddHttpMessageHandler<SecondMiddleware>();

HttpClient wrapper

HttpClient wrapper is another way to interfere with HTTP requests and responses. You can provide additional methods to the HttpClient wrapper to create a centralized interference code. Here is an example of how to create a method to send a request using a wrapper class:

public class InterfereByHttpClientWrapper
{
    ...
    public async Task<T?> GetAsync<T>(string requestUrl)
    {
        // Interfering code before sending the request
        var response = await _httpClient.GetFromJsonAsync<T>(requestUrl);
        // Interfering code after sending the request

        return response;
    }
}

HttpClient extension

HttpClient extension is another way to interfere with HTTP requests and responses. It allows you to add custom methods and functionality to the HttpClient class.

To create a HttpClient extension, you can create a static class with extension methods for the HttpClient class. For example:

public static class HttpClientExtension
{
    public static async Task<T?> GetResponse<T>(this HttpClient httpClient, string url)
    {
        // Interfering code before sending the request
        var result = await httpClient.GetFromJsonAsync<T>(url);
        // Interfering code after sending the request

        return result;
    }
}

How to send requests and handle responses

There are several common HTTP methods that are used to communicate with web servers. Here are some of the most common ones:

  1. GET: This method is used to retrieve information from the server. It is the most commonly used HTTP method and is used to fetch web pages, images, videos, and other resources from the server.

  2. POST: This method is used to submit data to the server. It is commonly used in web forms to send data to the server for processing.

  3. PUT: This method is used to update or replace existing data on the server. It is commonly used in RESTful API's to update resources on the server.

  4. DELETE: This method is used to delete a resource from the server. It is commonly used in RESTful API's to delete resources.

  5. HEAD: This method is similar to the GET method, but it only retrieves the headers of the response, not the body. It is commonly used to check the status of a resource without downloading the entire response.

  6. OPTIONS: This method is used to retrieve the supported HTTP methods for a resource. It is commonly used in CORS (Cross-Origin Resource Sharing) to check whether a particular origin is allowed to access a resource.

  7. PATCH: This method is used to make partial updates to a resource. It is commonly used in RESTful API's to update specific fields of a resource without replacing the entire resource.

You can find out more at https://www.w3schools.com/tags/ref_httpmethods.asp.

Sending data through a URL

It is possible to send data to an API by including it in the URL, a commonly used method with the GET request. However, it is important to note that the data sent through the URL should be a short, primitive data type. It is not recommended to send large data sets through the URL. Instead, HttpClient can be utilized to send data through the URL, as shown below:

await httpClient.GetAsync($"example/ProcessPrimitiveUrlData?data=Blazor School");

Sending primitive data in the request body

When sending primitive data in an HTTP request, the data can be included in the request body rather than in the URL. This is useful when the data being sent is larger or more complex than a simple query parameter. To send primitive data in the request body, the following code can be used:

await httpClient.PostAsync("example/ProcessPrimitiveData", new StringContent("Blazor School", Encoding.UTF8, "application/json"));

This will encode the data as a JSON string and send it to the specified API endpoint for processing.

Sending complex data in the request body

Sending complex data in the request body is a common practice in web development when exchanging data between a client (such as a web browser) and a server. Complex data can include objects, arrays, and nested data structures.

When sending complex data in the request body, the data is usually serialized into a format that can be transmitted over the network. There are several formats for serializing data, including JSON, XML, and YAML, among others. JSON (JavaScript Object Notation) is a widely used format for serializing complex data, and it is supported by most modern web development frameworks.

There are several methods available in the System.Net.Http.Json namespace that can assist in sending complex data in the request body. As an example, the following code snippet demonstrates the usage of the PostAsJsonAsync method to send a complex object in the request body:

var data = new ExampleClass()
{
    ExampleString = "Blazor School"
};
await httpClient.PostAsJsonAsync<ExampleClass>("example/ProcessComplexData", data);

Sending stream data in the request body

Sending stream data in the request body is a technique used to transmit large or continuous data streams from the client to the server over an HTTP connection. This technique is commonly used in scenarios where data is generated or consumed in real-time, such as video streaming, file uploading, or real-time sensor data.

When sending stream data in the request body, the client typically uses the HTTP POST method to send a series of data chunks to the server. The data chunks are sent in a continuous stream, without waiting for a response from the server after each chunk is sent.

The most common use case for sending stream data is to upload a file to an API. In Blazor, the InputFile component can be used to prompt the user to select a file. The selected file is then mapped to the IBrowserFile interface by the InputFile component. The file can be converted to StreamContent and sent to the server using the HttpClient class. Here is an example of how to upload a file to an API using the InputFile component in Blazor:

  1. Create a form model. For example:
public class ExampleFormModel
{
    public IBrowserFile ExampleFile { get; set; } = default!;
}
  1. Use the InputFile component:
@inject IHttpClientFactory HttpClientFactory

<EditForm Model="FormModel" OnSubmit="SubmitFormAsync">
    <InputFile OnChange="FileChanged" />
    <button>Submit</button>
</EditForm>

@code {
    public ExampleFormModel FormModel { get; set; } = new();

    public void FileChanged(InputFileChangeEventArgs args)
    {
        FormModel.ExampleFile = args.File;
    }

    public async Task SubmitFormAsync()
    {
        var httpClient = HttpClientFactory.CreateClient("Second API");
        using var formDataContent = new MultipartFormDataContent();
        using var stream = FormModel.ExampleFile.OpenReadStream(long.MaxValue);
        using var streamContent = new StreamContent(stream);
        streamContent.Headers.ContentType = new(FormModel.ExampleFile.ContentType);
        formDataContent.Add(streamContent, "FileStream", FormModel.ExampleFile.Name);
        var message = await httpClient.PostAsync("example/ProcessStreamdata", formDataContent);
        // Further logic
    }
}

In the above example, the formDataContent only contains a single file. However, you can add multiple files to the formDataContent by calling the Add method on the content object multiple times, each time with a different file and a unique name. This way, you can send multiple files in a single request to the server.

Handling primitive data in the response

After sending a request to the API, the API will process the request and return a response to the HttpClient. Sometimes, the response will contain data, while other times it won't. To handle a response that contains data, you can use the following code:

var response = await httpClient.GetAsync("example/ReturnPrimitiveData");

if(response.IsSuccessStatusCode)
{
    DataReceived = await response.Content.ReadAsStringAsync();
}
else
{
    DataReceived = "Failed";
}

Automatically deserialize response data to object using Microsoft library

You can automatically deserialize JSON response data to a C# object using the Microsoft library System.Net.Http.Json. This library leverages System.Text.Json to handle the deserialization. Here's an example implementation:

@inject IHttpClientFactory HttpClientFactory

<div>Data Received: @ExampleInstance.ExampleString</div>

@code {
    public ExampleClass ExampleInstance { get; set; } = new();

    protected override async Task OnInitializedAsync()
    {
        var httpClient = HttpClientFactory.CreateClient("Second API");
        ExampleInstance = await httpClient.GetFromJsonAsync<ExampleClass>("example/ReturnComplexData") ?? new();
    }
}

Deserialize response data to object using Newtonsoft library

Newtonsoft.Json is a popular third-party library for working with JSON data in .NET applications. It provides a comprehensive set of classes and methods for serializing and deserializing JSON data to and from .NET objects, as well as working with JSON documents directly. Newtonsoft.Json is often considered more user-friendly and flexible than System.Text.Json, with a wider range of configuration options and a more intuitive API. Here's an example implementation:

@using Newtonsoft.Json
@inject IHttpClientFactory HttpClientFactory

<div>Data Received: @ExampleInstance.ExampleString</div>

@code {
    public ExampleClass ExampleInstance { get; set; } = new();

    protected override async Task OnInitializedAsync()
    {
        var httpClient = HttpClientFactory.CreateClient("Second API");
        var response = await httpClient.GetAsync("example/ReturnComplexData");

        if(response.IsSuccessStatusCode)
        {
            string responseContent = await response.Content.ReadAsStringAsync();
            ExampleInstance = JsonConvert.DeserializeObject<ExampleClass>(responseContent) ?? new();
        }
    }
}

To avoid repeating the same code, you can follow one of the techniques in the Interfere HttpClient section.

Handling stream data in the response

When an API returns a stream, you can process it using the ReadAsStreamAsync() method. Here's an example code that processes an image file stream:

@inject IHttpClientFactory HttpClientFactory

<img src="@ImageSrc"/>

@code {
    public string ImageSrc { get; set; } = "";

    protected override async Task OnInitializedAsync()
    {
        var httpClient = HttpClientFactory.CreateClient("Second API");
        var response = await httpClient.GetAsync("example/ReturnStreamData");

        if(response.IsSuccessStatusCode)
        {
            using var stream = await response.Content.ReadAsStreamAsync();
            byte[] buffer = new byte[stream.Length];
            await stream.ReadAsync(buffer, 0, (int)stream.Length);
            string base64 = Convert.ToBase64String(buffer);
            ImageSrc = $"data:image/png;base64,{base64}";
        }

        StateHasChanged();
    }
}
Note that you need to process the stream according to its type, and it may not always be an image file stream as in the example.

Key differences between Blazor WebAssembly and Blazor Server

In Blazor WebAssembly, requests are sent from the browser, and they can contain client-side information such as cookies.

On the other hand, in Blazor Server, requests are sent from the server, and they do not contain client-side information.

BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2024 Blazor School
An unhandled error has occurred. Reload 🗙