From d9ab1c3233425de971e1f9ef64e8b480f925d5cc Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Fri, 8 Mar 2024 08:33:30 -0500 Subject: [PATCH] PATCH section and other updates (#32007) --- aspnetcore/blazor/call-web-api.md | 213 +++++++----------------------- 1 file changed, 51 insertions(+), 162 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 501bd443fa..f3886b056a 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: riande ms.custom: mvc -ms.date: 03/07/2024 +ms.date: 03/08/2024 uid: blazor/call-web-api --- # Call a web API from ASP.NET Core Blazor @@ -136,7 +136,7 @@ else } @code { - private IEnumerable? branches = Array.Empty(); + private IEnumerable? branches = []; private bool getBranchesError; private bool shouldRender; @@ -175,6 +175,8 @@ else } ``` +In the preceding example for C# 12 or later, an empty array (`[]`) is created for the `branches` variable. For earlier versions of C#, create an empty array (`Array.Empty()`). + For an additional working example, see the server-side file upload example that uploads files to a web API controller in the article. :::moniker range=">= aspnetcore-8.0" @@ -297,28 +299,31 @@ Use the namespace for acces @using System.Net.Http.Json ``` -### GET from JSON (`GetFromJsonAsync`) +The following sections cover JSON helpers: + +* [GET](#get-from-json-getfromjsonasync) +* [POST](#post-as-json-postasjsonasync) +* [PUT](#put-as-json-putasjsonasync) +* [PATCH](#patch-as-json-patchasjsonasync) + + includes additional methods for sending HTTP requests and receiving HTTP responses, for example to send a DELETE request. For more information, see the [DELETE and additional extension methods](#delete-deleteasync-and-additional-extension-methods) section. + +## GET from JSON (`GetFromJsonAsync`) sends an HTTP GET request and parses the JSON response body to create an object. In the following component code, the `todoItems` are displayed by the component. is called when the component is finished initializing ([`OnInitializedAsync`](xref:blazor/components/lifecycle#component-initialization-oninitializedasync)). -> [!NOTE] -> When targeting ASP.NET Core 5.0 or earlier, add `@using` directives to the following component for , , and . - ```csharp todoItems = await Http.GetFromJsonAsync("todoitems"); ``` -### POST as JSON (`PostAsJsonAsync`) +## POST as JSON (`PostAsJsonAsync`) sends a POST request to the specified URI containing the value serialized as JSON in the request body. In the following component code, `newItemName` is provided by a bound element of the component. The `AddItem` method is triggered by selecting a ` - - } - - -@code { - private async Task UpdateItem(long id) => - await Http.PatchAsJsonAsync( - $"todoitems/{id}", - "[{\"operationType\":2,\"path\":\"/IsComplete\",\"op\":\"replace\",\"value\":true}]"); -} +await Http.PatchAsJsonAsync( + $"todoitems/{id}", + "[{\"operationType\":2,\"path\":\"/IsComplete\",\"op\":\"replace\",\"value\":true}]"); ``` returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON todo item data as an array. An empty array is created if no item data is returned by the method, so `content` isn't null after the statement executes: ```csharp +var response = await Http.PatchAsJsonAsync(...); var content = await response.Content.ReadFromJsonAsync() ?? Array.Empty(); ``` - receives a JSON PATCH document for the PATCH request. The preceding `UpdateItem` method called with a PATCH document as a string with escaped quotes. Laid out with indentation, spacing, and non-escaped quotes, the unencoded PATCH document appears as the following JSON: +Laid out with indentation, spacing, and unescaped quotes, the unencoded PATCH document appears as the following JSON: ```json [ @@ -438,9 +396,11 @@ Install the [`Microsoft.AspNetCore.JsonPatch`](https://www.nuget.org/packages/Mi [!INCLUDE[](~/includes/package-reference.md)] -Add an `@using` directive for the namespace to the top of the Razor component: +Add `@using` directives for the , , and namespaces to the top of the Razor component: ```razor +@using System.Text.Json +@using System.Text.Json.Serialization @using Microsoft.AspNetCore.JsonPatch ``` @@ -451,7 +411,7 @@ var patchDocument = new JsonPatchDocument() .Replace(p => p.IsComplete, true); ``` -Pass the document's operations (`patchDocument.Operations`) to the call. The following example shows how to make the call: +Pass the document's operations (`patchDocument.Operations`) to the call: ```csharp private async Task UpdateItem(long id) @@ -461,119 +421,60 @@ private async Task UpdateItem(long id) patchDocument.Operations, new JsonSerializerOptions() { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, - WriteIndented = true + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }); } ``` is set to to ignore a property only if it equals the default value for its type. - is used merely to present the JSON payload in a pleasant format for this article. Writing indented JSON has no bearing on processing PATCH requests and isn't typically performed in production apps for web API requests. +Add set to `true` if you want to present the JSON payload in a pleasant format for display. Writing indented JSON has no bearing on processing PATCH requests and isn't typically performed in production apps for web API requests. -Next, follow the guidance in the article to add a PATCH controller action to the web API, which is reproduced here in the following steps. +Follow the guidance in the article to add a PATCH controller action to the web API. Alternatively, PATCH request processing can be implemented as a [Minimal API](xref:fundamentals/minimal-apis) with the following steps. Add a package reference for the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson) NuGet package to the web API app. > [!NOTE] > There's no need to add a package reference for the [`Microsoft.AspNetCore.JsonPatch`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch) package to the app because the reference to the `Microsoft.AspNetCore.Mvc.NewtonsoftJson` package automatically transitively adds a package reference for `Microsoft.AspNetCore.JsonPatch`. -Add a custom JSON PATCH input formatter to the web API app. - -`JSONPatchInputFormatter.cs`: - -```csharp -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Extensions.Options; - -public static class JSONPatchInputFormatter -{ - public static NewtonsoftJsonPatchInputFormatter Get() - { - var builder = new ServiceCollection() - .AddLogging() - .AddMvc() - .AddNewtonsoftJson() - .Services.BuildServiceProvider(); - - return builder - .GetRequiredService>() - .Value - .InputFormatters - .OfType() - .First(); - } -} -``` - -Configure the web API's controllers to use the `Microsoft.AspNetCore.Mvc.NewtonsoftJson` package and process PATCH requests with the JSON PATCH input formatter. Insert the `JSONPatchInputFormatter` in the first position of MVC's input formatter collection so that it processes requests prior to any other input formatter. - -In the `Program` file modify the call to : - -```csharp -builder.Services.AddControllers(options => -{ - options.InputFormatters.Insert(0, JSONPatchInputFormatter.Get()); -}).AddNewtonsoftJson(); -``` - -In `Controllers/TodoItemsController.cs`, add a `using` statement for the namespace: +In the `Program` file add an `@using` directive for the namespace: ```csharp using Microsoft.AspNetCore.JsonPatch; ``` -In `Controllers/TodoItemsController.cs`, add the following `PatchTodoItem` action method: +Provide the endpoint to the request processing pipeline of the web API: ```csharp -[HttpPatch("{id}")] -public async Task PatchTodoItem(long id, - JsonPatchDocument patchDoc) +app.MapPatch("/todoitems/{id}", async (long id, TodoContext db) => { - if (patchDoc == null) + if (await db.TodoItems.FindAsync(id) is TodoItem todo) { - return BadRequest(); - } - - var todoItem = await _context.TodoItems.FindAsync(id); + var patchDocument = + new JsonPatchDocument().Replace(p => p.IsComplete, true); + patchDocument.ApplyTo(todo); + await db.SaveChangesAsync(); - if (todoItem == null) - { - return NotFound(); + return TypedResults.Ok(todo); } - patchDoc.ApplyTo(todoItem); - - _context.Entry(todoItem).State = EntityState.Modified; - - try - { - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) when (!TodoItemExists(id)) - { - return NotFound(); - } - - return NoContent(); -} + return TypedResults.NoContent(); +}); ``` > [!WARNING] -> As with the other examples in the article, the preceding PATCH controller action doesn't protect the web API from over-posting attacks. For more information, see . +> As with the other examples in the article, the preceding PATCH API doesn't protect the web API from over-posting attacks. For more information, see . + +For a fully working PATCH experience, see the `BlazorWebAppCallWebApi` [sample app](#sample-apps). :::moniker-end -### Additional extension methods +## DELETE (`DeleteAsync`) and additional extension methods includes additional extension methods for sending HTTP requests and receiving HTTP responses. is used to send an HTTP DELETE request to a web API. In the following component code, the `