{"id":40865,"date":"2022-07-12T10:08:46","date_gmt":"2022-07-12T17:08:46","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=40865"},"modified":"2022-07-21T14:53:39","modified_gmt":"2022-07-21T21:53:39","slug":"asp-net-core-updates-in-dotnet-7-preview-6","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/asp-net-core-updates-in-dotnet-7-preview-6\/","title":{"rendered":"ASP.NET Core updates in .NET 7 Preview 6"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-dotnet-7-preview-6\">.NET 7 Preview 6 is now available<\/a> and includes many great new improvements to ASP.NET Core.<\/p>\n<p>Here&#8217;s a summary of what&#8217;s new in this preview release:<\/p>\n<ul>\n<li>Request decompression middleware<\/li>\n<li>Output caching middleware<\/li>\n<li>Updates to rate limiting middleware<\/li>\n<li>Kestrel support for WebSockets over HTTP\/2<\/li>\n<li>Kestrel performance improvements on high core machines<\/li>\n<li>Support for logging additional request headers in W3CLogger<\/li>\n<li>Empty Blazor project templates<\/li>\n<li>System.Security.Cryptography support on WebAssembly<\/li>\n<li>Blazor custom elements no longer experimental<\/li>\n<li>Experimental <code>QuickGrid<\/code> component for Blazor<\/li>\n<li>gRPC JSON transcoding multi-segment parameters<\/li>\n<li><code>MapGroup<\/code> support for more extension methods<\/li>\n<\/ul>\n<p>For more details on the ASP.NET Core work planned for .NET 7 see the full <a href=\"https:\/\/aka.ms\/aspnet\/roadmap\">ASP.NET Core roadmap for .NET 7<\/a> on GitHub.<\/p>\n<h2>Get started<\/h2>\n<p>To get started with ASP.NET Core in .NET 7 Preview 6, <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/7.0\">install the .NET 7 SDK<\/a>.<\/p>\n<p>If you&#8217;re on Windows using Visual Studio, we recommend installing the latest <a href=\"https:\/\/visualstudio.com\/preview\">Visual Studio 2022 preview<\/a>. If you&#8217;re on macOS, we recommend installing the latest <a href=\"https:\/\/visualstudio.microsoft.com\/vs\/mac\/preview\/\">Visual Studio 2022 for Mac preview<\/a>.<\/p>\n<p>To install the latest .NET WebAssembly build tools, run the following command from an elevated command prompt:<\/p>\n<pre><code class=\"language-console\">dotnet workload install wasm-tools<\/code><\/pre>\n<blockquote><p>Note: Building .NET 6 Blazor projects with the .NET 7 SDK and the .NET 7 WebAssembly build tools is currently not supported. This will be addressed in a future .NET 7 update: <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/65211\">dotnet\/runtime#65211<\/a>.<\/p><\/blockquote>\n<h2>Upgrade an existing project<\/h2>\n<p>To upgrade an existing ASP.NET Core app from .NET 7 Preview 5 to .NET 7 Preview 6:<\/p>\n<ul>\n<li>Update all Microsoft.AspNetCore.* package references to <code>7.0.0-preview.6.*<\/code>.<\/li>\n<li>Update all Microsoft.Extensions.* package references to <code>7.0.0-preview.6.*<\/code>.<\/li>\n<\/ul>\n<p>See also the full list of <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/compatibility\/7.0#aspnet-core\">breaking changes<\/a> in ASP.NET Core for .NET 7.<\/p>\n<h2>Request decompression middleware<\/h2>\n<p>The request decompression middleware is a new middleware that uses the <code>Content-Encoding<\/code> HTTP header to automatically identify and decompress requests with compressed content so that the developer of the server does not need to handle this themselves.<\/p>\n<p>The request decompression middleware is added using the <code>UseRequestDecompression<\/code> extension method on <code>IApplicationBuilder<\/code> and the <code>AddRequestDecompression<\/code> extension method for <code>IServiceCollection<\/code>.<\/p>\n<p>Support for Brotli (<code>br<\/code>), Deflate (<code>deflate<\/code>), and GZip (<code>gzip<\/code>) are included out of the box. Other content encodings can be added by registering a custom decompression provider class, which implements the <code>IDecompressionProvider<\/code> interface, along with a <code>Content-Encoding<\/code> header value in <code>RequestDecompressionOptions<\/code>.<\/p>\n<p>Thanks to <a href=\"https:\/\/github.com\/david-acker\">@david-acker<\/a> for contributing this feature!<\/p>\n<h2>Output caching middleware<\/h2>\n<p>Output caching is a new middleware that helps you store results from your web app and serve them from a cache rather than computing them every time, which improves performance and frees up resources for other activities.<\/p>\n<p>To get started with output caching, use the <code>AddOutputCache<\/code> extension method on <code>IServiceCollection<\/code> and the <code>UseOutputCache<\/code> extension method on <code>IApplicationBuilder<\/code>.<\/p>\n<p>Once you&#8217;ve done so, you can start configuring output caching on your endpoints. Here&#8217;s a simple example of using output caching on an endpoint that returns timestamps.<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/notcached\", () =&gt; DateTime.Now.ToString());\r\n\r\napp.MapGet(\"\/cached\", () =&gt; DateTime.Now.ToString()).CacheOutput();<\/code><\/pre>\n<p>Requests sent to &#8220;\/notcached&#8221; will see the current time. But the &#8220;\/cached&#8221; endpoint uses the <code>.CacheOutput()<\/code> extension, so each request to &#8220;\/cached&#8221; after the first one will get a cached response (the time returned will not update after the first request).<\/p>\n<p>There are many more advanced ways to customize output caching. The example below shows how <code>VaryByQuery<\/code> can be used to control caching based on a query parameter:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Cached entries will vary by culture, but any other additional query\r\n\/\/ is ignored and returns the same cached content.\r\napp.MapGet(\"\/query\", () =&gt; DateTime.Now.ToString()).CacheOutput(p =&gt; p.VaryByQuery(\"culture\"));<\/code><\/pre>\n<p>With this configuration, the &#8220;\/query&#8221; endpoint will cache its output uniquely per value of the <code>culture<\/code> parameter. Requests for <code>\/query?culture=en<\/code> will get a cached response and requests for <code>\/query?culture=es<\/code> will get a different cached response.<\/p>\n<p>In addition to varying by query strings, there is also support for varying cached responses by headers (<code>VaryByHeader<\/code>) or by custom values (<code>VaryByValue<\/code>).<\/p>\n<p>The output caching middleware has built in protection for some common pitfalls of caching. Imagine the situation where you have a busy web service and you revoke\/invalidate a popular cache entry. When that happens, there can be many requests for that entry all at the same time, and because the response is not in the cache at that moment, the server would have to process each of those requests.<\/p>\n<p>Resource locking is a feature of output caching that prevents the server from being overwhelmed by these &#8220;cache stampedes&#8221;. It does this by only allowing one request for the resource to be processed, while the rest of them wait for the cache entry to be updated. Once that happens, all of the waiting requests can be served from the cache, preventing the server from being inundated by redundant work.<\/p>\n<p>If you&#8217;d like to see a more complete example demonstrating these and other features of output caching, take a look at the <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/tree\/main\/src\/Middleware\/OutputCaching\/samples\/OutputCachingSample\">OutputCachingSample app<\/a> in the ASP.NET Core repo.<\/p>\n<p>For a deeper dive into the output caching feature, be sure to check out this <a href=\"https:\/\/mybuild.microsoft.com\/sessions\/92e64465-4bd1-486a-a502-25f2577e7fac\">on-demand session we presented at Build 2022<\/a>. Note that some of the features\/APIs discussed have changed since the talk was recorded.<\/p>\n<p>We plan to continue to add functionality to this middleware, so please let us know what you&#8217;d like to see.<\/p>\n<h2>Updates to rate limiting middleware<\/h2>\n<p>The rate limiting middleware now supports rate limiting on specific endpoints, which can be combined with a global limiter that runs on all requests. Below is a sample that adds a <code>TokenBucketRateLimiter<\/code> to an endpoint, and uses a global <code>ConcurrencyLimiter<\/code>:<\/p>\n<pre><code class=\"language-csharp\">var coolEndpointName = \"coolPolicy\";\r\n\r\n\/\/ Define endpoint limiters and a global limiter.\r\n\/\/ coolEndpoint will be limited by a TokenBucket Limiter with a max permit count of 6 and a queue depth of 3.\r\nvar options = new RateLimiterOptions()\r\n    .AddTokenBucketLimiter(coolEndpointName, new TokenBucketRateLimiterOptions(3, QueueProcessingOrder.OldestFirst, 6, TimeSpan.FromSeconds(10), 1))\r\n\r\n\/\/ The global limiter will be a concurrency limiter with a max permit count of 10 and a queue depth of 5.\r\noptions.GlobalLimiter = PartitionedRateLimiter.Create&lt;HttpContext, string&gt;(context =&gt;\r\n    {\r\n        return RateLimitPartition.CreateConcurrencyLimiter&lt;string&gt;(\"globalLimiter\", key =&gt; new ConcurrencyLimiterOptions(10, QueueProcessingOrder.NewestFirst, 5));\r\n    });\r\napp.UseRateLimiter(options);\r\n\r\napp.MapGet(\"\/\", () =&gt; \"Hello World!\").RequireRateLimiting(coolEndpointName);<\/code><\/pre>\n<p>We also support adding custom rate limiting policies through the new <code>AddPolicy<\/code> methods on <code>RateLimiterOptions<\/code>.<\/p>\n<p>To learn more about rate limiting with ASP.NET Core, check out the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-rate-limiting-for-dotnet\/\">Announcing Rate Limiting for .NET<\/a> blog post.<\/p>\n<h2>Kestrel support for WebSockets over HTTP\/2<\/h2>\n<p><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc6455.html\">WebSockets<\/a> were originally designed for HTTP\/1.1 but have since been <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8441.html\">adapted<\/a> to work over HTTP\/2. Using WebSockets over HTTP\/2 lets you take advantage of new features like header compression and multiplexing that reduce the time and resources needed when making multiple requests to the server. That support is now available in Kestrel on all HTTP\/2 enabled platforms.<\/p>\n<p>The HTTP version negotiation is automatic in browsers and Kestrel so no new APIs are needed. You can follow the <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/websockets\">existing ASP.NET Core samples<\/a> for using WebSockets over HTTP\/2. Note HTTP\/2 WebSockets use CONNECT requests rather than GET, so your routes and controllers may need updating. Chrome and Edge have HTTP\/2 WebSockets enabled by default, and you can enable it in FireFox on the <code>about:config<\/code> page with the <code>network.http.spdy.websockets<\/code> flag.<\/p>\n<p>SignalR and the SignalR browser JavaScript client have been updated to support WebSockets over HTTP\/2. Support is also coming to <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/69669\">ClientWebSocket<\/a> and <a href=\"https:\/\/github.com\/microsoft\/reverse-proxy\/issues\/1375\">YARP<\/a> soon.<\/p>\n<h2>Kestrel performance improvements on high core machines<\/h2>\n<p>Kestrel uses <code>ConcurrentQueue<\/code> for many purposes. One purpose is scheduling I\/O operations in Kestrel&#8217;s default <code>Socket<\/code> transport. Partitioning the <code>ConcurrentQueue<\/code> based on the associated socket reduces contention and increases throughput on machines with many CPU cores.<\/p>\n<p>Recent profiling on new even higher core machines like <a href=\"https:\/\/azure.microsoft.com\/blog\/now-in-preview-azure-virtual-machines-with-ampere-altra-armbased-processors\/\">the 80 core ARM64 Ampere Altra VMs recently made available on Azure<\/a> showed a lot of contention in one of Kestrel&#8217;s other <code>ConcurrentQueue<\/code> instances, the <code>PinnedMemoryPool<\/code> which Kestrel uses to cache byte buffers.<\/p>\n<p>In Preview 6, Kestrel&#8217;s memory pool is now partitioned the same way as its I\/O queue leading to much lower contention and higher throughput on high core machines. We&#8217;re seeing an <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/42237#issuecomment-1159251566\">over 500% RPS improvement<\/a> in the TechEmpower plaintext benchmark on the previously mentioned 80 core ARM64 VMs and a <a href=\"https:\/\/aka.ms\/aspnet\/benchmarks\">nearly 100% improvement<\/a> on 48 Core AMD VMs in our HTTPS JSON benchmark.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/07\/kestrel-perf-graph.png\" alt=\"Benchmark line graph with an upward &quot;cliff&quot; showing RPS double\" \/><\/p>\n<h2>Support for logging additional request headers in W3CLogger<\/h2>\n<p>You can now specify additional request headers to log when using the W3C logger by calling <code>AdditionalRequestHeaders()<\/code> on <code>W3CLoggerOptions<\/code>:<\/p>\n<pre><code class=\"language-csharp\">services.AddW3CLogging(logging =&gt;\r\n{\r\n    logging.AdditionalRequestHeaders.Add(\"x-forwarded-for\");\r\n    logging.AdditionalRequestHeaders.Add(\"x-client-ssl-protocol\");\r\n});<\/code><\/pre>\n<p>Thanks to <a href=\"https:\/\/github.com\/dustinsoftware\">@dustinsoftware<\/a> for contributing this feature!<\/p>\n<h2>Empty Blazor project templates<\/h2>\n<p>Blazor has two new project templates for starting from a blank slate. The new &#8220;Blazor Server App Empty&#8221; and &#8220;Blazor WebAssembly App Empty&#8221; project templates are just like their non-empty counterparts but without any extra demo code. These empty templates have only a very basic home page, and we&#8217;ve also removed Bootstrap so you can start with whatever CSS framework you prefer.<\/p>\n<p>The new templates are available from within Visual Studio once you install the .NET 7 SDK:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/07\/blazor-empty-project-templates.png\" alt=\"Empty Blazor project templates\" \/><\/p>\n<p>You can also use the empty Blazor project templates from the command-line:<\/p>\n<pre><code class=\"language-console\">dotnet new blazorserver-empty\r\ndotnet new blazorwasm-empty<\/code><\/pre>\n<h2>System.Security.Cryptography support on WebAssembly<\/h2>\n<p>.NET 6 supported the SHA family of hashing algorithms when running on WebAssembly. .NET 7 enables more cryptographic algorithms by taking advantage of <a href=\"https:\/\/developer.mozilla.org\/docs\/Web\/API\/SubtleCrypto\">SubtleCrypto<\/a> when possible, and falling back to a .NET implementation when SubtleCrypto can&#8217;t be used. In .NET 7 Preview 6 the following algorithms are supported on WebAssembly:<\/p>\n<ul>\n<li>SHA1<\/li>\n<li>SHA256<\/li>\n<li>SHA384<\/li>\n<li>SHA512<\/li>\n<li>HMACSHA1<\/li>\n<li>HMACSHA256<\/li>\n<li>HMACSHA384<\/li>\n<li>HMACSHA512<\/li>\n<\/ul>\n<p>Support for AES-CBC, PBKDF2, and HKDF algorithms are planned for a future .NET 7 update.<\/p>\n<p>For more information, see <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/40074\">dotnet\/runtime#40074<\/a>.<\/p>\n<h2>Blazor custom elements no longer experimental<\/h2>\n<p>The previously experimental <a href=\"https:\/\/www.nuget.org\/packages\/microsoft.aspnetcore.components.customelements\">Microsoft.AspNetCore.Components.CustomElements<\/a> package for building <a href=\"https:\/\/html.spec.whatwg.org\/multipage\/custom-elements.html#custom-elements\">standards based custom elements<\/a> with Blazor is no longer experimental and is now part of the .NET 7 release.<\/p>\n<p>To create a custom element using Blazor, register a Blazor root component as a custom element like this:<\/p>\n<pre><code class=\"language-csharp\">options.RootComponents.RegisterCustomElement&lt;Counter&gt;(\"my-counter\");<\/code><\/pre>\n<p>You can then use this custom element with any other web framework you&#8217;d like:<\/p>\n<pre><code class=\"language-html\">&lt;my-counter increment-amount=\"10\"&gt;&lt;\/my-counter&gt;<\/code><\/pre>\n<p>For additional details, refer to the Blazor docs on <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/blazor\/components#blazor-custom-elements\">building custom elements<\/a>.<\/p>\n<h2>Experimental <code>QuickGrid<\/code> component for Blazor<\/h2>\n<p><code>QuickGrid<\/code> is a new experimental component for Blazor for quickly and efficiently displaying data in tabular form. <code>QuickGrid<\/code> provides a simple and convenient data grid component for the most common needs as well as a reference architecture and performance baseline for anyone building Blazor data grid components.<\/p>\n<p>To get started with <code>QuickGrid<\/code>:<\/p>\n<ol>\n<li>Add reference to the Microsoft.AspNetCore.Components.QuickGrid package.\n<pre><code class=\"language-console\">dotnet add package Microsoft.AspNetCore.Components.QuickGrid --prerelease<\/code><\/pre>\n<\/li>\n<li>Add the following Razor code to render a very simple grid.\n<pre><code class=\"language-razor\">&lt;QuickGrid Items=\"@people\"&gt;\r\n    &lt;PropertyColumn Property=\"@(p =&gt; p.PersonId)\" Sortable=\"true\" \/&gt;\r\n    &lt;PropertyColumn Property=\"@(p =&gt; p.Name)\" Sortable=\"true\" \/&gt;\r\n    &lt;PropertyColumn Property=\"@(p =&gt; p.BirthDate)\" Format=\"yyyy-MM-dd\" Sortable=\"true\" \/&gt;\r\n&lt;\/QuickGrid&gt;\r\n\r\n@code {\r\n    record Person(int PersonId, string Name, DateOnly BirthDate);\r\n\r\n    IQueryable&lt;Person&gt; people = new[]\r\n    {\r\n        new Person(10895, \"Jean Martin\", new DateOnly(1985, 3, 16)),\r\n        new Person(10944, \"Ant\u00f3nio Langa\", new DateOnly(1991, 12, 1)),\r\n        new Person(11203, \"Julie Smith\", new DateOnly(1958, 10, 10)),\r\n        new Person(11205, \"Nur Sari\", new DateOnly(1922, 4, 27)),\r\n        new Person(11898, \"Jose Hernandez\", new DateOnly(2011, 5, 3)),\r\n        new Person(12130, \"Kenji Sato\", new DateOnly(2004, 1, 9)),\r\n    }.AsQueryable();\r\n}<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>You can see examples of <code>QuickGrid<\/code> in action on the <a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/\">QuickGrid for Blazor<\/a> demo site, which includes examples of:<\/p>\n<ul>\n<li><a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/datasources\">Working with remote data sources<\/a><\/li>\n<li><a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/datasources\">Adding simple and templated columns<\/a><\/li>\n<li><a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/sorting\">Sorting data<\/a><\/li>\n<li><a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/filtering\">Filtering data<\/a><\/li>\n<li><a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/paging\">Paging data<\/a><\/li>\n<li><a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/virtualizing\">Virtualizing data<\/a><\/li>\n<li><a href=\"https:\/\/aspnet.github.io\/quickgridsamples\/styling\">Styling and theming<\/a><\/li>\n<\/ul>\n<p><code>QuickGrid<\/code> is highly optimized and uses advanced techniques to achieve optimal rendering performance. The QuickGrid demo site is built using Blazor WebAssembly and is hosted on GitHub Pages. The site loads fast thanks to static prerendering using <a href=\"https:\/\/github.com\/jsakamoto\">jsakamoto<\/a>&#8216;s <a href=\"https:\/\/github.com\/jsakamoto\/BlazorWasmPreRendering.Build\">BlazorWasmPrerendering.Build<\/a> project.<\/p>\n<p>It&#8217;s not a goal to add all the features to <code>QuickGrid<\/code> that full-blown commercial grids tend to have, for example hierarchical rows, drag-to-reorder columns, or Excel-like range selections. If you need those, continue using commercial grids or other open-source options.<\/p>\n<p><code>QuickGrid<\/code> is currently experimental, which means it&#8217;s not officially supported and isn&#8217;t committed to ship as part of .NET 7 or any future .NET release at this time. But, <code>QuickGrid<\/code> is open source and freely available to use. We hope you&#8217;ll give it a try and let us know what you think!<\/p>\n<h2>gRPC JSON transcoding multi-segment parameters<\/h2>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-grpc-json-transcoding-for-dotnet\/\">gRPC JSON transcoding<\/a> is a new feature in .NET 7 for turning gRPC APIs into RESTful APIs.<\/p>\n<p>New in Preview 6 is support for multi-segment parameters in gRPC JSON transcoding routes. This feature brings ASP.NET Core transcoding into parity with other transcoding solutions in the gRPC ecosystem.<\/p>\n<p>It&#8217;s now possible to configure gRPC APIs to bind properties to multi-segment parameters. For example, <code>\/v1\/{book=shelves\/*\/books\/*}<\/code> specifies a route with a multi-segment book parameter. It matches URL&#8217;s like <code>\/v1\/shelves\/user-name\/books\/cool-book<\/code> and the book parameter has the value <code>shelves\/user-name\/books\/cool-book<\/code>.<\/p>\n<p>For more information about this routing syntax, see <a href=\"https:\/\/cloud.google.com\/service-infrastructure\/docs\/service-management\/reference\/rpc\/google.api#path-template-syntax\">gRPC transcoding path syntax<\/a>.<\/p>\n<h2><code>MapGroup<\/code> support for more extension methods<\/h2>\n<p>Route groups were <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/asp-net-core-updates-in-dotnet-7-preview-4\/#route-groups\">introduced in .NET 7 Preview 4<\/a> and allow defining groups of endpoints with a common route prefix and a common set of conventions. Conventions include extension methods like <code>RequireAuthorization()<\/code>, <code>RequireCors()<\/code> and their associated attributes. Conventions add metadata to an endpoint that other middleware (authorization, CORS, etc.) typically act on.<\/p>\n<p><code>WithTags()<\/code>, <code>WithDescription()<\/code>, <code>ExcludeFromDescription()<\/code> and <code>WithSummary()<\/code> previously only targeted <code>RouteHandlerBuilder<\/code> instead of the <code>IEndpointConventionBuilder<\/code> interface making them incompatible with the <code>RouteGroupBuilder<\/code> returned by <code>MapGroup()<\/code>. Starting in Preview 6, these extension methods now target <code>IEndpointConventionBuilder<\/code>making them compatible with route groups.<\/p>\n<p>Similarly, <code>WithOpenApi()<\/code> and <code>AddFilter()<\/code> targeted <code>RouteHandlerBuilder<\/code> instead of <code>IEndpointConventionBuilder<\/code> making them incompatible with route groups. Supporting these required rethinking how groups work internally to allow group conventions to both observe metadata about individual endpoints and still modify the metadata and even the final <code>RequestDelegate<\/code> generated for the endpoint. This was made possible through <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/42195\">a change to EndpointDataSource<\/a>, but the key takeaway is that you can now call <code>WithOpenApi()<\/code> and <code>AddFilter()<\/code> on route groups in Preview 6.<\/p>\n<blockquote><p><strong><em>NOTE:<\/em><\/strong> <code>AddFilter()<\/code> has been renamed to <code>AddRouteHandlerFilter()<\/code> in Preview 6 and will be <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/issues\/42589\">renamed again to <code>AddEndpointFilter()<\/code><\/a> starting in Preview 7.<\/p><\/blockquote>\n<pre><code class=\"language-csharp\">var todos = app.MapGroup(\"\/todos\")\r\n    .WithTags(\"todos\", \"some_other_tag\")\r\n    .WithOpenApi()\r\n    .AddRouteHandlerFilter((endpointContext, next) =&gt;\r\n    {\r\n        var logger = endpointContext.ApplicationServices.GetRequiredService&lt;ILogger&lt;Program&gt;&gt;();\r\n        return invocationContext =&gt;\r\n        {\r\n            logger.LogInformation($\"Received request for: {invocationContext.HttpContext.Request.Path}\");\r\n            return next(invocationContext);\r\n        };\r\n    });\r\n\r\ntodos.MapGet(\"\/{id}\", GetTodo);\r\ntodos.MapGet(\"\/\", GetAllTodos).WithTags(\"get_all_todos\");\r\ntodos.MapPost(\"\/\", CreateTodo).WithOpenApi(operation =&gt;\r\n{\r\n    operation.Summary = \"Create a todo.\";\r\n    return operation;\r\n});<\/code><\/pre>\n<h2>Give feedback<\/h2>\n<p>We hope you enjoy this preview release of ASP.NET Core in .NET 7. Let us know what you think about these new improvements by filing issues on <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/issues\/new\">GitHub<\/a>.<\/p>\n<p>Thanks for trying out ASP.NET Core!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>.NET 7 Preview 6 is now available! Check out what&#8217;s new in ASP.NET Core in this update.<\/p>\n","protected":false},"author":417,"featured_media":40866,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,197,7509,7251],"tags":[],"class_list":["post-40865","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-aspnet","category-aspnetcore","category-blazor"],"acf":[],"blog_post_summary":"<p>.NET 7 Preview 6 is now available! Check out what&#8217;s new in ASP.NET Core in this update.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/40865","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/417"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=40865"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/40865\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/40866"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=40865"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=40865"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=40865"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}