diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 58e793f25d78..1541d00b521b 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -5,7 +5,7 @@ description: Learn how to call a web API from Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 11/11/2025 +ms.date: 12/18/2025 uid: blazor/call-web-api --- # Call a web API from ASP.NET Core Blazor @@ -14,390 +14,130 @@ uid: blazor/call-web-api This article describes how to call a web API from a Blazor app. -## Package - -The [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json) package provides extension methods for and that perform automatic serialization and deserialization using [`System.Text.Json`](https://www.nuget.org/packages/System.Text.Json). The `System.Net.Http.Json` package is provided by the .NET shared framework and doesn't require adding a package reference to the app. - -:::moniker range=">= aspnetcore-8.0" - -## Use a token handler for web API calls - -Blazor Web Apps with OIDC authentication can use a token handler approach to make outgoing requests to secure external web API calls. This approach is used by the `BlazorWebAppOidc` and `BlazorWebAppOidcServer` sample apps described in the *Sample apps* section of this article. - -For more information, see the following resources: - -* -* *Secure an ASP.NET Core Blazor Web App with OpenID Connect (OIDC)* - * [Without YARP and Aspire (Interactive Auto)](xref:blazor/security/blazor-web-app-oidc?pivots=without-yarp-and-aspire) - * [Without YARP and Aspire (Interactive Server)](xref:blazor/security/blazor-web-app-oidc?pivots=without-yarp-and-aspire-server) - -## Microsoft identity platform for web API calls - -Blazor Web Apps that use use [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) can make streamlined web API calls with API provided by the [`Microsoft.Identity.Web.DownstreamApi` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web.DownstreamApi). - -[!INCLUDE[](~/includes/package-reference.md)] - -In the app settings file (`appsettings.json`), provide a base URL and scopes. In the following example, the `{BASE ADDRESS}` placeholder is the base URL of the web API. A single scope is specified with an App ID URI (`{APP ID URI}` placeholder) and scope name (`{SCOPE NAME}` placeholder): - -```json -"DownstreamApi": { - "BaseUrl": "{BASE ADDRESS}", - "Scopes": [ "{APP ID URI}/{SCOPE NAME}" ] -} -``` - -Example: - -```json -"DownstreamApi": { - "BaseUrl": "https://localhost:7277", - "Scopes": [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ] -} -``` - -In the app's `Program` file, call: +## Server-side scenarios for calling external web APIs - +Server-based components call external web APIs using instances, typically created using . For guidance that applies to server-side apps, see . -* : Enables token acquisition to call web APIs. -* `AddDownstreamApi`: Microsoft Identity Web packages provide API to create a named downstream web service for making web API calls. is injected into a server-side class, which is used to call to obtain weather data from an external web API (`MinimalApiJwt` project). -* : Adds the .NET distributed token caches to the service collection. -* : Adds a default implementation of that stores cache items in memory. -* Configure the distributed token cache options (): - * In development for debugging purposes, you can disable the L1 cache by setting to `true`. ***Be sure to reset it back to `false` for production.*** - * Set the maximum size of your L1 cache with [`L1CacheOptions.SizeLimit`](xref:Microsoft.Extensions.Caching.Memory.MemoryCacheOptions.SizeLimit%2A) to prevent the cache from overrunning the server's memory. The default value is 500 MB. - * In development for debugging purposes, you can disable token encryption at rest by setting to `false`, which is the default value. ***Be sure to reset it back to `true` for production.*** - * Set token eviction from the cache with . The default value is 1 hour. - * For more information, including guidance on the callback for L2 cache failures () and asynchronous L2 cache writes (), see and [Token cache serialization: Distributed token caches](/entra/msal/dotnet/how-to/token-cache-serialization#distributed-token-caches). +A server-side app doesn't include an service. Provide an to the app using the [`HttpClient` factory infrastructure](xref:fundamentals/http-requests). -You can choose to encrypt the cache and should always do so in production. +In the `Program` file: ```csharp -builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) - .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) - .EnableTokenAcquisitionToCallDownstreamApi() - .AddDownstreamApi("DownstreamApi", - builder.Configuration.GetSection("DownstreamApi")) - .AddDistributedTokenCaches(); - -// Requires the 'Microsoft.Extensions.Caching.Memory' NuGet package -builder.Services.AddDistributedMemoryCache(); - -builder.Services.Configure( - options => - { - // The following lines that are commented out reflect - // default values. We recommend overriding the default - // value of Encrypt to encrypt tokens at rest. - - //options.DisableL1Cache = false; - //options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024; - options.Encrypt = true; - //options.SlidingExpiration = TimeSpan.FromHours(1); - }); +builder.Services.AddHttpClient(); ``` -In-memory distributed token caches are created when calling to ensure that there's a base implementation available for distributed token caching. - -Production web apps and web APIs should use a production distributed token cache (for example: [Redis](https://redis.io/), [Microsoft SQL Server](https://www.microsoft.com/sql-server), [Microsoft Azure Cosmos DB](https://azure.microsoft.com/products/cosmos-db)). - -> [!NOTE] -> For local development and testing on a single machine, you can use in-memory token caches instead of distributed token caches: -> -> ```csharp -> builder.Services.AddInMemoryTokenCaches(); -> ``` -> -> Later in the development and testing period, adopt a production distributed token cache provider. - - adds a default implementation of that stores cache items in memory, which is used by Microsoft Identity Web for token caching. - - requires a package reference to the [`Microsoft.Extensions.Caching.Memory` NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory). - -[!INCLUDE[](~/includes/package-reference.md)] - -To configure a production distributed cache provider, see . - -> [!WARNING] -> Always replace the in-memory distributed token caches with a real token cache provider when deploying the app to a production environment. If you fail to adopt a production distributed token cache provider, the app may suffer significantly degraded performance. - -For more information, see [Token cache serialization: Distributed caches](/entra/msal/dotnet/how-to/token-cache-serialization?tabs=msal#distributed-caches). However, the code examples shown don't apply to ASP.NET Core apps, which configure distributed caches via , not . - - - -Use a shared Data Protection key ring in production so that instances of the app across servers in a web farm can decrypt tokens when is set to `true`. - -> [!NOTE] -> For early development and local testing on a single machine, you can set to `false` and configure a shared Data Protection key ring later: -> -> ```csharp -> options.Encrypt = false; -> ``` -> -> Later in the development and testing period, enable token encryption and adopt a shared Data Protection key ring. - -The following example shows how to use [Azure Blob Storage and Azure Key Vault (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using the Azure services' dedicated documentation sets, which are linked at the end of this section. - -Add the following packages to the server project of the Blazor Web App: - -* [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) -* [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) - -[!INCLUDE[](~/includes/package-reference.md)] - -> [!NOTE] -> Before proceeding with the following steps, confirm that the app is registered with Microsoft Entra. - -Configure Azure Blob Storage to maintain data protection keys. Follow the guidance in . +The following Razor component makes a request to a web API for GitHub branches similar to the *Basic Usage* example in the article. -Configure Azure Key Vault to encrypt the data protection keys at rest. Follow the guidance in . +`CallWebAPI.razor`: -Use the following code in the `Program` file where services are registered: +```razor +@page "/call-web-api" +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory -```csharp -TokenCredential? credential; +

Call web API from a server-side Razor component

-if (builder.Environment.IsProduction()) +@if (getBranchesError || branches is null) { - credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); +

Unable to get branches from GitHub. Please try again later.

} else { - // Local development and testing only - DefaultAzureCredentialOptions options = new() - { - // Specify the tenant ID to use the dev credentials when running the app locally - // in Visual Studio. - VisualStudioTenantId = "{TENANT ID}", - SharedTokenCacheTenantId = "{TENANT ID}" - }; - - credential = new DefaultAzureCredential(options); +
    + @foreach (var branch in branches) + { +
  • @branch.Name
  • + } +
} -builder.Services.AddDataProtection() - .SetApplicationName("{APPLICATION NAME}") - .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) - .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); -``` - -`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). - -`{TENANT ID}`: Tenant ID. - -`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. - -`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. - -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The key identifier is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). - -> [!NOTE] -> In non-`Production` environments, the preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). +@code { + private IEnumerable? branches = []; + private bool getBranchesError; + private bool shouldRender; -Inject and call when calling on behalf of a user: + protected override bool ShouldRender() => shouldRender; -```csharp -internal sealed class ServerWeatherForecaster(IDownstreamApi downstreamApi) : IWeatherForecaster -{ - public async Task> GetWeatherForecastAsync() + protected override async Task OnInitializedAsync() { - var response = await downstreamApi.CallApiForUserAsync("DownstreamApi", - options => - { - options.RelativePath = "/weather-forecast"; - }); - - return await response.Content.ReadFromJsonAsync() ?? - throw new IOException("No weather forecast!"); - } -} -``` - -This approach is used by the `BlazorWebAppEntra` and `BlazorWebAppEntraBff` sample apps described in the *Sample apps* section of this article. - -For more information, see the following resources: - -* -* -* [Use the Azure SDK for .NET in ASP.NET Core apps](/dotnet/azure/sdk/aspnetcore-guidance?tabs=api) -* [Web API documentation | Microsoft identity platform](/entra/identity-platform/index-web-api) -* [A web API that calls web APIs: Call an API: Option 2: Call a downstream web API with the helper class](/entra/identity-platform/scenario-web-api-call-api-call-api?tabs=aspnetcore#option-2-call-a-downstream-web-api-with-the-helper-class) -* -* *Secure an ASP.NET Core Blazor Web App with Microsoft Entra ID* - * [With YARP and Aspire (Interactive Auto)](xref:blazor/security/blazor-web-app-entra?pivots=with-yarp-and-aspire) - * [Without YARP and Aspire (Interactive Auto)](xref:blazor/security/blazor-web-app-entra?pivots=without-yarp-and-aspire) -* [Host ASP.NET Core in a web farm: Data Protection](xref:host-and-deploy/web-farm#data-protection) -* [Azure Key Vault documentation](/azure/key-vault/general/) -* [Azure Storage documentation](/azure/storage/) -* [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli) - -## Sample apps - -For working examples, see the following sample apps in the [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples/) ([how to download](xref:blazor/fundamentals/index#sample-apps)). + using var request = new HttpRequestMessage(HttpMethod.Get, + "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches"); + request.Headers.Add("Accept", "application/vnd.github.v3+json"); + request.Headers.Add("User-Agent", "HttpClientFactory-Sample"); -### `BlazorWebAppCallWebApi` + var client = ClientFactory.CreateClient(); -Call an external (not in the Blazor Web App) todo list web API from a Blazor Web App: + using var response = await client.SendAsync(request); -* `Backend`: A web API app for maintaining a todo list, based on [Minimal APIs](xref:fundamentals/minimal-apis). The web API app is a separate app from the Blazor Web App, possibly hosted on a different server. -* `BlazorApp`/`BlazorApp.Client`: A Blazor Web App that calls the web API app with an for todo list operations, such as creating, reading, updating, and deleting (CRUD) items from the todo list. + if (response.IsSuccessStatusCode) + { + using var responseStream = await response.Content.ReadAsStreamAsync(); + branches = await JsonSerializer.DeserializeAsync + >(responseStream); + } + else + { + getBranchesError = true; + } -For client-side rendering (CSR), which includes Interactive WebAssembly components and Auto components that have adopted CSR, calls are made with a preconfigured registered in the `Program` file of the client project (`BlazorApp.Client`): + shouldRender = true; + } -```csharp -builder.Services.AddScoped(sp => - new HttpClient + public class GitHubBranch { - BaseAddress = new Uri(builder.Configuration["FrontendUrl"] ?? - "https://localhost:5002") - }); -``` - -For server-side rendering (SSR), which includes prerendered and interactive Server components, prerendered WebAssembly components, and Auto components that are prerendered or have adopted SSR, calls are made with an registered in the `Program` file of the server project (`BlazorApp`): - -```csharp -builder.Services.AddHttpClient(); -``` - -Call an internal (inside the Blazor Web App) movie list API, where the API resides in the server project of the Blazor Web App: - -* `BlazorApp`: A Blazor Web App that maintains a movie list: - * When operations are performed on the movie list within the app on the server, ordinary API calls are used. - * When API calls are made by a web-based client, a web API is used for movie list operations, based on [Minimal APIs](xref:fundamentals/minimal-apis). -* `BlazorApp.Client`: The client project of the Blazor Web App, which contains Interactive WebAssembly and Auto components for user management of the movie list. - -For CSR, which includes Interactive WebAssembly components and Auto components that have adopted CSR, calls to the API are made via a client-based service (`ClientMovieService`) that uses a preconfigured registered in the `Program` file of the client project (`BlazorApp.Client`). Because these calls are made over a public or private web, the movie list API is a *web API*. - -The following example obtains a list of movies from the `/movies` endpoint: - -```csharp -public class ClientMovieService(HttpClient http) : IMovieService -{ - public async Task GetMoviesAsync(bool watchedMovies) => - await http.GetFromJsonAsync("movies") ?? []; -} -``` - -For SSR, which includes prerendered and interactive Server components, prerendered WebAssembly components, and Auto components that are prerendered or have adopted SSR, calls are made directly via a server-based service (`ServerMovieService`). The API doesn't rely on a network, so it's a standard API for movie list CRUD operations. - -The following example obtains a list of movies: - -```csharp -public class ServerMovieService(MovieContext db) : IMovieService -{ - public async Task GetMoviesAsync(bool watchedMovies) => - watchedMovies ? - await db.Movies.Where(t => t.IsWatched).ToArrayAsync() : - await db.Movies.ToArrayAsync(); + [JsonPropertyName("name")] + public string? Name { get; set; } + } } ``` -For more information on how to secure movie data in this scenario, see the weather data example described by [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering). - -### `BlazorWebAppCallWebApi_Weather` - -A weather data sample app that uses streaming rendering for weather data. - -### `BlazorWebAssemblyCallWebApi` - -Calls a todo list web API from a Blazor WebAssembly app: - -* `Backend`: A web API app for maintaining a todo list, based on [Minimal APIs](xref:fundamentals/minimal-apis). -* `BlazorTodo`: A Blazor WebAssembly app that calls the web API with a preconfigured for todo list CRUD operations. - -### `BlazorWebAssemblyStandaloneWithIdentity` - -A standalone Blazor WebAssembly app secured with ASP.NET Core Identity: - -* `Backend`: A backend web API app that maintains a user identity store for ASP.NET Core Identity. -* `BlazorWasmAuth`: A standalone Blazor WebAssembly frontend app with user authentication. - -The solution demonstrates calling a secure web API for the following: - -* Obtaining an authenticated user's roles. -* Data processing for all authenticated users. -* Data processing for authorized users (the user must be in the `Manager` role) via an [authorization policy](xref:security/authorization/policies). - -### `BlazorWebAppOidc` - -A Blazor Web App with global Auto interactivity that uses OIDC authentication with Microsoft Entra without using Entra-specific packages. The sample demonstrates how to [use a token handler for web API calls](xref:blazor/security/additional-scenarios#use-a-token-handler-for-web-api-calls) to call an external secure web API. - -### `BlazorWebAppOidcServer` - -A Blazor Web App with global Interactive Server interactivity that uses OIDC authentication with Microsoft Entra without using Entra-specific packages. The sample demonstrates how to [pass an access token](xref:blazor/security/additional-scenarios#use-a-token-handler-for-web-api-calls) to call an external secure web API. - -### `BlazorWebAppOidcBff` - -A Blazor Web App with global Auto interactivity that uses: +In the preceding example for C# 12 or later, an empty array (`[]`) is created for the `branches` variable. For earlier versions of C# compiled with an SDK earlier than .NET 8, create an empty array (`Array.Empty()`). -* OIDC authentication with Microsoft Entra without using Entra-specific packages. -* The [Backend for Frontend (BFF) pattern](/azure/architecture/patterns/backends-for-frontends), which is a pattern of app development that creates backend services for frontend apps or interfaces. +For an additional working example, see the server-side file upload example that uploads files to a web API controller in the article. -The solution includes a demonstration of obtaining weather data securely via an external web API when a component that adopts Interactive Auto rendering is rendered on the client. +:::moniker range=">= aspnetcore-8.0" -:::moniker-end +## Service abstractions for web API calls -:::moniker range=">= aspnetcore-9.0" +*This section applies to Blazor Web Apps that maintain a web API in the server project or transform web API calls to an external web API.* -### `BlazorWebAppEntra` +When using the interactive WebAssembly and Auto render modes, components are prerendered by default. Auto components are also initially rendered interactively from the server before the Blazor bundle downloads to the client and the client-side runtime activates. This means that components using these render modes should be designed so that they run successfully from both the client and the server. If the component must call a server project-based API or transform a request to an external web API (one that's outside of the Blazor Web App) when running on the client, the recommended approach is to abstract that API call behind a service interface and implement client and server versions of the service: -A Blazor Web App with global Auto interactivity that uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra). The solution includes a demonstration of obtaining weather data securely via an external web API when a component that adopts Interactive Auto rendering is rendered on the client. +* The client version calls the web API with a preconfigured . +* The server version can typically access the server-side resources directly. Injecting an on the server that makes calls back to the server isn't recommended, as the network request is typically unnecessary. Alternatively, the API might be external to the server project, but a service abstraction for the server is required to transform the request in some way, for example to add an access token to a proxied request. -### `BlazorWebAppEntraBff` +When using the WebAssembly render mode, you also have the option of disabling prerendering, so the components only render from the client. For more information, see . -A Blazor Web App with global Auto interactivity that uses: +Examples ([sample apps](#sample-apps)): -* [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra). -* The [Backend for Frontend (BFF) pattern](/azure/architecture/patterns/backends-for-frontends), which is a pattern of app development that creates backend services for frontend apps or interfaces. +* Movie list web API in the `BlazorWebAppCallWebApi` sample app. +* Streaming rendering weather data web API in the `BlazorWebAppCallWebApi_Weather` sample app. +* Weather data returned to the client in either the `BlazorWebAppOidc` (non-BFF pattern) or `BlazorWebAppOidcBff` (BFF pattern) sample apps. These apps demonstrate secure (web) API calls. For more information, see . -The solution includes a demonstration of obtaining weather data securely via an external web API when a component that adopts Interactive Auto rendering is rendered on the client. +## Blazor Web App external web APIs -:::moniker-end +*This section applies to Blazor Web Apps that call a web API maintained by a separate (external) project, possibly hosted on a different server.* -## Disposal of `HttpRequestMessage`, `HttpResponseMessage`, and `HttpClient` +Blazor Web Apps normally prerender client-side WebAssembly components, and Auto components render on the server during static or interactive server-side rendering (SSR). services aren't registered by default in a Blazor Web App's main project. If the app is run with only the services registered in the `.Client` project, as described in the [Add the `HttpClient` service](#add-the-httpclient-service) section, executing the app results in a runtime error: -An without a body doesn't require explicit disposal. However, you can dispose of it with either of the following patterns: +> :::no-loc text="InvalidOperationException: Cannot provide a value for property 'Http' on type '...{COMPONENT}'. There is no registered service of type 'System.Net.Http.HttpClient'."::: -* `using` declaration (C# 8 or later): +Use ***either*** of the following approaches: - ```csharp - using var request = new HttpRequestMessage(...); - ``` - -* [`using` block (all C# releases)](/dotnet/csharp/language-reference/keywords/using): +* Add the services to the server project to make the available during SSR. Use the following service registration in the server project's `Program` file: ```csharp - using (var request = new HttpRequestMessage(...)) - { - ... - } + builder.Services.AddHttpClient(); ``` -We recommend disposing of every with every use for the following reasons: - -* To gain a performance improvement by avoiding finalizers. -* It hardens the code for the future in case a request body is ever added to an that didn't initially have one. -* To potentially avoid functional issues if a delegating handler expects a call to /. -* It's simpler to apply a general rule everywhere than trying to remember specific cases. - -***Always*** dispose of instances. + services are provided by the shared framework, so a package reference in the app's project file isn't required. -***Never*** dispose of instances created by calling because they're managed by the framework. + Example: Todo list web API in the `BlazorWebAppCallWebApi` [sample app](#sample-apps) -Example: +* If prerendering isn't required for a WebAssembly component that calls the web API, disable prerendering by following the guidance in . If you adopt this approach, you don't need to add services to the main project of the Blazor Web App because the component isn't prerendered on the server. -```csharp -using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast"); -var client = clientFactory.CreateClient("ExternalApi"); -using var response = await client.SendAsync(request); -``` +For more information, see the [Client-side services fail to resolve during prerendering](xref:blazor/components/prerender#client-side-services-fail-to-resolve-during-prerendering) section of the *Prerendering* article. ## Client-side scenarios for calling external web APIs @@ -516,217 +256,273 @@ Even if you call instances, typically created using . For guidance that applies to server-side apps, see . +*The guidance in this section applies to client-side scenarios.* -A server-side app doesn't include an service. Provide an to the app using the [`HttpClient` factory infrastructure](xref:fundamentals/http-requests). +Client-side components call web APIs using a preconfigured service, which is focused on making requests back to the server of origin. Additional service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with . Requests can include [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) option configuration. -In the `Program` file: +The configuration examples in this section are only useful when a single web API is called for a single instance in the app. When the app must call multiple web APIs, each with its own base address and configuration, you can adopt the following approaches, which are covered in the next two sections of this article: + +* [Named `HttpClient` with `IHttpClientFactory`](#named-httpclient-with-ihttpclientfactory): Each web API is provided a unique name. When app code or a Razor component calls a web API, it uses a named instance to make the call. +* [Typed `HttpClient`](#typed-httpclient): Each web API is typed. When app code or a Razor component calls a web API, it uses a typed instance to make the call. + +In the `Program` file, add an service if it isn't already present from a Blazor project template used to create the app: ```csharp -builder.Services.AddHttpClient(); +builder.Services.AddScoped(sp => + new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); ``` -The following Razor component makes a request to a web API for GitHub branches similar to the *Basic Usage* example in the article. +The preceding example sets the base address with `builder.HostEnvironment.BaseAddress` (), which gets the base address for the app and is typically derived from the `` tag's `href` value in the host page. -`CallWebAPI.razor`: +The most common use cases for using the client's own base address are: -```razor -@page "/call-web-api" -@using System.Text.Json -@using System.Text.Json.Serialization -@inject IHttpClientFactory ClientFactory +* The client project (`.Client`) of a Blazor Web App (.NET 8 or later) makes web API calls from WebAssembly components or code that runs on the client in WebAssembly to APIs in the server app. +* The client project (**:::no-loc text="Client":::**) of a hosted Blazor WebAssembly app makes web API calls to the server project (**:::no-loc text="Server":::**). Note that the Hosted Blazor WebAssembly project template is no longer available in .NET 8 or later. However, hosted Blazor WebAssembly apps remain supported for .NET 8. -

Call web API from a server-side Razor component

+If you're calling an external web API (not in the same URL space as the client app), set the URI to the web API's base address. The following example sets the base address of the web API to `https://localhost:5001`, where a separate web API app is running and ready to respond to requests from the client app: -@if (getBranchesError || branches is null) -{ -

Unable to get branches from GitHub. Please try again later.

-} -else -{ -
    - @foreach (var branch in branches) - { -
  • @branch.Name
  • - } -
-} +```csharp +builder.Services.AddScoped(sp => + new HttpClient { BaseAddress = new Uri("https://localhost:5001") }); +``` -@code { - private IEnumerable? branches = []; - private bool getBranchesError; - private bool shouldRender; +## Named `HttpClient` with `IHttpClientFactory` - protected override bool ShouldRender() => shouldRender; + services and the configuration of a named are supported. - protected override async Task OnInitializedAsync() - { - using var request = new HttpRequestMessage(HttpMethod.Get, - "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches"); - request.Headers.Add("Accept", "application/vnd.github.v3+json"); - request.Headers.Add("User-Agent", "HttpClientFactory-Sample"); +> [!NOTE] +> An alternative to using a named from an is to use a typed . For more information, see the [Typed `HttpClient`](#typed-httpclient) section. - var client = ClientFactory.CreateClient(); +Add the [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) NuGet package to the app. - using var response = await client.SendAsync(request); +[!INCLUDE[](~/includes/package-reference.md)] - if (response.IsSuccessStatusCode) - { - using var responseStream = await response.Content.ReadAsStreamAsync(); - branches = await JsonSerializer.DeserializeAsync - >(responseStream); - } - else - { - getBranchesError = true; - } +In the `Program` file of a client project: - shouldRender = true; - } +```csharp +builder.Services.AddHttpClient("WebAPI", client => + client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); +``` - public class GitHubBranch +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" + +If the named client is to be used by prerendered client-side components of a Blazor Web App, the preceding service registration should appear in both the server project and the `.Client` project. On the server, `builder.HostEnvironment.BaseAddress` is replaced by the web API's base address, which is described further below. + +:::moniker-end + +The preceding client-side example sets the base address with `builder.HostEnvironment.BaseAddress` (), which gets the base address for the client-side app and is typically derived from the `` tag's `href` value in the host page. + +:::moniker range=">= aspnetcore-8.0" + +The most common use cases for using the client's own base address are: + +* The client project (`.Client`) of a Blazor Web App that makes web API calls from WebAssembly/Auto components or code that runs on the client in WebAssembly to APIs in the server app at the same host address. +* The client project (**:::no-loc text="Client":::**) of a hosted Blazor WebAssembly app that makes web API calls to the server project (**:::no-loc text="Server":::**). + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +The most common use case for using the client's own base address is in the client project (**:::no-loc text="Client":::**) of a hosted Blazor WebAssembly app that makes web API calls to the server project (**:::no-loc text="Server":::**). + +:::moniker-end + +If you're calling an external web API (not in the same URL space as the client app) or you're configuring the services in a server-side app (for example to deal with prerendering of client-side components on the server), set the URI to the web API's base address. The following example sets the base address of the web API to `https://localhost:5001`, where a separate web API app is running and ready to respond to requests from the client app: + +```csharp +builder.Services.AddHttpClient("WebAPI", client => + client.BaseAddress = new Uri("https://localhost:5001")); +``` + +In the following component code: + +* An instance of creates a named . +* The named is used to issue a GET request for JSON weather forecast data from the web API at `/forecast`. + +```razor +@inject IHttpClientFactory ClientFactory + +... + +@code { + private Forecast[]? forecasts; + + protected override async Task OnInitializedAsync() { - [JsonPropertyName("name")] - public string? Name { get; set; } + var client = ClientFactory.CreateClient("WebAPI"); + + forecasts = await client.GetFromJsonAsync("forecast") ?? []; } } ``` -In the preceding example for C# 12 or later, an empty array (`[]`) is created for the `branches` variable. For earlier versions of C# compiled with an SDK earlier than .NET 8, create an empty array (`Array.Empty()`). +:::moniker range=">= aspnetcore-8.0" -For an additional working example, see the server-side file upload example that uploads files to a web API controller in the article. +The `BlazorWebAppCallWebApi` [sample app](#sample-apps) demonstrates calling a web API with a named in its `CallTodoWebApiCsrNamedClient` component. For an additional working demonstration in a client app based on calling Microsoft Graph with a named , see . -:::moniker range=">= aspnetcore-8.0" +:::moniker-end -## Service abstractions for web API calls +:::moniker range="< aspnetcore-8.0" -*This section applies to Blazor Web Apps that maintain a web API in the server project or transform web API calls to an external web API.* +For a working demonstration in a client app based on calling Microsoft Graph with a named , see . -When using the interactive WebAssembly and Auto render modes, components are prerendered by default. Auto components are also initially rendered interactively from the server before the Blazor bundle downloads to the client and the client-side runtime activates. This means that components using these render modes should be designed so that they run successfully from both the client and the server. If the component must call a server project-based API or transform a request to an external web API (one that's outside of the Blazor Web App) when running on the client, the recommended approach is to abstract that API call behind a service interface and implement client and server versions of the service: +:::moniker-end -* The client version calls the web API with a preconfigured . -* The server version can typically access the server-side resources directly. Injecting an on the server that makes calls back to the server isn't recommended, as the network request is typically unnecessary. Alternatively, the API might be external to the server project, but a service abstraction for the server is required to transform the request in some way, for example to add an access token to a proxied request. +## Typed `HttpClient` -When using the WebAssembly render mode, you also have the option of disabling prerendering, so the components only render from the client. For more information, see . +Typed uses one or more of the app's instances, default or named, to return data from one or more web API endpoints. -Examples ([sample apps](#sample-apps)): +> [!NOTE] +> An alternative to using a typed is to use a named from an . For more information, see the [Named `HttpClient` with `IHttpClientFactory`](#named-httpclient-with-ihttpclientfactory) section. -* Movie list web API in the `BlazorWebAppCallWebApi` sample app. -* Streaming rendering weather data web API in the `BlazorWebAppCallWebApi_Weather` sample app. -* Weather data returned to the client in either the `BlazorWebAppOidc` (non-BFF pattern) or `BlazorWebAppOidcBff` (BFF pattern) sample apps. These apps demonstrate secure (web) API calls. For more information, see . +Add the [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) NuGet package to the app. -## Blazor Web App external web APIs +[!INCLUDE[](~/includes/package-reference.md)] -*This section applies to Blazor Web Apps that call a web API maintained by a separate (external) project, possibly hosted on a different server.* +The following example issues a GET request for JSON weather forecast data from the web API at `/forecast`. -Blazor Web Apps normally prerender client-side WebAssembly components, and Auto components render on the server during static or interactive server-side rendering (SSR). services aren't registered by default in a Blazor Web App's main project. If the app is run with only the services registered in the `.Client` project, as described in the [Add the `HttpClient` service](#add-the-httpclient-service) section, executing the app results in a runtime error: +`ForecastHttpClient.cs`: -> :::no-loc text="InvalidOperationException: Cannot provide a value for property 'Http' on type '...{COMPONENT}'. There is no registered service of type 'System.Net.Http.HttpClient'."::: +```csharp +using System.Net.Http.Json; -Use ***either*** of the following approaches: +namespace BlazorSample.Client; -* Add the services to the server project to make the available during SSR. Use the following service registration in the server project's `Program` file: +public class ForecastHttpClient(HttpClient http) +{ + public async Task GetForecastAsync() => + await http.GetFromJsonAsync("forecast") ?? []; +} +``` - ```csharp - builder.Services.AddHttpClient(); - ``` +In the `Program` file of a client project: - services are provided by the shared framework, so a package reference in the app's project file isn't required. +```csharp +builder.Services.AddHttpClient(client => + client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); +``` - Example: Todo list web API in the `BlazorWebAppCallWebApi` [sample app](#sample-apps) +:::moniker range=">= aspnetcore-8.0" -* If prerendering isn't required for a WebAssembly component that calls the web API, disable prerendering by following the guidance in . If you adopt this approach, you don't need to add services to the main project of the Blazor Web App because the component isn't prerendered on the server. +If the typed client is to be used by prerendered client-side components of a Blazor Web App, the preceding service registration should appear in both the server project and the `.Client` project. On the server, `builder.HostEnvironment.BaseAddress` is replaced by the web API's base address, which is described further below. -For more information, see the [Client-side services fail to resolve during prerendering](xref:blazor/components/prerender#client-side-services-fail-to-resolve-during-prerendering) section of the *Prerendering* article. +:::moniker-end -## Prerendered data +The preceding example sets the base address with `builder.HostEnvironment.BaseAddress` (), which gets the base address for the client-side app and is typically derived from the `` tag's `href` value in the host page. -When prerendering, components render twice: first statically, then interactively. State doesn't automatically flow from the prerendered component to the interactive one. If a component performs asynchronous initialization operations and renders different content for different states during initialization, such as a "Loading..." progress indicator, you may see a flicker when the component renders twice. +:::moniker range=">= aspnetcore-8.0" -You can address this by flowing prerendered state using the Persistent Component State API, which the `BlazorWebAppCallWebApi` and `BlazorWebAppCallWebApi_Weather` [sample apps](#sample-apps) demonstrate. When the component renders interactively, it can render the same way using the same state. For more information, see the following resources: +The most common use cases for using the client's own base address are: -* -* +* The client project (`.Client`) of a Blazor Web App that makes web API calls from WebAssembly/Auto components or code that runs on the client in WebAssembly to APIs in the server app at the same host address. +* The client project (**:::no-loc text="Client":::**) of a hosted Blazor WebAssembly app that makes web API calls to the server project (**:::no-loc text="Server":::**). :::moniker-end -:::moniker range="< aspnetcore-10.0" +:::moniker range="< aspnetcore-8.0" -> [!NOTE] -> The Persistent Component State API only supports enhanced navigation in .NET 10 or later. For apps that target .NET 8 or .NET 9, you can disable enhanced navigation on links to the page with the `data-enhance-nav` attribute set to `false`. For more information, see . +The most common use case for using the client's own base address is in the client project (**:::no-loc text="Client":::**) of a hosted Blazor WebAssembly app that makes web API calls to the server project (**:::no-loc text="Server":::**). :::moniker-end -:::moniker range=">= aspnetcore-9.0" +If you're calling an external web API (not in the same URL space as the client app) or you're configuring the services in a server-side app (for example to deal with prerendering of client-side components on the server), set the URI to the web API's base address. The following example sets the base address of the web API to `https://localhost:5001`, where a separate web API app is running and ready to respond to requests from the client app: -## Client-side request streaming +```csharp +builder.Services.AddHttpClient(client => + client.BaseAddress = new Uri("https://localhost:5001")); +``` -For Chromium-based browsers (for example, Google Chrome and Microsoft Edge) using the HTTP/2 protocol, and HTTPS, client-side Blazor uses [Streams API](https://developer.mozilla.org/docs/Web/API/Streams_API) to permit [request streaming](https://developer.chrome.com/docs/capabilities/web-apis/fetch-streaming-requests). +Components inject the typed to call the web API. -To enable request streaming, set to `true` on the . +In the following component code: -In the following file upload example: +* An instance of the preceding `ForecastHttpClient` is injected, which creates a typed . +* The typed is used to issue a GET request for JSON weather forecast data from the web API. -* `content` is the file's . -* `/Filesave` is the web API endpoint. -* `Http` is the . +```razor +@inject ForecastHttpClient Http -```csharp -using var request = new HttpRequestMessage(HttpMethod.Post, "/Filesave"); -request.SetBrowserRequestStreamingEnabled(true); -request.Content = content; +... -using var response = await Http.SendAsync(request); +@code { + private Forecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + forecasts = await Http.GetForecastAsync(); + } +} ``` -Streaming requests: +:::moniker range=">= aspnetcore-8.0" -* Require HTTPS protocol and don't work on HTTP/1.x. -* Include a body but not a `Content-Length` header. [CORS](xref:security/cors) with a preflight request is required for cross-origin streaming requests. +The `BlazorWebAppCallWebApi` [sample app](#sample-apps) demonstrates calling a web API with a typed in its `CallTodoWebApiCsrTypedClient` component. Note that the component adopts and client-side rendering (CSR) (`InteractiveWebAssembly` render mode) ***with prerendering***, so the typed client service registration appears in the `Program` file of both the server project and the `.Client` project. + +:::moniker-end + +## Disposal of `HttpRequestMessage`, `HttpResponseMessage`, and `HttpClient` + +An without a body doesn't require explicit disposal. However, you can dispose of it with either of the following patterns: -For more information on file uploads with an component, see and the example at [Upload files to a server with client-side rendering (CSR)](xref:blazor/file-uploads#upload-files-to-a-server-with-client-side-rendering-csr). +* `using` declaration (C# 8 or later): -:::moniker-end + ```csharp + using var request = new HttpRequestMessage(...); + ``` + +* [`using` block (all C# releases)](/dotnet/csharp/language-reference/keywords/using): -## Add the `HttpClient` service + ```csharp + using (var request = new HttpRequestMessage(...)) + { + ... + } + ``` -*The guidance in this section applies to client-side scenarios.* +We recommend disposing of every with every use for the following reasons: -Client-side components call web APIs using a preconfigured service, which is focused on making requests back to the server of origin. Additional service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with . Requests can include [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) option configuration. +* To gain a performance improvement by avoiding finalizers. +* It hardens the code for the future in case a request body is ever added to an that didn't initially have one. +* To potentially avoid functional issues if a delegating handler expects a call to /. +* It's simpler to apply a general rule everywhere than trying to remember specific cases. -The configuration examples in this section are only useful when a single web API is called for a single instance in the app. When the app must call multiple web APIs, each with its own base address and configuration, you can adopt the following approaches, which are covered later in this article: +***Always*** dispose of instances. -* [Named `HttpClient` with `IHttpClientFactory`](#named-httpclient-with-ihttpclientfactory): Each web API is provided a unique name. When app code or a Razor component calls a web API, it uses a named instance to make the call. -* [Typed `HttpClient`](#typed-httpclient): Each web API is typed. When app code or a Razor component calls a web API, it uses a typed instance to make the call. +***Never*** dispose of instances created by calling because they're managed by the framework. -In the `Program` file, add an service if it isn't already present from a Blazor project template used to create the app: +Example: ```csharp -builder.Services.AddScoped(sp => - new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast"); +var client = clientFactory.CreateClient("ExternalApi"); +using var response = await client.SendAsync(request); ``` -The preceding example sets the base address with `builder.HostEnvironment.BaseAddress` (), which gets the base address for the app and is typically derived from the `` tag's `href` value in the host page. +## Prerendered data -The most common use cases for using the client's own base address are: +When prerendering, components render twice: first statically, then interactively. State doesn't automatically flow from the prerendered component to the interactive one. If a component performs asynchronous initialization operations and renders different content for different states during initialization, such as a "Loading..." progress indicator, you may see a flicker when the component renders twice. -* The client project (`.Client`) of a Blazor Web App (.NET 8 or later) makes web API calls from WebAssembly components or code that runs on the client in WebAssembly to APIs in the server app. -* The client project (**:::no-loc text="Client":::**) of a hosted Blazor WebAssembly app makes web API calls to the server project (**:::no-loc text="Server":::**). Note that the Hosted Blazor WebAssembly project template is no longer available in .NET 8 or later. However, hosted Blazor WebAssembly apps remain supported for .NET 8. +You can address this by flowing prerendered state using the Persistent Component State API, which the `BlazorWebAppCallWebApi` and `BlazorWebAppCallWebApi_Weather` [sample apps](#sample-apps) demonstrate. When the component renders interactively, it can render the same way using the same state. For more information, see the following resources: -If you're calling an external web API (not in the same URL space as the client app), set the URI to the web API's base address. The following example sets the base address of the web API to `https://localhost:5001`, where a separate web API app is running and ready to respond to requests from the client app: +* +* -```csharp -builder.Services.AddScoped(sp => - new HttpClient { BaseAddress = new Uri("https://localhost:5001") }); -``` +:::moniker range="< aspnetcore-10.0" + +> [!NOTE] +> The Persistent Component State API only supports enhanced navigation in .NET 10 or later. For apps that target .NET 8 or .NET 9, you can disable enhanced navigation on links to the page with the `data-enhance-nav` attribute set to `false`. For more information, see . + +:::moniker-end ## JSON helpers - is available as a preconfigured service for making requests back to the origin server. +The [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json) package provides extension methods for and that perform automatic serialization and deserialization using [`System.Text.Json`](https://www.nuget.org/packages/System.Text.Json). The `System.Net.Http.Json` package is provided by the .NET shared framework and doesn't require adding a package reference to the app. - and JSON helpers () are also used to call third-party web API endpoints. is implemented using the browser's [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) and is subject to its limitations, including enforcement of the same-origin policy, which is discussed later in this article in the *Cross-Origin Resource Sharing (CORS)* section. + is available as a preconfigured service for making requests back to the origin server. and JSON helpers () are also used to call third-party web API endpoints. is implemented using the browser's [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) and is subject to its limitations, including enforcement of the same-origin policy, which is discussed later in this article in the *Cross-Origin Resource Sharing (CORS)* section. The client's base address is set to the originating server's address. Inject an instance into a component using the [`@inject`](xref:mvc/views/razor#inject) directive: @@ -990,232 +786,307 @@ In the following component code, the `