API interaction

🟨 Slightly different to Blazor WebAssembly

APIs are an essential component of modern web development. While interacting with third-party APIs is a common use case, it is important to be aware of the potential risks and limitations of interacting with first-party APIs in Blazor Server. Interacting with third-party APIs can provide valuable functionality and data for your application. However, interacting with first-party APIs, such as those provided by the same server hosting your Blazor Server application, can introduce security vulnerabilities and create potential performance issues. Therefore, it is generally recommended to avoid direct interaction with first-party APIs in Blazor Server. In this tutorial, we will focus on interacting with third-party APIs and explore the various options and best practices for integrating them into your Blazor Server application. 

  • What is first-party API?
  • Why designing a first-party web API for Blazor Server is not recommended
  • How to register HttpClient for a single API
  • How to register HttpClient for multiple APIs
  • Interfere HttpClient
  • How to send requests and handle responses
  • Common mistakes
  • Key differences between Blazor WebAssembly and Blazor Server
You can download the example code used in this topic on GitHub.

What is first-party API?

A first-party API is an API that is created and maintained by the same organization that also maintains the application that uses the API. In the context of Blazor Server, a first-party API would typically be an API created by the same team or organization that also develops and hosts the Blazor Server application.

In contrast, a third-party API is an API created and maintained by a separate organization that is not affiliated with the team or organization that is developing and hosting the application that uses the API. For example, a weather API provided by a third-party service would be a third-party API for a Blazor Server application that uses it.


Why designing a first-party web API for Blazor Server is not recommended

Designing a first-party web API for your Blazor Server project is generally not recommended due to several reasons that can negatively impact your project:

  • Authentication and authorization must be implemented twice, as the Blazor Server code executed on the server side is already secure. Adding another layer of authentication and authorization is unnecessary and can lead to complications.
  • Requests are sent from the server of Blazor Server to the API, which means that you cannot directly access cookies, request headers, parameters, and browser-related information. Instead, you will need to pass this information to the server of Blazor Server, which in turn will pass it to the API server. This can create potential performance issues and repetitive code.
  • Blazor Server is a single-page application (SPA), which means that there is only one request to the Blazor Server's server. This can cause issues with reflecting updates made to the API on the user's end, as the user would need to refresh the website to see the changes.
  • Using a first-party API means that you will need to scale both the Blazor Server host and the API host. Without a first-party API, you only need to scale the Blazor Server host, which can simplify the deployment and management of your project.

If you are considering designing a first-party API for your Blazor project, it is recommended to use Blazor WebAssembly instead. This can help avoid the issues mentioned above and provide a smoother user experience.

However, if you already have an API and want to reuse the logic in the API, you can split the logic into a Class Library project and then use that project in your Blazor Server website. This allows you to reuse the existing logic while avoiding the security vulnerabilities and performance issues associated with first-party APIs.

To use the Class Library project in your Blazor Server website, you can add a reference to the project and use the logic in your Blazor components. This approach can help you maintain a separation of concerns and ensure that your code is modular and easy to maintain.


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 Server application.


How to register HttpClient for multiple APIs

When working with multiple APIs in Blazor Server, 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.

Common mistakes

When interacting with an API in Blazor Server, there are some common mistakes that you need to avoid. These include:

  • Using a first-party API, which can introduce security vulnerabilities and performance issues.
  • Attempting to extend the HttpClient class in Blazor Server. While this technique can be used in Blazor WebAssembly, it is not recommended in Blazor Server. The AddHttpClient method does not support a derived HttpClient class, and you will need to manage the lifecycle of your extended class manually.

Failing to manage the lifecycle of your extended HttpClient class correctly can cause socket exhaustion, which can impact the performance of your Blazor Server website host.

Consider the following code:

@page "/extend-http-client"

<h3>ExtendHttpClient</h3>
<div>@((MarkupString)Log)</div>

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

    protected override async Task OnInitializedAsync()
    {
        foreach (int index in Enumerable.Range(1, 10))
        {
            using var httpClient = new HttpClient();
            var response = await httpClient.GetAsync("https://blazorschool.com");

            if(response.IsSuccessStatusCode)
            {
                Log += $"Request {index} was sucessfully.<br/>";
            }
            else
            {
                Log += $"Request {index} was failed.<br/>";
            }
        }
    }
}

If you don't properly manage the lifecycle of the HttpClient objects, it can lead to socket exhaustion. To prevent this issue, you should use the HttpClientFactory to create and manage the lifecycle of your HttpClient objects.

In the following video, you can see an example of what can happen if you don't properly manage the lifecycle of your HttpClient objects in Blazor Server:


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 🗙