|
| 1 | +--- |
| 2 | +title: Call a web API |
| 3 | +author: guardrex |
| 4 | +description: Learn how to call a web API from a Blazor app using JSON helpers, including making cross-origin resource sharing (CORS) requests. |
| 5 | +monikerRange: '>= aspnetcore-3.0' |
| 6 | +ms.author: riande |
| 7 | +ms.custom: mvc |
| 8 | +ms.date: 05/20/2019 |
| 9 | +uid: blazor/call-web-api |
| 10 | +--- |
| 11 | +# Call a web API |
| 12 | + |
| 13 | +By [Luke Latham](https://github.com/guardrex) |
| 14 | + |
| 15 | +Blazor apps call web API services using [HttpClient](xref:fundamentals/http-requests). Compose requests using Blazor JSON helpers or with <xref:System.Net.Http.HttpRequestMessage>, which can include JavaScript [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) request options. |
| 16 | + |
| 17 | +[View or download sample code](https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/blazor/common/samples/) ([how to download](xref:index#how-to-download-a-sample)) |
| 18 | + |
| 19 | +See the following components in the sample app: |
| 20 | + |
| 21 | +* Call Web API (*Pages/CallWebAPI.razor*) |
| 22 | +* HTTP Request Tester (*Components/HTTPRequestTester.razor*) |
| 23 | + |
| 24 | +## HttpClient and JSON helpers |
| 25 | + |
| 26 | +Use [HttpClient](xref:fundamentals/http-requests) and JSON helpers to call a web API service endpoint in a Razor component. Blazor client-side uses the [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API), while Blazor server-side uses <xref:System.Net.Http.HttpClient?displayProperty=fullName>. |
| 27 | + |
| 28 | +Include an `@using` statement for <xref:System.Threading.Tasks> for asynchronous web API calls and inject an `HttpClient` instance: |
| 29 | + |
| 30 | +```cshtml |
| 31 | +@using System.Threading.Tasks |
| 32 | +@inject HttpClient Http |
| 33 | +``` |
| 34 | + |
| 35 | +In the following examples, a Todo web API service processes create, read, update, and delete (CRUD) operations. The examples are based on a `TodoItem` class that stores the: |
| 36 | + |
| 37 | +* ID (`Id`, `long`) – Unique ID of the item. |
| 38 | +* Name (`Name`, `string`) – Name of the item. |
| 39 | +* Status (`IsComplete`, `bool`) – Indication if the Todo item is finished. |
| 40 | + |
| 41 | +```csharp |
| 42 | +private class TodoItem |
| 43 | +{ |
| 44 | + public long Id { get; set; } |
| 45 | + public string Name { get; set; } |
| 46 | + public bool IsComplete { get; set; } |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +JSON helper methods send requests to a URI (a web API in the following examples) and process the response: |
| 51 | + |
| 52 | +* `GetJsonAsync` – Sends a GET request and parses the JSON response body to create an object. |
| 53 | + |
| 54 | + In the following code, the `_todoItems` are displayed by the component. The `GetTodoItems` method is triggered when the component is finished rendering ([OnInitAsync](xref:blazor/components#lifecycle-methods)). See the sample app for a complete example. |
| 55 | + |
| 56 | + ```cshtml |
| 57 | + @using Microsoft.AspNetCore.Blazor.Http |
| 58 | + @inject HttpClient Http |
| 59 | +
|
| 60 | + @functions { |
| 61 | + private const string ServiceEndpoint = "https://localhost:10000/api/todo"; |
| 62 | + private TodoItem[] _todoItems; |
| 63 | +
|
| 64 | + protected override async Task OnInitAsync() => |
| 65 | + await GetTodoItems(); |
| 66 | +
|
| 67 | + private async Task GetTodoItems() => |
| 68 | + _todoItems = await Http.GetJsonAsync<TodoItem[]>(ServiceEndpoint); |
| 69 | + } |
| 70 | + ``` |
| 71 | + |
| 72 | +* `PostJsonAsync` – Sends a POST request, including JSON-encoded content, and parses the JSON response body to create an object. |
| 73 | + |
| 74 | + In the following code, `_newItemName` is provided by a bound element of the component. The `AddItem` method is triggered by selecting a `<button>` element. See the sample app for a complete example. |
| 75 | + |
| 76 | + ```cshtml |
| 77 | + @using Microsoft.AspNetCore.Blazor.Http |
| 78 | + @inject HttpClient Http |
| 79 | +
|
| 80 | + <input type="text" bind="@_newItemName" placeholder="New Todo Item" /> |
| 81 | + <button onclick="@(async () => await AddItem())">Add</button> |
| 82 | +
|
| 83 | + @functions { |
| 84 | + private const string ServiceEndpoint = "https://localhost:10000/api/todo"; |
| 85 | + private string _newItemName; |
| 86 | +
|
| 87 | + private async Task AddItem() |
| 88 | + { |
| 89 | + var addItem = new TodoItem { Name = _newItemName, IsComplete = false }; |
| 90 | + await Http.PostJsonAsync(ServiceEndpoint, addItem); |
| 91 | + } |
| 92 | + } |
| 93 | + ``` |
| 94 | + |
| 95 | +* `PutJsonAsync` – Sends a PUT request, including JSON-encoded content. |
| 96 | + |
| 97 | + In the following code, `_editItem` values (`Id`, `Name`, `IsCompleted`) are provided by bound elements of the component. The `SaveItem` method is triggered by selecting the Save `<button>` element. See the sample app for a complete example. |
| 98 | + |
| 99 | + ```cshtml |
| 100 | + @using Microsoft.AspNetCore.Blazor.Http |
| 101 | + @inject HttpClient Http |
| 102 | +
|
| 103 | + <input type="text" bind="@_editItem.Id" /> |
| 104 | + <input type="checkbox" bind="@_editItem.IsComplete" /> |
| 105 | + <input type="text" bind="@_editItem.Name" /> |
| 106 | + <button onclick="@(async () => await SaveItem())">Save</button> |
| 107 | +
|
| 108 | + @functions { |
| 109 | + private const string ServiceEndpoint = "https://localhost:10000/api/todo"; |
| 110 | + private TodoItem _editItem = new TodoItem(); |
| 111 | +
|
| 112 | + private async Task SaveItem() |
| 113 | + { |
| 114 | + await Http.PutJsonAsync( |
| 115 | + Path.Combine(ServiceEndpoint, _editItem.Id.ToString()), _editItem); |
| 116 | + } |
| 117 | + } |
| 118 | + ``` |
| 119 | + |
| 120 | +<xref:System.Net.Http> includes additional extension methods for sending HTTP requests and receiving HTTP responses. [HttpClient.DeleteAsync](xref:System.Net.Http.HttpClient.DeleteAsync*) is used to send a DELETE request to a web API. |
| 121 | + |
| 122 | +In the following code, the Delete `<button>` element supplies the `id` when it's selected in the UI. See the sample app for a complete example. |
| 123 | + |
| 124 | +```cshtml |
| 125 | +@using Microsoft.AspNetCore.Blazor.Http |
| 126 | +@inject HttpClient Http |
| 127 | +
|
| 128 | +<input type="text" bind="@_id" /> |
| 129 | +<button onclick="@(async () => await DeleteItem())">Delete</button> |
| 130 | +
|
| 131 | +@functions { |
| 132 | + private const string ServiceEndpoint = "https://localhost:10000/api/todo"; |
| 133 | + private long _id; |
| 134 | +
|
| 135 | + private async Task DeleteItem() |
| 136 | + { |
| 137 | + await Http.DeleteAsync(Path.Combine(ServiceEndpoint, _id.ToString())); |
| 138 | + } |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +## HttpClient and HttpRequestMessage with Fetch API request options |
| 143 | + |
| 144 | +Use [HttpClient](xref:fundamentals/http-requests) and <xref:System.Net.Http.HttpRequestMessage> to supply request options to the underlying JavaScript [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API). |
| 145 | + |
| 146 | +In the following example: |
| 147 | + |
| 148 | +* JSON serialization and deserialization must be handled by user code (*not shown*). |
| 149 | +* The `credentials` property is set to any of the following values: |
| 150 | + |
| 151 | + * `FetchCredentialsOption.Include` ("include") – Advises the browser to send credentials (such as cookies or HTTP auth headers) even for cross-origin requests. Only allowed when the CORS Middleware policy in the web API is configured to <xref:Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder.AllowCredentials*>. |
| 152 | + * `FetchCredentialsOption.Omit` ("omit") – Advises the browser never to send credentials (such as cookies or HTTP auth headers). |
| 153 | + * `FetchCredentialsOption.SameOrigin` ("same-origin") – Advises the browser to send credentials (such as cookies or HTTP auth headers) only if the target URL is on the same origin as the calling application. |
| 154 | + |
| 155 | +```cshtml |
| 156 | +@using System.Net.Http.Headers |
| 157 | +@using Microsoft.AspNetCore.Blazor.Http |
| 158 | +@inject HttpClient Http |
| 159 | +
|
| 160 | +@functions { |
| 161 | + private async Task PostRequest() |
| 162 | + { |
| 163 | + Http.DefaultRequestHeaders.Authorization = |
| 164 | + new AuthenticationHeaderValue("Bearer", "{OAUTH TOKEN}"); |
| 165 | +
|
| 166 | + var requestMessage = new HttpRequestMessage() |
| 167 | + { |
| 168 | + Method = new HttpMethod("POST"), |
| 169 | + RequestUri = new Uri("https://localhost:10000/api/todo"), |
| 170 | + Content = |
| 171 | + new StringContent( |
| 172 | + @"{""name"":""A New Todo Item"",""isComplete"":false}") |
| 173 | + }; |
| 174 | +
|
| 175 | + requestMessage.Content.Headers.ContentType = |
| 176 | + new System.Net.Http.Headers.MediaTypeHeaderValue( |
| 177 | + "application/json"); |
| 178 | +
|
| 179 | + requestMessage.Content.Headers.TryAddWithoutValidation( |
| 180 | + "x-custom-header", "value"); |
| 181 | + |
| 182 | + requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new |
| 183 | + { |
| 184 | + credentials = FetchCredentialsOption.Include |
| 185 | + }; |
| 186 | +
|
| 187 | + var response = await Http.SendAsync(requestMessage); |
| 188 | + var responseStatusCode = response.StatusCode; |
| 189 | + var responseBody = await response.Content.ReadAsStringAsync(); |
| 190 | + } |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +For more information on Fetch API options, see [MDN web docs: WindowOrWorkerGlobalScope.fetch():Parameters](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters). |
| 195 | + |
| 196 | +When sending credentials (authorization cookies/headers) on CORS requests, allow the `Authorization` header when creating the CORS policy for CORS middleware. The following policy includes configuration for: |
| 197 | + |
| 198 | +* Request origins (`http://localhost:5000`, `https://localhost:5001`). |
| 199 | +* Any method (verb). |
| 200 | +* `Content-Type` and `Authorization` headers. To allow a custom header (for example, `x-custom-header`), list the header when calling <xref:Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder.WithHeaders*>. |
| 201 | +* Credentials set by client-side JavaScript code (`credentials` property set to `include`). |
| 202 | + |
| 203 | +```csharp |
| 204 | +app.UseCors(policy => |
| 205 | + policy.WithOrigins("http://localhost:5000", "https://localhost:5001") |
| 206 | + .AllowAnyMethod() |
| 207 | + .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header") |
| 208 | + .AllowCredentials()); |
| 209 | +``` |
| 210 | + |
| 211 | +For more information, see <xref:security/cors> and the sample app's HTTP Request Tester component (*Components/HTTPRequestTester.razor*). |
| 212 | + |
| 213 | +## Cross-origin resource sharing (CORS) |
| 214 | + |
| 215 | +For more information on making cross-origin resource sharing (CORS) requests, see <xref:security/cors>. The sample app demonstrates CORS. See the Call Web API component (*Pages/CallWebAPI.razor*). |
| 216 | + |
| 217 | +## HTTP Request Tester |
| 218 | + |
| 219 | +The sample app includes an HTTP Request Tester component (*Components/HTTPRequestTester.razor*). The component is included in the Call Web API component: |
| 220 | + |
| 221 | +```cshtml |
| 222 | +<HTTPRequestTester /> |
| 223 | +``` |
| 224 | + |
| 225 | +Use the component to test request-responses against web API service endpoints. |
| 226 | + |
| 227 | +## Additional resources |
| 228 | + |
| 229 | +* <xref:fundamentals/http-requests> |
| 230 | +* [Cross Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/) |
0 commit comments