From 6483c567308049711720afb3ecd8c8f8b12def21 Mon Sep 17 00:00:00 2001 From: Tom Dykstra Date: Tue, 13 May 2025 08:53:47 -0700 Subject: [PATCH 1/4] H2-->H3 (#35453) --- .../release-notes/aspnetcore-10/includes/doc-provider-in-di.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md b/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md index f5edf861d5e5..b9220798e379 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md @@ -1,4 +1,4 @@ -## Support for IOpenApiDocumentProvider in the DI container. +### Support for IOpenApiDocumentProvider in the DI container. ASP.NET Core in .NET 10 supports [IOpenApiDocumentProvider](https://source.dot.net/#Microsoft.AspNetCore.OpenApi/Services/IOpenApiDocumentProvider.cs) in the dependency injection (DI) container. Developers can inject `IOpenApiDocumentProvider` into their apps and use it to access the OpenAPI document. This approach is useful for accessing OpenAPI documents outside the context of HTTP requests, such as in background services or custom middleware. From 77f6e45d2df60931698975615acc6b2faa676205 Mon Sep 17 00:00:00 2001 From: Tom Dykstra Date: Tue, 13 May 2025 09:08:40 -0700 Subject: [PATCH 2/4] Add 'For more information' links (#35455) --- .../release-notes/aspnetcore-10/includes/doc-provider-in-di.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md b/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md index b9220798e379..35e3c666559c 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/doc-provider-in-di.md @@ -2,6 +2,5 @@ ASP.NET Core in .NET 10 supports [IOpenApiDocumentProvider](https://source.dot.net/#Microsoft.AspNetCore.OpenApi/Services/IOpenApiDocumentProvider.cs) in the dependency injection (DI) container. Developers can inject `IOpenApiDocumentProvider` into their apps and use it to access the OpenAPI document. This approach is useful for accessing OpenAPI documents outside the context of HTTP requests, such as in background services or custom middleware. -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). - +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). From 1c5b676426120dbfe3971c17de6905c115766a20 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Tue, 13 May 2025 12:13:59 -0400 Subject: [PATCH 3/4] [Pre4, Blazor only] New PATCH w/`System.Text.Json` (#35456) --- aspnetcore/blazor/call-web-api.md | 55 +++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) 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 Date: Tue, 13 May 2025 10:23:43 -0600 Subject: [PATCH 4/4] Code prep (#35457) * Code prep * Code prep --- .../10.x/api/Controllers/HomeController.cs | 104 ++++++++++++++++++ .../samples/10.x/api/JsonPatchSample.csproj | 17 +++ .../samples/10.x/api/Models/Category.cs | 9 ++ .../samples/10.x/api/Models/Customer.cs | 7 ++ .../samples/10.x/api/Models/Order.cs | 7 ++ .../samples/10.x/api/Models/Product.cs | 8 ++ .../jsonpatch/samples/10.x/api/MyJPIF.cs | 24 ++++ .../jsonpatch/samples/10.x/api/Program.cs | 42 +++++++ .../samples/10.x/api/appsettings.json | 9 ++ 9 files changed, 227 insertions(+) create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/appsettings.json 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": "*" +}