diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 92ddd1d4859e..8bfbd4cc35d7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,7 +1,7 @@ --- author: tdykstra ms.author: wpickett -ms.date: 09-21-2025 +ms.date: 10-16-2025 --- # Copilot Instructions for `dotnet/AspNetCore.Docs` @@ -78,6 +78,7 @@ When working on an issue: ## Repository-Specific Guidelines - [ ] Follow the [Microsoft Writing Style Guide](https://learn.microsoft.com/en-us/style-guide/welcome/) + - [ ] Use contractions following the guidance in [Use contractions](https://learn.microsoft.com/en-us/style-guide/word-choice/use-contractions) - [ ] **Repository Exceptions**: - [ ] Number ordered lists as "1." for every item (don't use sequential numbers) - [ ] Use backticks around content specifically for file names (`file.txt`), folders (`folder`), file paths (`folder/file.txt`), custom types (`myVariable`, `MyClass`), raw URLs in the text (`https://www.contoso.com`), URL segments (`/product/id/199`), file extensions (`.razor`), NuGet packages (`Microsoft.AspNetCore.SignalR.Client`), and code that should never be localized diff --git a/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Create.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Create.cshtml.cs index 0e8927d5728e..b3eb5e393a51 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Create.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Create.cshtml.cs @@ -43,7 +43,7 @@ public async Task OnPostAsync() return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. emptyCourse.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Edit.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Edit.cshtml.cs index fa5ba61efb7e..c4ccad53760c 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Edit.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu-completed-stages/part7/ContosoUniversity/Pages/Courses/Edit.cshtml.cs @@ -32,7 +32,7 @@ public async Task OnGetAsync(int? id) return NotFound(); } - // Select current DepartmentID. + // Populate departments dropdown. Course.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, Course.DepartmentID); return Page(); } @@ -60,7 +60,7 @@ public async Task OnPostAsync(int? id) return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. courseToUpdate.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Create.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Create.cshtml.cs index 610cbdf2ac28..8ab53562dc6f 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Create.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Create.cshtml.cs @@ -41,7 +41,7 @@ public async Task OnPostAsync() return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. emptyCourse.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Edit.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Edit.cshtml.cs index c3fb527d311a..825eec2db7a7 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Edit.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu/Pages/Courses/Edit.cshtml.cs @@ -32,7 +32,7 @@ public async Task OnGetAsync(int? id) return NotFound(); } - // Select current DepartmentID. + // Populate departments dropdown. Course.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context,Course.DepartmentID); return Page(); } @@ -55,7 +55,7 @@ public async Task OnPostAsync(int? id) return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. courseToUpdate.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Create.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Create.cshtml.cs index a4b96567b041..4c412b441bf9 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Create.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Create.cshtml.cs @@ -53,7 +53,7 @@ public async Task OnPostAsync() return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. emptyCourse.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Edit.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Edit.cshtml.cs index 709aeac42b71..eee8962bed82 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Edit.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part7/Pages/Courses/Edit.cshtml.cs @@ -38,7 +38,7 @@ public async Task OnGetAsync(int? id) return NotFound(); } - // Select current DepartmentID. + // Populate departments dropdown. Course.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, Course.DepartmentID); return Page(); } @@ -61,7 +61,7 @@ public async Task OnPostAsync(int? id) return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. courseToUpdate.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Create.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Create.cshtml.cs index a4b96567b041..4c412b441bf9 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Create.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Create.cshtml.cs @@ -53,7 +53,7 @@ public async Task OnPostAsync() return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. emptyCourse.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Edit.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Edit.cshtml.cs index 709aeac42b71..eee8962bed82 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Edit.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu20snapshots/cu-part8/Pages/Courses/Edit.cshtml.cs @@ -38,7 +38,7 @@ public async Task OnGetAsync(int? id) return NotFound(); } - // Select current DepartmentID. + // Populate departments dropdown. Course.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, Course.DepartmentID); return Page(); } @@ -61,7 +61,7 @@ public async Task OnPostAsync(int? id) return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. courseToUpdate.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Create.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Create.cshtml.cs index 2d9006b15167..258ec1968602 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Create.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Create.cshtml.cs @@ -36,7 +36,7 @@ public async Task OnPostAsync() return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. emptyCourse.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Edit.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Edit.cshtml.cs index 582ff5a656da..f23a02ab60eb 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Edit.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Courses/Edit.cshtml.cs @@ -32,7 +32,7 @@ public async Task OnGetAsync(int? id) return NotFound(); } - // Select current DepartmentID. + // Populate departments dropdown. Course.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, Course.DepartmentID); return Page(); } @@ -60,7 +60,7 @@ public async Task OnPostAsync(int? id) return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. courseToUpdate.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Create.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Create.cshtml.cs index 2d9006b15167..258ec1968602 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Create.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Create.cshtml.cs @@ -36,7 +36,7 @@ public async Task OnPostAsync() return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. emptyCourse.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Edit.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Edit.cshtml.cs index 582ff5a656da..f23a02ab60eb 100644 --- a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Edit.cshtml.cs +++ b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Courses/Edit.cshtml.cs @@ -32,7 +32,7 @@ public async Task OnGetAsync(int? id) return NotFound(); } - // Select current DepartmentID. + // Populate departments dropdown. Course.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, Course.DepartmentID); return Page(); } @@ -60,7 +60,7 @@ public async Task OnPostAsync(int? id) return RedirectToPage("./Index"); } - // Select DepartmentID if TryUpdateModelAsync fails. + // Repopulate departments dropdown. courseToUpdate.DepartmentID determines the selected item. PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID); return Page(); } diff --git a/aspnetcore/data/ef-rp/update-related-data.md b/aspnetcore/data/ef-rp/update-related-data.md index 04f11200b2cd..7bbb093802c8 100644 --- a/aspnetcore/data/ef-rp/update-related-data.md +++ b/aspnetcore/data/ef-rp/update-related-data.md @@ -30,8 +30,10 @@ Create a `Pages/Courses/DepartmentNamePageModel.cs` file with the following code [!code-csharp[](intro/samples/cu50/Pages/Courses/DepartmentNamePageModel.cs)] -The preceding code creates a to contain the list of department names. If `selectedDepartment` is specified, that department is selected in the `SelectList`. +The preceding code creates a to contain the list of department names. The `selectedDepartment` parameter allows the calling code to specify the item that will be selected when the drop-down list is rendered. However, when using the Select Tag Helper with `asp-for`, the selected item is determined by the model property value (such as `Course.DepartmentID`), not by the `selectedDepartment` parameter. +> [!IMPORTANT] +> When using the Select Tag Helper with `asp-for="Course.DepartmentID"`, the selected option is automatically determined by the value of `Course.DepartmentID`. The `selectedDepartment` parameter passed to the SelectList constructor is ignored in this scenario. The Create and Edit page model classes will derive from `DepartmentNamePageModel`. ### Update the Course Create page model @@ -77,7 +79,7 @@ Update `Pages/Courses/Edit.cshtml.cs` with the following code: [!code-csharp[](intro/samples/cu50/Pages/Courses/Edit.cshtml.cs?highlight=8,28,35,36,40-66)] -The changes are similar to those made in the Create page model. In the preceding code, `PopulateDepartmentsDropDownList` passes in the department ID, which selects that department in the drop-down list. +The changes are similar to those made in the Create page model. In the preceding code, `PopulateDepartmentsDropDownList` passes in the department ID. When using the Select Tag Helper with `asp-for="Course.DepartmentID"`, the selected item in the drop-down list is determined by the value of `Course.DepartmentID`, not by the `selectedDepartment` parameter passed to the SelectList constructor. ### Update the Course Edit Razor page @@ -253,7 +255,7 @@ Create a `Pages/Courses/DepartmentNamePageModel.cs` file with the following code [!code-csharp[](intro/samples/cu30/Pages/Courses/DepartmentNamePageModel.cs)] -The preceding code creates a to contain the list of department names. If `selectedDepartment` is specified, that department is selected in the `SelectList`. +The preceding code creates a to contain the list of department names. The `selectedDepartment` parameter allows the calling code to specify the item that will be selected when the drop-down list is rendered. However, when using the Select Tag Helper with `asp-for`, the selected item is determined by the model property value (such as `Course.DepartmentID`), not by the `selectedDepartment` parameter. The Create and Edit page model classes will derive from `DepartmentNamePageModel`. @@ -300,7 +302,7 @@ Update `Pages/Courses/Edit.cshtml.cs` with the following code: [!code-csharp[](intro/samples/cu30/Pages/Courses/Edit.cshtml.cs?highlight=8,28,35,36,40-66)] -The changes are similar to those made in the Create page model. In the preceding code, `PopulateDepartmentsDropDownList` passes in the department ID, which selects that department in the drop-down list. +The changes are similar to those made in the Create page model. In the preceding code, `PopulateDepartmentsDropDownList` passes in the department ID. When using the Select Tag Helper with `asp-for="Course.DepartmentID"`, the selected item in the drop-down list is determined by the value of `Course.DepartmentID`, not by the `selectedDepartment` parameter passed to the SelectList constructor. ### Update the Course Edit Razor page @@ -470,7 +472,7 @@ The Courses/Create and Courses/Edit pages each need a list of department names. [!code-csharp[](intro/samples/cu/Pages/Courses/DepartmentNamePageModel.cshtml.cs?highlight=9,11,20-21)] -The preceding code creates a to contain the list of department names. If `selectedDepartment` is specified, that department is selected in the `SelectList`. +The preceding code creates a to contain the list of department names. The `selectedDepartment` parameter allows the calling code to specify the item that will be selected when the drop-down list is rendered. However, when using the Select Tag Helper with `asp-for`, the selected item is determined by the model property value (such as `Course.DepartmentID`), not by the `selectedDepartment` parameter. The Create and Edit page model classes will derive from `DepartmentNamePageModel`. @@ -517,7 +519,7 @@ Replace the code in `Pages/Courses/Edit.cshtml.cs` with the following code: [!code-csharp[](intro/samples/cu/Pages/Courses/Edit.cshtml.cs?highlight=8,28,35,36,40,47-999)] -The changes are similar to those made in the Create page model. In the preceding code, `PopulateDepartmentsDropDownList` passes in the department ID, which select the department specified in the drop-down list. +The changes are similar to those made in the Create page model. In the preceding code, `PopulateDepartmentsDropDownList` passes in the department ID. When using the Select Tag Helper with `asp-for="Course.DepartmentID"`, the selected item in the drop-down list is determined by the value of `Course.DepartmentID`, not by the `selectedDepartment` parameter passed to the SelectList constructor. Update `Pages/Courses/Edit.cshtml` with the following markup: diff --git a/aspnetcore/fundamentals/host/platform-specific-configuration.md b/aspnetcore/fundamentals/host/platform-specific-configuration.md index 6b5ed4a0271c..db0b7626cf40 100644 --- a/aspnetcore/fundamentals/host/platform-specific-configuration.md +++ b/aspnetcore/fundamentals/host/platform-specific-configuration.md @@ -5,7 +5,7 @@ description: Discover how to enhance an ASP.NET Core app from an external assemb monikerRange: '>= aspnetcore-2.1' ms.author: tdykstra ms.custom: mvc -ms.date: 09/26/2019 +ms.date: 09/04/2025 uid: fundamentals/configuration/platform-specific-configuration --- # Use hosting startup assemblies in ASP.NET Core @@ -336,6 +336,9 @@ deployment/additionalDeps/shared/Microsoft.AspNetCore.App/3.0.0/StartupDiagnosti For runtime to discover the runtime store location, the additional dependencies file location is added to the `DOTNET_ADDITIONAL_DEPS` environment variable. +> [!NOTE] +> The `DOTNET_ADDITIONAL_DEPS` environment variable only works with framework-dependent applications. This environment variable is ignored for self-contained applications. + In the sample app (*RuntimeStore* project), building the runtime store and generating the additional dependencies file is accomplished using a [PowerShell](/powershell/scripting/overview) script. For examples of how to set environment variables for various operating systems, see [Use multiple environments](xref:fundamentals/environments). @@ -722,6 +725,9 @@ deployment/additionalDeps/shared/Microsoft.AspNetCore.App/2.1.0/StartupDiagnosti For runtime to discover the runtime store location, the additional dependencies file location is added to the `DOTNET_ADDITIONAL_DEPS` environment variable. +> [!NOTE] +> The `DOTNET_ADDITIONAL_DEPS` environment variable only works with framework-dependent applications. This environment variable is ignored for self-contained applications. + In the sample app (*RuntimeStore* project), building the runtime store and generating the additional dependencies file is accomplished using a [PowerShell](/powershell/scripting/overview) script. For examples of how to set environment variables for various operating systems, see [Use multiple environments](xref:fundamentals/environments). diff --git a/aspnetcore/fundamentals/openapi/customize-openapi.md b/aspnetcore/fundamentals/openapi/customize-openapi.md index 7866e4c47686..75d0b230f4fa 100644 --- a/aspnetcore/fundamentals/openapi/customize-openapi.md +++ b/aspnetcore/fundamentals/openapi/customize-openapi.md @@ -102,6 +102,14 @@ For example, the following operation transformer adds `500` as a response status [!code-csharp[](~/fundamentals/openapi/samples/10.x/WebMinOpenApi/Program.cs?name=snippet_operationtransformer1)] + + +Operation transformers can also be added to specific endpoint with the `AddOpenApiOperationTransformer` API, instead of all endpoints in a document. This can be useful to change specific OpenAPI data for a specific endpoint, like adding a security scheme, response description or other OpenAPI operation properties. The following example demonstrates adding an operation transformer to a deprecated endpoint specifically, which marks the endpoint as deprecated in the OpenAPI document. + +[!code-csharp[](~/fundamentals/openapi/samples/10.x/WebMinOpenApi/Program.cs?name=snippet_operationtransformer2)] + ## Use schema transformers Schemas are the data models that are used in request and response bodies in an OpenAPI document. Schema transformers are useful when a modification: diff --git a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/Program.cs b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/Program.cs index b33984981bf1..95e3a6624007 100644 --- a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/Program.cs +++ b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/Program.cs @@ -2,7 +2,7 @@ #define DOCUMENTtransformerInOut //#define DOCUMENTtransformer1 //#define DOCUMENTtransformer2 -// #define DOCUMENTtransformerUse999 +//#define DOCUMENTtransformerUse999 //#define FIRST //#define OPENAPIWITHSCALAR //#define MAPOPENAPIWITHCACHING @@ -10,6 +10,7 @@ //#define SWAGGERUI //#define MULTIDOC_OPERATIONtransformer1 //#define OPERATIONtransformer1 +//#define OPERATIONtransformer2 #if DEFAULT // @@ -61,7 +62,7 @@ internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(options => { @@ -96,7 +97,7 @@ internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; var builder = WebApplication.CreateBuilder(); @@ -125,7 +126,7 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync(); if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer")) { - var requirements = new Dictionary + var securitySchemes = new Dictionary { ["Bearer"] = new OpenApiSecurityScheme { @@ -136,7 +137,7 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf } }; document.Components ??= new OpenApiComponents(); - document.Components.SecuritySchemes = requirements; + document.Components.SecuritySchemes = securitySchemes; } } } @@ -149,7 +150,7 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; var builder = WebApplication.CreateBuilder(); @@ -159,6 +160,7 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf { options.AddOperationTransformer((operation, context, cancellationToken) => { + operation.Responses ??= new OpenApiResponses(); operation.Responses.Add("500", new OpenApiResponse { Description = "Internal server error" }); return Task.CompletedTask; }); @@ -177,13 +179,44 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf // #endif +#if OPERATIONtransformer2 +// +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = WebApplication.CreateBuilder(); + +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.MapGet("/old", () => "This endpoint is old and should not be used anymore") +.AddOpenApiOperationTransformer((operation, context, cancellationToken) => +{ + operation.Deprecated = true; + return Task.CompletedTask; +}); + +app.MapGet("/new", () => "This endpoint replaces /old"); + +app.Run(); +// +#endif + #if MULTIDOC_OPERATIONtransformer1 // using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; var builder = WebApplication.CreateBuilder(); @@ -217,7 +250,7 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer")) { // Add the security scheme at the document level - var requirements = new Dictionary + var securitySchemes = new Dictionary { ["Bearer"] = new OpenApiSecurityScheme { @@ -228,14 +261,15 @@ public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransf } }; document.Components ??= new OpenApiComponents(); - document.Components.SecuritySchemes = requirements; + document.Components.SecuritySchemes = securitySchemes; // Apply it as a requirement for all operations foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations)) { + operation.Value.Security ??= []; operation.Value.Security.Add(new OpenApiSecurityRequirement { - [new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }] = Array.Empty() + [new OpenApiSecuritySchemeReference("Bearer")] = [] }); } } @@ -285,9 +319,9 @@ public class Body { using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); @@ -316,9 +350,9 @@ public class Body { using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication().AddJwtBearer(); builder.Services.AddAuthorization(o => @@ -343,9 +377,9 @@ public class Body { using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache(options => { @@ -374,10 +408,10 @@ public class Body { using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Scalar.AspNetCore; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); @@ -417,9 +451,9 @@ public class Body { #if DOCUMENTtransformerUse999 // using Microsoft.AspNetCore.OpenApi; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(options => { @@ -485,9 +519,9 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext #if DOCUMENTtransformerInOut // using Microsoft.AspNetCore.OpenApi; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; -var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(options => { diff --git a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/WebMinOpenApi.csproj b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/WebMinOpenApi.csproj index d83ead4e0098..3c487b035926 100644 --- a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/WebMinOpenApi.csproj +++ b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/WebMinOpenApi.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable ./ @@ -14,14 +14,14 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + diff --git a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/global.json b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/global.json index 6091d76c863a..6e016cfb3eeb 100644 --- a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/global.json +++ b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/global.json @@ -1,5 +1,7 @@ { "sdk": { - "version": "9.0.100-rc.1.24414.13" + "version": "10.0.0", + "rollForward": "latestFeature", + "allowPrerelease": true } } diff --git a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/projectFile.xml b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/projectFile.xml index 0ff4d21eb2d5..c92813a8bdd1 100644 --- a/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/projectFile.xml +++ b/aspnetcore/fundamentals/openapi/samples/10.x/WebMinOpenApi/projectFile.xml @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable @@ -12,8 +12,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/aspnetcore/fundamentals/websockets.md b/aspnetcore/fundamentals/websockets.md index a65fea5df19a..b8f1ee86e4ca 100644 --- a/aspnetcore/fundamentals/websockets.md +++ b/aspnetcore/fundamentals/websockets.md @@ -5,7 +5,7 @@ description: Learn how to get started with WebSockets in ASP.NET Core. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 07/09/2025 +ms.date: 10/13/2025 uid: fundamentals/websockets --- # WebSockets support in ASP.NET Core @@ -33,7 +33,7 @@ These supported features are available in Kestrel on all HTTP/2 enabled platform > HTTP/2 WebSockets use CONNECT requests rather than GET, so your own routes and controllers may need updating. > For more information, see [Add HTTP/2 WebSockets support for existing controllers](#add-http2-websockets-support-for-existing-controllers) in this article. > -> Chrome and Edge have HTTP/2 WebSockets enabled by default, and you can enable it in FireFox on the `about:config` page with the `network.http.spdy.websockets` flag. +> Chrome, Edge, and Firefox (version 128 and later) have HTTP/2 WebSockets enabled by default. You can verify or change this setting in Firefox by opening `about:config` and locating the `network.http.http2.websockets` preference. WebSockets were originally designed for HTTP/1.1 but have since been adapted to work over HTTP/2. ([RFC 8441](https://www.rfc-editor.org/rfc/rfc8441)) diff --git a/aspnetcore/fundamentals/websockets/includes/websockets7.md b/aspnetcore/fundamentals/websockets/includes/websockets7.md index de7bf43d57c2..145ff8caa844 100644 --- a/aspnetcore/fundamentals/websockets/includes/websockets7.md +++ b/aspnetcore/fundamentals/websockets/includes/websockets7.md @@ -19,7 +19,7 @@ These supported features are available in Kestrel on all HTTP/2 enabled platform > HTTP/2 WebSockets use CONNECT requests rather than GET, so your own routes and controllers may need updating. > For more information, see [Add HTTP/2 WebSockets support for existing controllers](#add-http2-websockets-support-for-existing-controllers) in this article. > -> Chrome and Edge have HTTP/2 WebSockets enabled by default, and you can enable it in FireFox on the `about:config` page with the `network.http.spdy.websockets` flag. +> Chrome, Edge, and Firefox (version 128 and later) have HTTP/2 WebSockets enabled by default. You can verify or change this setting in Firefox by opening `about:config` and locating the `network.http.http2.websockets` preference. WebSockets were originally designed for HTTP/1.1 but have since been adapted to work over HTTP/2. ([RFC 8441](https://www.rfc-editor.org/rfc/rfc8441)) diff --git a/aspnetcore/fundamentals/websockets/includes/websockets8.md b/aspnetcore/fundamentals/websockets/includes/websockets8.md index 74b287139c90..900a4e8013f2 100644 --- a/aspnetcore/fundamentals/websockets/includes/websockets8.md +++ b/aspnetcore/fundamentals/websockets/includes/websockets8.md @@ -19,7 +19,7 @@ These supported features are available in Kestrel on all HTTP/2 enabled platform > HTTP/2 WebSockets use CONNECT requests rather than GET, so your own routes and controllers may need updating. > For more information, see [Add HTTP/2 WebSockets support for existing controllers](#add-http2-websockets-support-for-existing-controllers) in this article. > -> Chrome and Edge have HTTP/2 WebSockets enabled by default, and you can enable it in FireFox on the `about:config` page with the `network.http.spdy.websockets` flag. +> Chrome, Edge, and Firefox (version 128 and later) have HTTP/2 WebSockets enabled by default. You can verify or change this setting in Firefox by opening `about:config` and locating the `network.http.http2.websockets` preference. WebSockets were originally designed for HTTP/1.1 but have since been adapted to work over HTTP/2. ([RFC 8441](https://www.rfc-editor.org/rfc/rfc8441)) diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index 2778bf16d292..c4e0d51aa207 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -437,49 +437,56 @@ For more information and examples, see : +The recommended approach that works regardless of the response state is to call . When `NavigationManager.NotFound` is called, the middleware renders the path passed to the method: - ```csharp - app.UseStatusCodePagesWithReExecute( - "/not-found", createScopeForStatusCodePages: true); - ``` +```csharp +app.UseStatusCodePagesWithReExecute( + "/not-found", createScopeForStatusCodePages: true); +``` -* When the response has started, the can be used by subscribing to the `OnNotFoundEvent` in the router: - - ```razor - @code { - [CascadingParameter] - public HttpContext? HttpContext { get; set; } - - private void OnNotFoundEvent(object sender, NotFoundEventArgs e) - { - // Only execute the logic if HTTP response has started, - // because setting NotFoundEventArgs.Path blocks re-execution - if (HttpContext?.Response.HasStarted == false) - { - return; - } - - var type = typeof(CustomNotFoundPage); - var routeAttributes = type.GetCustomAttributes(inherit: true); - - if (routeAttributes.Length == 0) - { - throw new InvalidOperationException($"The type {type.FullName} " + - $"doesn't have a {typeof(RouteAttribute).FullName} applied."); - } - - var routeAttribute = (RouteAttribute)routeAttributes[0]; - - if (routeAttribute.Template != null) - { - e.Path = routeAttribute.Template; - } - } - } - ``` +If you don't want to use , the app can still support `NavigationManager.NotFound` for responses that have already started. Subscribe to `OnNotFoundEvent` in the router and assign the Not Found page path to `NotFoundEventArgs.Path` to inform the renderer what content to render when `NavigationManager.NotFound` is called. + +`CustomRouter.razor`: + +```razor +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Http +@implements IDisposable +@inject NavigationManager NavigationManager + +@code { + protected override void OnInitialized() => + NavigationManager.OnNotFound += OnNotFoundEvent; + + [CascadingParameter] + public HttpContext? HttpContext { get; set; } + + private void OnNotFoundEvent(object sender, NotFoundEventArgs e) + { + // Only execute the logic if HTTP response has started + // because setting NotFoundEventArgs.Path blocks re-execution + if (HttpContext?.Response.HasStarted == false) + { + return; + } + + e.Path = GetNotFoundRoutePath(); + } + + // Return the path of the Not Found page that you want to display + private string GetNotFoundRoutePath() + { + ... + } + + public void Dispose() => NavigationManager.OnNotFound -= OnNotFoundEvent; +} +``` + +If you use both approaches in your app, the Not Found path specified in the `OnNotFoundEvent` handler takes precedence over the path configured in the re-execution middleware. ### Metrics and tracing diff --git a/aspnetcore/test/razor-pages-tests.md b/aspnetcore/test/razor-pages-tests.md index 78e1ff49dd25..2292379ec12b 100644 --- a/aspnetcore/test/razor-pages-tests.md +++ b/aspnetcore/test/razor-pages-tests.md @@ -148,7 +148,7 @@ Another set of unit tests is responsible for tests of page model methods. In the | `OnPostDeleteMessageAsync` | Executes `DeleteMessageAsync` to delete a message with the `Id` specified. | | `OnPostAnalyzeMessagesAsync` | If one or more messages are in the database, calculates the average number of words per message. | -The page model methods are tested using seven tests in the `IndexPageTests` class (`tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs`). The tests use the familiar Arrange-Assert-Act pattern. These tests focus on: +The page model methods are tested using seven tests in the `IndexPageTests` class (`tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs`). The tests use the familiar Arrange-Act-Assert pattern. These tests focus on: * Determining if the methods follow the correct behavior when the [ModelState](xref:Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary) is invalid. * Confirming the methods produce the correct . @@ -331,7 +331,7 @@ Another set of unit tests is responsible for tests of page model methods. In the | `OnPostDeleteMessageAsync` | Executes `DeleteMessageAsync` to delete a message with the `Id` specified. | | `OnPostAnalyzeMessagesAsync` | If one or more messages are in the database, calculates the average number of words per message. | -The page model methods are tested using seven tests in the `IndexPageTests` class (`tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs`). The tests use the familiar Arrange-Assert-Act pattern. These tests focus on: +The page model methods are tested using seven tests in the `IndexPageTests` class (`tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs`). The tests use the familiar Arrange-Act-Assert pattern. These tests focus on: * Determining if the methods follow the correct behavior when the [ModelState](xref:Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary) is invalid. * Confirming the methods produce the correct .