🟨 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.
HttpClient
for a single APIHttpClient
for multiple APIsHttpClient
You can download the example code used in this topic on GitHub.
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.
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:
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.
HttpClient
for a single APIIf 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.
HttpClient
for multiple APIsWhen 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:
HttpClient
HttpClient
HttpClient
wrapperEach 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.
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:
HttpClient
class and sets the BaseAddress
property in its constructor. For example:public class CustomHttpClient : HttpClient { public CustomHttpClient() { BaseAddress = new("http://localhost:30911"); } }
AddScoped
method of the IServiceCollection
interface. For example:builder.Services.AddScoped<CustomHttpClient>();
CustomHttpClient
class using the @inject
directive. For example:@inject CustomHttpClient CustomHttpClient
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:
builder.Services.AddHttpClient("First API", httpClient => httpClient.BaseAddress = new("http://localhost:30911")); builder.Services.AddHttpClient("Second API", httpClient => httpClient.BaseAddress = new("http://localhost:30912"));
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.
HttpClient
wrapperBy 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:
HttpClient
as a dependency, like this:public class SecondApiHttpClientWrapper { private readonly HttpClient _httpClient; public SecondApiHttpClientWrapper(HttpClient httpClient) { _httpClient = httpClient; } }
builder.Services.AddHttpClient<SecondApiHttpClientWrapper>(httpClient => httpClient.BaseAddress = new("http://localhost:30912"));
@inject SecondApiHttpClientWrapper SecondApiHttpClientWrapper
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
middlewareThe 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.
Follow these steps to create and use HttpClient
middleware:
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 callbase.Send()
orbase.SendAsync()
when overriding theSend
orSendAsync
method respectively, to ensure the middleware chain continues without being broken.
builder.Services.AddTransient<FirstMiddleware>(); builder.Services.AddTransient<SecondMiddleware>();
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
wrapperHttpClient
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
extensionHttpClient
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; } }
There are several common HTTP methods that are used to communicate with web servers. Here are some of the most common ones:
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.
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.
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.
DELETE: This method is used to delete a resource from the server. It is commonly used in RESTful API's to delete resources.
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.
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.
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.
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");
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 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 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:
public class ExampleFormModel { public IBrowserFile ExampleFile { get; set; } = default!; }
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.
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"; }
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(); } }
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.
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.
When interacting with an API in Blazor Server, there are some common mistakes that you need to avoid. These include:
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:
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.