You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Blazor/Documentation/docusaurus/docs/api/make-http-requests.md

448 lines
14 KiB

---
sidebar_position: 3
title: Make HTTP requests
---
## Data format
Two operations exist to retrieve data from an API, the first is to retrieve raw data in JSON format, the second is to use serialization / deserialization.
In our case, the library used implements serialization / deserialization by default.
In order to be able to manipulate our data we will use our `Item` component class locally, the data received comes from the serialization of the `Item` class of the server.
```csharp title="Models/Item.cs"
public class Item
{
public int Id { get; set; }
public string DisplayName { get; set; }
public string Name { get; set; }
public int StackSize { get; set; }
public int MaxDurability { get; set; }
public List<string> EnchantCategories { get; set; }
public List<string> RepairWith { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
public string ImageBase64 { get; set; }
}
```
:::info
Adding or deleting fields in our model does not generate an error when retrieving data.
:::
## Using the IOC
Thanks to our IOC we will therefore call our API in a specific service implementing the `IDataService` interface.
Create the `DataApiService` class:
```csharp title="Services/DataApiService.cs"
public class DataApiService : IDataService
{
private readonly HttpClient _http;
public DataApiService(
HttpClient http)
{
_http = http;
}
public async Task Add(ItemModel model)
{
// Get the item
var item = ItemFactory.Create(model);
// Save the data
await _http.PostAsJsonAsync("https://localhost:7234/api/Crafting/", item);
}
public async Task<int> Count()
{
return await _http.GetFromJsonAsync<int>("https://localhost:7234/api/Crafting/count");
}
public async Task<List<Item>> List(int currentPage, int pageSize)
{
return await _http.GetFromJsonAsync<List<Item>>($"https://localhost:7234/api/Crafting/?currentPage={currentPage}&pageSize={pageSize}");
}
public async Task<Item> GetById(int id)
{
return await _http.GetFromJsonAsync<Item>($"https://localhost:7234/api/Crafting/{id}");
}
public async Task Update(int id, ItemModel model)
{
// Get the item
var item = ItemFactory.Create(model);
await _http.PutAsJsonAsync($"https://localhost:7234/api/Crafting/{id}", item);
}
public async Task Delete(int id)
{
await _http.DeleteAsync($"https://localhost:7234/api/Crafting/{id}");
}
public async Task<List<CraftingRecipe>> GetRecipes()
{
return await _http.GetFromJsonAsync<List<CraftingRecipe>>("https://localhost:7234/api/Crafting/recipe");
}
}
```
We now have a class allowing us to pass all of our calls through an API.
## Register the data service
Open the `Program.cs` file and edit the following line:
```csharp title="Program.cs"
...
builder.Services.AddScoped<IDataService, DataApiService>();
...
```
## Add the API sample to your project
Download this [project](/Minecraft.Crafting.Api.zip).
Unzip the file in the directory of your project, at the same place of the directory of the Blazor Project.
Example:
![Sample Api Location](/img/api/sample-api-location.png)
On Visual Studio, click right on the solution and choose `Add => Existing Project...`
![Add Existing Project](/img/api/add-existing-project.png)
Select the file `Minecraft.Crafting.Api.csproj` in the directory `Minecraft.Crafting.Api`.
Your solution now contains two projects, your client and the sample API.
## How to start two projects at same time in Visual Studio
For test your client with the API, you have to start the two projects at same time.
For this, on Visual Studio, click right on the solution and choose `Properties`
![Solution Properties](/img/api/solution-properties.png)
On the new screen select "Multiple startup projects" and select "Start" for the two projects.
![Multiple startup projects](/img/api/multiple-startup-projects.png)
When you start debug mode, the two projects are started.
## Concept: IHttpClientFactory
An `IHttpClientFactory` can be registered and used to configure and create HttpClient instances in an app.
`IHttpClientFactory` offers the following benefits:
* Provides a central location for naming and configuring logical `HttpClient` instances. For example, a client named github could be registered and configured to access GitHub. A default client can be registered for general access.
* Codifies the concept of outgoing middleware via delegating handlers in `HttpClient`. Provides extensions for Polly-based middleware to take advantage of delegating handlers in `HttpClient`.
* Manages the pooling and lifetime of underlying `HttpClientMessageHandler` instances. Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing `HttpClient` lifetimes.
* Adds a configurable logging experience (via `ILogger`) for all requests sent through clients created by the factory.
### Consumption patterns
There are several ways IHttpClientFactory can be used in an app:
* Basic usage
* Named clients
* Typed clients
* Generated clients
The best approach depends upon the app's requirements.
#### Basic usage
Register `IHttpClientFactory` by calling `AddHttpClient` in `Program.cs`:
```csharp
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// highlight-next-line
builder.Services.AddHttpClient();
```
An `IHttpClientFactory` can be requested using dependency injection (DI). The following code uses `IHttpClientFactory` to create an `HttpClient` instance:
```csharp
public class BasicModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
// highlight-next-line
public BasicModel(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};
// highlight-next-line
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
```
Using `IHttpClientFactory` like in the preceding example is a good way to refactor an existing app.
It has no impact on how `HttpClient` is used. In places where `HttpClient` instances are created in an existing app, replace those occurrences with calls to `CreateClient`.
#### Named clients
Named clients are a good choice when:
* The app requires many distinct uses of `HttpClient`.
* Many `HttpClients` have different configuration.
Specify configuration for a named `HttpClient` during its registration in `Program.cs`:
```csharp
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
```
In the preceding code the client is configured with:
* The base address https://api.github.com/.
* Two headers required to work with the GitHub API.
##### CreateClient
Each time CreateClient is called:
* A new instance of `HttpClient` is created.
* The configuration action is called.
To create a named client, pass its name into `CreateClient`:
```csharp
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public NamedClientModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
// highlight-next-line
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
```
In the preceding code, the request doesn't need to specify a hostname. The code can pass just the path, since the base address configured for the client is used.
#### Typed clients
Typed clients:
* Provide the same capabilities as named clients without the need to use strings as keys.
* Provides IntelliSense and compiler help when consuming clients.
* Provide a single location to configure and interact with a particular `HttpClient`. For example, a single typed client might be used:
* For a single backend endpoint.
* To encapsulate all logic dealing with the endpoint.
* Work with DI and can be injected where required in the app.
A typed client accepts an `HttpClient` parameter in its constructor:
```csharp
public class GitHubService
{
private readonly HttpClient _httpClient;
// highlight-next-line
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}
```
In the preceding code:
* The configuration is moved into the typed client.
* The provided `HttpClient` instance is stored as a private field.
API-specific methods can be created that expose `HttpClient` functionality. For example, the `GetAspNetCoreDocsBranches` method encapsulates code to retrieve docs GitHub branches.
The following code calls `AddHttpClient` in `Program.cs` to register the `GitHubService` typed client class:
```csharp
builder.Services.AddHttpClient<GitHubService>();
```
The typed client is registered as transient with DI. In the preceding code, `AddHttpClient` registers `GitHubService` as a transient service. This registration uses a factory method to:
* Create an instance of `HttpClient`.
* Create an instance of `GitHubService`, passing in the instance of `HttpClient` to its constructor.
The typed client can be injected and consumed directly:
```csharp
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
// highlight-next-line
public TypedClientModel(GitHubService gitHubService) => _gitHubService = gitHubService;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
// highlight-next-line
GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}
```
The configuration for a typed client can also be specified during its registration in `Program.cs`, rather than in the typed client's constructor:
```csharp
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
```
#### Generated clients
`IHttpClientFactory` can be used in combination with third-party libraries such as Refit. Refit is a REST library for .NET. It converts REST APIs into live interfaces.
Call `AddRefitClient` to generate a dynamic implementation of an interface, which uses `HttpClient` to make the external HTTP calls.
A custom interface represents the external API:
```csharp
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
```
Call `AddRefitClient` to generate the dynamic implementation and then call `ConfigureHttpClient` to configure the underlying `HttpClient`:
```csharp
// highlight-next-line
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
```
Use DI to access the dynamic implementation of `IGitHubClient`:
```csharp
public class RefitModel : PageModel
{
private readonly IGitHubClient _gitHubClient;
// highlight-next-line
public RefitModel(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
// highlight-next-line
GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}
```
### HttpClient request type
HttpClient supports other HTTP verbs:
| Properties | Verbe |
| ---- | ---- |
| Delete | Represents an HTTP DELETE protocol method. |
| Get | Represents an HTTP GET protocol method. |
| Head | Represents an HTTP HEAD protocol method. The HEAD method is identical to GET except that the server only returns message-headers in the response, without a message-body. |
| Method | An HTTP method. |
| Options | Represents an HTTP OPTIONS protocol method. |
| Patch | Gets the HTTP PATCH protocol method. |
| Post | Represents an HTTP POST protocol method that is used to post a new entity as an addition to a URI. |
| Put | Represents an HTTP PUT protocol method that is used to replace an entity identified by a URI. |
| Trace | Represents an HTTP TRACE protocol method. |