diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 98e9bf9f4e54..c991ff290803 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -676,6 +676,35 @@ Laid out with indentation, spacing, and unescaped quotes, the unencoded PATCH do To simplify the creation of PATCH documents in the app issuing PATCH requests, an app can use .NET JSON PATCH support, as the following guidance demonstrates. +:::moniker-end + +:::moniker range=">= aspnetcore-10.0" + + + +Install the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package and use the API features of the package to compose a `JsonPatchDocument` for a PATCH request. + +[!INCLUDE[](~/includes/package-reference.md)] + +Add `@using` directives for the , , and `Microsoft.AspNetCore.JsonPatch.SystemTextJson` namespaces to the top of the Razor component: + +```razor +@using System.Text.Json +@using System.Text.Json.Serialization +@using Microsoft.AspNetCore.JsonPatch.SystemTextJson +``` + +Compose the `JsonPatchDocument` for a `TodoItem` with `IsComplete` set to `true` using the `JsonPatchDocument.Replace` method: + +```csharp +var patchDocument = new JsonPatchDocument() + .Replace(p => p.IsComplete, true); +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0 < aspnetcore-10.0" + Install the [`Microsoft.AspNetCore.JsonPatch`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch) NuGet package and use the API features of the package to compose a for a PATCH request. [!INCLUDE[](~/includes/package-reference.md)] @@ -695,6 +724,10 @@ var patchDocument = new JsonPatchDocument() .Replace(p => p.IsComplete, true); ``` +:::moniker-end + +:::moniker range=">= aspnetcore-7.0" + Pass the document's operations (`patchDocument.Operations`) to the call: ```csharp @@ -716,6 +749,24 @@ Add 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. +:::moniker-end + +:::moniker range=">= aspnetcore-10.0" + + + +Add a package reference for the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson) NuGet package to the web API app. + +In the `Program` file add an `@using` directive for the `Microsoft.AspNetCore.JsonPatch.SystemTextJson` namespace: + +```csharp +using Microsoft.AspNetCore.JsonPatch.SystemTextJson; +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0 < aspnetcore-10.0" + 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] @@ -727,6 +778,10 @@ In the `Program` file add an `@using` directive for the , which is part of Aspire's framework for distributed application hosting and publishing. For more information, see [Aspire Documentation](https://aspire.example.com/docs/distributed-application-publisher). - +Previously, running application startup logic without launching an HTTP server could be done by using `HostFactoryResolver` with a no-op `IServer` implementation. The new feature simplifies this process by providing a streamlined API inspired by Aspire's , which is part of Aspire's framework for distributed application hosting and publishing. For more information, see [Aspire Documentation](https://aspire.example.com/docs/distributed-application-publisher) and [dotnet/aspnetcore #61463](https://github.com/dotnet/aspnetcore/pull/61463). diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs new file mode 100644 index 000000000000..cf7eb4badad3 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs @@ -0,0 +1,104 @@ +using JsonPatchSample.Models; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Dynamic; + +namespace JsonPatchSample.Controllers; + +[Route("jsonpatch/[action]")] +[ApiController] +public class HomeController : ControllerBase +{ + // + [HttpPatch] + public IActionResult JsonPatchWithModelState( + [FromBody] JsonPatchDocument patchDoc) + { + if (patchDoc != null) + { + var customer = CreateCustomer(); + + patchDoc.ApplyTo(customer, ModelState); + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return new ObjectResult(customer); + } + else + { + return BadRequest(ModelState); + } + } + // + + [HttpPatch] + public IActionResult JsonPatchWithModelStateAndPrefix( + [FromBody] JsonPatchDocument patchDoc, + string prefix) + { + var customer = CreateCustomer(); + + patchDoc.ApplyTo(customer, ModelState, prefix); + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return new ObjectResult(customer); + } + + [HttpPatch] + public IActionResult JsonPatchWithoutModelState([FromBody] JsonPatchDocument patchDoc) + { + var customer = CreateCustomer(); + + patchDoc.ApplyTo(customer); + + return new ObjectResult(customer); + } + + [HttpPatch] + public IActionResult JsonPatchForProduct([FromBody] JsonPatchDocument patchDoc) + { + var product = new Product(); + + patchDoc.ApplyTo(product); + + return new ObjectResult(product); + } + + // + [HttpPatch] + public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch) + { + dynamic obj = new ExpandoObject(); + patch.ApplyTo(obj); + + return Ok(obj); + } + // + + private Customer CreateCustomer() + { + return new Customer + { + CustomerName = "John", + Orders = new List() + { + new Order + { + OrderName = "Order0" + }, + new Order + { + OrderName = "Order1" + } + } + }; + } +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj b/aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj new file mode 100644 index 000000000000..9fdb95013b67 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs new file mode 100644 index 000000000000..108f5ae39e1e --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace JsonPatchSample.Models +{ + public class Category + { + public string CategoryName { get; set; } + } +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs new file mode 100644 index 000000000000..d64fd761dc7c --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs @@ -0,0 +1,7 @@ +namespace JsonPatchSample.Models; + +public class Customer +{ + public string? CustomerName { get; set; } + public List? Orders { get; set; } +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs new file mode 100644 index 000000000000..d3ce1355e151 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs @@ -0,0 +1,7 @@ +namespace JsonPatchSample.Models; + +public class Order +{ + public string OrderName { get; set; } + public string OrderType { get; set; } +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs new file mode 100644 index 000000000000..5ea1f5085e4c --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs @@ -0,0 +1,8 @@ +namespace JsonPatchSample.Models +{ + public class Product + { + public string ProductName { get; set; } + public Category ProductCategory { get; set; } + } +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs new file mode 100644 index 000000000000..6a20608945c0 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.Options; + +namespace JsonPatchSample; + +public static class MyJPIF +{ + public static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter() + { + var builder = new ServiceCollection() + .AddLogging() + .AddMvc() + .AddNewtonsoftJson() + .Services.BuildServiceProvider(); + + return builder + .GetRequiredService>() + .Value + .InputFormatters + .OfType() + .First(); + } +} \ No newline at end of file diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs new file mode 100644 index 000000000000..6b755abe0c02 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs @@ -0,0 +1,42 @@ +#define BOTH // FIRST BOTH +#if NEVER +#elif FIRST +// +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers() + .AddNewtonsoftJson(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); +// +#elif BOTH +// +using JsonPatchSample; +using Microsoft.AspNetCore.Mvc.Formatters; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(options => +{ + options.InputFormatters.Insert(0, MyJPIF.GetJsonPatchInputFormatter()); +}); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); +// +#endif diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/appsettings.json b/aspnetcore/web-api/jsonpatch/samples/10.x/api/appsettings.json new file mode 100644 index 000000000000..10f68b8c8b4f --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}