{"id":39840,"date":"2022-05-10T12:10:22","date_gmt":"2022-05-10T19:10:22","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=39840"},"modified":"2022-07-13T08:42:00","modified_gmt":"2022-07-13T15:42:00","slug":"asp-net-core-updates-in-dotnet-7-preview-4","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/asp-net-core-updates-in-dotnet-7-preview-4\/","title":{"rendered":"ASP.NET Core updates in .NET 7 Preview 4"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-dotnet-7-preview-4\">.NET 7 Preview 4 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>HTTP\/2 performance improvements<\/li>\n<li>Typed results for minimal APIs<\/li>\n<li>OpenAPI improvements for minimal APIs<\/li>\n<li>Return multiple results types from minimal APIs<\/li>\n<li>Route groups<\/li>\n<li>Client results in SignalR<\/li>\n<li>gRPC JSON transcoding<\/li>\n<li>Project template option to use <code>Program.Main<\/code> method instead of top-level statements<\/li>\n<li>Rate limiting middleware<\/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 4, <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>. Visual Studio for Mac support for .NET 7 previews isn&#8217;t available yet but is coming soon.<\/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>\n<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>\n<\/blockquote>\n<h2>Upgrade an existing project<\/h2>\n<p>To upgrade an existing ASP.NET Core app from .NET 7 Preview 3 to .NET 7 Preview 4:<\/p>\n<ul>\n<li>Update all Microsoft.AspNetCore.* package references to <code>7.0.0-preview.4.*<\/code>.<\/li>\n<li>Update all Microsoft.Extensions.* package references to <code>7.0.0-preview.4.*<\/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>HTTP\/2 performance improvements<\/h2>\n<p>.NET 7 Preview 4 introduces a significant re-architecture of how Kestrel processes HTTP\/2 requests. ASP.NET Core apps with busy HTTP\/2 connections will experience reduced CPU usage and higher throughput.<\/p>\n<p>HTTP\/2 allows up to 100 requests to run on a TCP connection in parallel. This is called multiplexing. It&#8217;s a powerful feature but makes HTTP\/2 complex to implement. Before Preview 4, HTTP\/2 multiplexing in Kestrel relied on C#&#8217;s <code>lock<\/code> keyword to control which request could write to the TCP connection. While <code>lock<\/code> is a simple solution to writing safe multi-threading code, it&#8217;s inefficient under high thread contention. Threads fighting over the lock waste CPU resources that could be used for other work.<\/p>\n<p>Kestrel profiling and benchmarking showed:<\/p>\n<ul>\n<li>High thread contention when a connection is busy.<\/li>\n<li>CPU cycles are wasted by requests fighting over the write lock.<\/li>\n<li>Idle CPU cores as requests wait for the write lock.<\/li>\n<li>Kestrel HTTP\/2 benchmarks are lower than other servers in busy connection scenarios.<\/li>\n<\/ul>\n<p>The solution is to rewrite how HTTP\/2 requests in Kestrel access the TCP connection. A <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/an-introduction-to-system-threading-channels\/\">thread-safe queue<\/a> replaces the write lock. Instead of fighting over who gets to use the write lock, requests now queue up in an orderly line, and a dedicated consumer processes them. Previously wasted CPU resources are available to the rest of the app.<\/p>\n<p>These improvements are visible in gRPC, a popular RPC framework that uses HTTP\/2. Kestrel + gRPC benchmarks show a dramatic improvement:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/05\/http2_performance.png\" alt=\"gRPC Server Streaming graph\" \/><\/p>\n<h2>Typed results for minimal APIs<\/h2>\n<p>In .NET 6 we introduced the <code>IResult<\/code> interface to ASP.NET Core to represent values returned from minimal APIs that don&#8217;t utilize the implicit support for JSON serializing the returned object to the HTTP response. The static <code>Results<\/code> class is used to create varying <code>IResult<\/code> objects that represent different types of responses, from simply setting the response status code, to redirecting to another URL. The <code>IResult<\/code>-implementing framework types returned from these methods were internal however, making it difficult to verify the specific <code>IResult<\/code>-type being returned from your methods in a unit test.<\/p>\n<p>In .NET 7 the types implementing <code>IResult<\/code> in ASP.NET Core have been made public, allowing for simple type assertions when testing. For example:<\/p>\n<p><strong>Program.cs<\/strong><\/p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\nbuilder.Services.AddSqlite&lt;TodoDb&gt;(\"Filename=:memory:\");\r\n\r\nvar app = builder.Build();\r\n\r\napp.MapTodosApi();\r\n\r\napp.Run();\r\n\r\npublic class Todo\r\n{\r\n    public int Id { get; set; }\r\n    public string? Title { get; set; }\r\n    public bool IsComplete { get; set; }\r\n}\r\n\r\npublic static class TodosApi\r\n{\r\n    public static IEndpointRouteBuilder MapTodosApi(this IEndpointRouteBuilder routes)\r\n    {\r\n        routes.MapGet(\"\/todos\", GetAllTodos);\r\n    }\r\n\r\n    public static async Task&lt;IResult&gt; GetAllTodos(TodoDb db)\r\n    {\r\n        return Results.Ok(await db.Todos.ToArrayAsync());\r\n    }\r\n}<\/code><\/pre>\n<p><strong>TodosApiTests.cs<\/strong> <em>(In an xUnit test project)<\/em><\/p>\n<pre><code class=\"language-csharp\">using Microsoft.AspNetCore.Http.HttpResults;\r\nusing Microsoft.Data.Sqlite;\r\nusing Microsoft.EntityFrameworkCore;\r\n\r\nnamespace Tests;\r\n\r\npublic class TodosApiTests : IDisposable\r\n{\r\n    private SqliteConnection _connection;\r\n    private DbContextOptions&lt;TodoDb&gt; _contextOptions;\r\n\r\n    public TodosApiTests()\r\n    {\r\n        _connection = new SqliteConnection(\"Filename=:memory:\");\r\n        _connection.Open();\r\n        _contextOptions = new DbContextOptionsBuilder&lt;TodoDb&gt;()\r\n            .UseSqlite(_connection)\r\n            .Options;\r\n\r\n        using var context = CreateDbContext();\r\n        context.Database.EnsureCreated();\r\n    }\r\n\r\n    public void Dispose() =&gt; _connection.Dispose();\r\n\r\n    private TodoDb CreateDbContext() =&gt; new TodoDb(_contextOptions);\r\n\r\n    [Fact]\r\n    public async Task GetAllTodos_ReturnsOkResultOfIEnumerableTodo()\r\n    {\r\n        \/\/ Arrange\r\n        var db = CreateDbContext();\r\n\r\n        \/\/ Act\r\n        var result = await TodosApi.GetAllTodos(db);\r\n\r\n        \/\/ Assert: Check the returned result type is correct\r\n        Assert.IsType&lt;Ok&lt;object&gt;&gt;(result);\r\n    }\r\n}<\/code><\/pre>\n<p>Let&#8217;s take a closer look at the test method:<\/p>\n<pre><code class=\"language-csharp\">[Fact]\r\npublic async Task GetAllTodos_ReturnsOkOfObjectResult()\r\n{\r\n    \/\/ Arrange\r\n    var db = CreateDbContext();\r\n\r\n    \/\/ Act\r\n    var result = await TodosApi.GetAllTodos(db);\r\n\r\n    \/\/ Assert: Check the returned result type is correct\r\n    Assert.IsType&lt;Ok&lt;object&gt;&gt;(result);\r\n}<\/code><\/pre>\n<p>Note that once the <code>result<\/code> is retrieved from the minimal API method being tested, it&#8217;s a simple case of asserting that the result&#8217;s type is <code>Ok&lt;object&gt;<\/code>. The generic argument of the new <code>Ok&lt;TValue&gt;<\/code> result type is still <code>object<\/code> in this case though. Why is that? Wouldn&#8217;t we want to preserve the actual type of the returned value?<\/p>\n<p>The value type is <code>object<\/code> because the <code>Results.Ok(object? value = null)<\/code> method accepts the value as <code>object<\/code>, not as the original type. It also still returns the result object typed as <code>IResult<\/code>, not as the newly public <code>Ok&lt;TValue&gt;<\/code> type. To solve this issue we&#8217;ve introduced a new factory class for creating &#8220;typed&#8221; results.<\/p>\n<h3>Microsoft.AspNetCore.Http.TypedResults<\/h3>\n<p>The new <code>Microsoft.AspNetCore.Http.TypedResults<\/code> static class is the &#8220;typed&#8221; equivalent of the existing <code>Microsoft.AspNetCore.Http.Results<\/code> class. You can use <code>TypedResults<\/code> in your minimal APIs to create instances of the in-framework <code>IResult<\/code>-implementing types and <strong>preserve<\/strong> the concrete type information.<\/p>\n<p>Let&#8217;s revisit our minimal API from earlier but this time updated to use the <code>TypedResults<\/code> class:<\/p>\n<pre><code class=\"language-csharp\">public static async Task&lt;IResult&gt; GetAllTodos(TodoDb db)\r\n{\r\n    return TypedResults.Ok(await db.Todos.ToArrayAsync());\r\n}<\/code><\/pre>\n<p>We can update our test now to check for the full concrete type detail:<\/p>\n<pre><code class=\"language-csharp\">[Fact]\r\npublic async Task GetAllTodos_ReturnsOkOfObjectResult()\r\n{\r\n    \/\/ Arrange\r\n    var db = CreateDbContext();\r\n\r\n    \/\/ Act\r\n    var result = await TodosApi.GetAllTodos(db);\r\n\r\n    \/\/ Assert: Check the returned result type is correct\r\n    Assert.IsType&lt;Ok&lt;Todo[]&gt;&gt;(result);\r\n}<\/code><\/pre>\n<h2>OpenAPI improvements for minimal APIs<\/h2>\n<h3>Introducing the Microsoft.AspNetCore.OpenApi package<\/h3>\n<p>The <a href=\"https:\/\/swagger.io\/specification\/\">OpenAPI specification<\/a> provides a language-agnostic standard for describing RESTful APIs. In .NET 7 Preview 4, we&#8217;re introducing support for the new <code>Microsoft.AspNetCore.OpenApi<\/code> package to provide APIs for interacting with the OpenAPI specification in minimal APIs. Package references to the new package are included automatically in minimal API-enabled application that are created from a template with <code>--enable-openapi<\/code>. In other cases, the dependency can be added as a package reference.<\/p>\n<p>The package exposes a <code>WithOpenApi<\/code> extension method that generates an <code>OpenApiOperation<\/code> derived from a given endpoint&#8217;s route handler and metadata.<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/todos\/{id}\", (int id) =&gt; ...)\r\n    .WithOpenApi();<\/code><\/pre>\n<p>The <code>WithOpenApi<\/code> extension method above generates an <code>OpenApiOperation<\/code> associated with a <code>GET<\/code> request to the <code>\/todos\/{id}<\/code> endpoint. A second <code>WithOpenApi<\/code> extension method overload can be used to extend and override the generated operation.<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/todos\/{id}\", (int id) =&gt; ...)\r\n    .WithOpenApi(operation =&gt; {\r\n        operation.Summary = \"Retrieve a Todo given its ID\";\r\n        operation.Parameters[0].AllowEmptyValue = false;\r\n        return operation;\r\n    });<\/code><\/pre>\n<h3>Self-describing minimal APIs with <code>IEndpointMetadataProvider<\/code> and <code>IEndpointParameterMetadataProvider<\/code><\/h3>\n<p>One of the features of minimal APIs is the ability for the framework to use the type information declared as part of your application&#8217;s route handlers (methods, lambdas, delegates, etc.), in addition to the implicit behavior of the framework, to automatically document the details of the APIs for OpenAPI\/Swagger.<\/p>\n<p>In cases where those details are not enough to describe your API, you can add endpoint metadata in your code to enhance the description:<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/todos\", async (TodoDb db)\r\n{\r\n    return Results.Ok(await db.Todos.ToArrayAsync());\r\n})\r\n    \/\/ Add metadata about the shape of the data returned for OpenAPI\/Swagger\r\n    .Produces&lt;Todo[]&gt;();<\/code><\/pre>\n<p>Wouldn&#8217;t it be nice if we could preserve the rich type information and use it to more fully describe our APIs automatically, without additional annotations? For example, if my route handler declares that it returns a type that represents an HTTP 200 OK response with a particularly shaped JSON payload, we should be able to use that type information to automatically describe the API.<\/p>\n<p>In .NET 7, we&#8217;ve introduced two new interfaces to ASP.NET Core that allow types used in minimal API route handlers to contribute to endpoint metadata: <code>IEndpointMetadataProvider<\/code> and <code>IEndpointParameterMetadataProvider<\/code>. These interfaces take advantage of a feature, newly out of preview in C# 11, <a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/whats-new\/tutorials\/static-abstract-interface-methods\"><code>static abstract<\/code> interface members<\/a>, to declare static methods that, if present on route handler return types or parameters, will be called by the framework when the endpoint is built.<\/p>\n<p>The interfaces look like this:<\/p>\n<pre><code class=\"language-csharp\">namespace Microsoft.AspNetCore.Http.Metadata;\r\n\r\npublic interface IEndpointMetadataProvider\r\n{\r\n    static abstract void PopulateMetadata(EndpointMetadataContext context);\r\n}\r\n\r\npublic interface IEndpointParameterMetadataProvider\r\n{\r\n    static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);\r\n}<\/code><\/pre>\n<p><code>IEndpointMetadataProvider<\/code> can be implemented by types either returned from a route handler, or accepted by a route handler as a parameter. <code>IEndpointParameterMetadataProvider<\/code>, as the name suggests, can only be implemented by types accepted by a route handler as a parameter and will be provided with the <code>ParameterInfo<\/code> for the associated route handler parameter when called.<\/p>\n<p>Updating the example above to use the new <code>TypedResults<\/code> class is all that&#8217;s needed to have it be able to describe itself to OpenAPI\/Swagger, as many of the in-framework <code>IResult<\/code>-implementing types also implement <code>IEndpointMetadataProvider<\/code>:<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/todos\", async (TodoDb db)\r\n{\r\n    \/\/ This lambda now returns Ok&lt;Todo[]&gt;, a type that implements IEndpointMetadataProvider.\r\n    \/\/ The framework will call Ok&lt;Todo[]&gt;.PopulateMetadata() when the endpoint is built,\r\n    \/\/ which adds the necessary endpoint metadata to describe the HTTP response type.\r\n    return TypedResults.Ok(await db.Todos.ToArrayAsync());\r\n});<\/code><\/pre>\n<p>This pattern works well for simple route handlers that return only one type of result, but what happens once we have an API that can return different result types depending on the conditions, like a <strong>200 OK<\/strong> when the todo is found and a <strong>404 Not Found<\/strong> when it isn&#8217;t? You can, of course, continue to return <code>IResult<\/code> from a route handler that returns different actual types (as long as they all implement <code>IResult<\/code>) by explicitly stating the handler&#8217;s return type, like this:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Declare that the lambda returns IResult so that multiple concrete types can be returned\r\napp.MapGet(\"\/todos\/{id}\", async IResult (int id, TodoDb db)\r\n{\r\n    return await db.Todos.FindAsync(id) is Todo todo\r\n        ? TypedResults.Ok(todo)\r\n        : TypedResults.NotFound();\r\n});<\/code><\/pre>\n<p>But now the concrete type information is no longer preserved on the handler signature, and thus the framework cannot use it to automatically describe the API. C# does not yet support declaring multiple return types from a single method, so how do we preserve the full type information? We&#8217;ll cover that next.<\/p>\n<h2>Return multiple result types from minimal APIs<\/h2>\n<p>The new <code>Results&lt;TResult1, TResult2, TResultN&gt;<\/code> generic union types, along with the <code>TypesResults<\/code> class, can be used to declare that a route handler returns multiple <code>IResult<\/code>-implementing concrete types, and any of those types implementing <code>IEndpointMetadataProvider<\/code> will contribute to the endpoint&#8217;s metadata, enabling the framework to automatically describe the various HTTP results for an API in OpenAPI\/Swagger:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Declare that the lambda returns multiple IResult types\r\napp.MapGet(\"\/todos\/{id}\", async Results&lt;Ok&lt;Todo&gt;, NotFound&gt; (int id, TodoDb db)\r\n{\r\n    return await db.Todos.FindAsync(id) is Todo todo\r\n        ? TypedResults.Ok(todo)\r\n        : TypedResults.NotFound();\r\n});<\/code><\/pre>\n<p>The <code>Results&lt;TResult1, TResultN&gt;<\/code> union types implement implicit cast operators so that the compiler can automatically convert the types specified in the generic arguments to an instance of the union type. This has the added benefit of providing compile-time checking that a route handler actually only returns the results that it declares it does. Attempting to return a type that isn&#8217;t declared as one of the generic arguments to <code>Results&lt;&gt;<\/code> will result in a compilation error.<\/p>\n<p>Below is an image of the Swagger UI for the API in the example above. You can see that the parameter and responses information has been automatically discovered from the route handler signature:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/05\/gettodos_swaggerui.png\" alt=\"Automatically documented minimal API in SwaggerUI\" \/><\/p>\n<h3>A self-documenting Todos API<\/h3>\n<p>Let&#8217;s revisit our todos API from earlier. It&#8217;s now updated to include methods for retrieving a single todo, creating a new todo, and editing an existing todo. It&#8217;s also now using the new <code>Results&lt;TResult1, TResultsN&gt;<\/code> and <code>TypedResults<\/code> types to preserve the full type information in the route handler signatures, making them easier to verify in tests, and be automatically described in OpenAPI\/Swagger:<\/p>\n<pre><code class=\"language-csharp\">using Microsoft.AspNetCore.Http.HttpResults;\r\nusing Microsoft.EntityFrameworkCore;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\nbuilder.Services.AddSqlite&lt;TodoDb&gt;(\"Filename=:memory:\");\r\n\r\nbuilder.Services.AddEndpointsApiExplorer();\r\nbuilder.Services.AddSwaggerGen();\r\n\r\nvar app = builder.Build();\r\n\r\napp.UseSwagger();\r\napp.UseSwaggerUI();\r\napp.MapTodosApi();\r\n\r\napp.Run();\r\n\r\npublic class Todo\r\n{\r\n    public int Id { get; set; }\r\n    public string? Title { get; set; }\r\n    public bool IsComplete { get; set; }\r\n}\r\n\r\npublic static class TodosApi\r\n{\r\n    public static IEndpointRouteBuilder MapTodosApi(this IEndpointRouteBuilder routes)\r\n    {\r\n        routes.MapGet(\"\/todos\", GetAllTodos);\r\n        routes.MapGet(\"\/todos\/{id}\", GetTodo);\r\n        routes.MapPost(\"\/todos\", CreateTodo);\r\n        routes.MapPut(\"\/todos\/{id}\", UpdateTodo);\r\n        return routes;\r\n    }\r\n\r\n    public static async Task&lt;Ok&lt;Todo[]&gt;&gt; GetAllTodos(TodoDb db)\r\n    {\r\n        return TypedResults.Ok(await db.Todos.ToArrayAsync());\r\n    }\r\n\r\n    public static async Task&lt;Results&lt;Ok&lt;Todo&gt;, NotFound&gt;&gt; GetTodo(int id, TodoDb db)\r\n    {\r\n        return await db.Todos.FindAsync(id) is Todo todo\r\n            ? TypedResults.Ok(todo)\r\n            : TypedResults.NotFound();\r\n    }\r\n\r\n    public static async Task&lt;Created&lt;Todo&gt;&gt; CreateTodo(Todo todo, TodoDb db)\r\n    {\r\n        db.Todos.Add(todo);\r\n        await db.SaveChangesAsync();\r\n\r\n        return TypedResults.Created($\"\/todos\/{todo.Id}\", todo);\r\n    }\r\n\r\n    public static async Task&lt;Results&lt;NoContent, NotFound&gt;&gt; UpdateTodo(int id, Todo todo, TodoDb db)\r\n    {\r\n        var existingTodo = await db.Todos.FindAsync(id);\r\n\r\n        if (existingTodo is null)\r\n        {\r\n            return TypedResults.NotFound();\r\n        }\r\n\r\n        existingTodo.Title = todo.Title;\r\n        existingTodo.IsComplete = todo.IsComplete;\r\n\r\n        await db.SaveChangesAsync();\r\n\r\n        return TypedResults.NoContent();\r\n    }\r\n}<\/code><\/pre>\n<h2>Route groups<\/h2>\n<p>.NET 7 Preview 4 introduces the <code>MapGroup()<\/code> extension method, which helps organize groups of <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing#endpoints\">endpoints<\/a> with a common prefix. It allows for customizing entire groups of endpoints with a singe call to methods like <code>RequireAuthorization()<\/code> and <code>WithMetadata()<\/code>.<\/p>\n<p>We can use <code>MapGroup()<\/code> to add private todo endpoints to our earlier API as follows:<\/p>\n<pre><code class=\"language-csharp\">\/\/ ...\r\n\/\/ Was: app.MapTodosApi()\r\napp.MapGroup(\"\/public\/todos\").MapTodosApi();\r\n\/\/ Auth configuration is left as an exercise for the reader. More to come in future previews.\r\napp.MapGroup(\"\/private\/todos\").MapTodosApi().RequireAuthorization();\r\n\r\napp.Run();\r\n\/\/ ...\r\npublic static class TodosApi\r\n{\r\n    \/\/ GroupRouteBuilder is both an IEndpointRouteBuilder and IEndpointConventionBuilder.\r\n    public static GroupRouteBuilder MapTodosApi(this GroupRouteBuilder group)\r\n    {\r\n        group.MapGet(\"\/\", GetAllTodos);\r\n        group.MapGet(\"\/{id}\", GetTodo);\r\n        group.MapPost(\"\/\", CreateTodo);\r\n        group.MapPut(\"\/{id}\", UpdateTodo);\r\n        return group;\r\n    }\r\n\/\/ ...\r\n    public static async Task&lt;Created&lt;Todo&gt;&gt; CreateTodo(Todo todo, TodoDb db, ClaimsPrincipal user)\r\n    {\r\n        if (user.Identity?.IsAuthenticated ?? false)\r\n        {\r\n            db.PrivateTodos.Add(todo);\r\n        }\r\n        else\r\n        {\r\n            db.Todos.Add(todo);\r\n        }\r\n\r\n        await db.SaveChangesAsync();\r\n        \/\/ Use relative path for Location header to support multiple group prefixes.\r\n        \/\/ Was: TypedResults.Created($\"\/todos\/{todo.Id}\", todo)\r\n        return TypedResults.Created($\"{todo.Id}\", todo);\r\n    }\r\n\/\/ ...<\/code><\/pre>\n<p>Instead of using relative addresses for the Location header in the 201 Created result, it&#8217;s also possible to use <code>GroupRouteBuilder.GroupPrefix<\/code> to construct a root-relative address or use <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.routing.linkgeneratorroutevaluesaddressextensions\">GetPathByRouteValues or GetUriByRouteValues<\/a> with a <code>routeName<\/code> for even more options.<\/p>\n<p>Route groups also support nested groups and complex prefix patterns with route parameters and constraints.<\/p>\n<p>Route groups are still a work in progress. We plan to add support for more extension methods such as <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.http.openapiroutehandlerbuilderextensions.withtags\">WithTags<\/a> and the recently-added <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/asp-net-core-updates-in-dotnet-7-preview-3\/#support-for-route-handler-filters-in-minimal-apis\">AddFilter<\/a> method. We&#8217;re also considering adding support for adding middleware to a route group in addition to endpoints. Take a look at the <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/issues\/41433\">&#8220;epic&#8221; issue for route groups<\/a> to follow progress and provide feedback.<\/p>\n<h2>Client results in SignalR<\/h2>\n<p>Previously, when using SignalR, the server could invoke a method on a client but didn&#8217;t have the ability to wait for a response. This scenario is now supported with .NET 7 Preview 4. The server uses <code>ISingleClientProxy.InvokeAsync()<\/code> to invoke a client method, and the client returns a result from its <code>.On()<\/code> handler.<\/p>\n<p>There are two ways to use the API on the server. The first is to call <code>Single()<\/code> on the <code>Clients<\/code> property in a hub method:<\/p>\n<pre><code class=\"language-csharp\">public class GameHub : Hub\r\n{\r\n    public async Task WaitForResult(string connectionId)\r\n    {\r\n        var randomValue = Random.Shared.Next(0, 10);\r\n        var result = await Clients.Single(connectionId).InvokeAsync&lt;int&gt;(\r\n            \"GetResult\", \"Guess the value between 0 and 10.\");\r\n        if (result == randomValue)\r\n        {\r\n            await Clients.Client(connectionId).SendAsync(\"EndResult\", \"You guessed correctly!\");\r\n        }\r\n        else\r\n        {\r\n            await Clients.Client(connectionId).SendAsync(\"EndResult\", $\"You guessed incorrectly, value was {randomValue}\");\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>Using <code>InvokeAsync<\/code> from a hub method requires setting the <code>MaximumParallelInvocationsPerClient<\/code> option to a value greater than 1.<\/p>\n<p>The second way is to call <code>Single()<\/code> on an instance of <code>IHubContext&lt;T&gt;<\/code>:<\/p>\n<pre><code class=\"language-csharp\">async Task SomeMethod(IHubContext&lt;MyHub&gt; context)\r\n{\r\n    var randomValue = Random.Shared.Next(0, 10);\r\n    var result = await context.Clients.Single(connectionID).InvokeAsync&lt;int&gt;(\r\n        \"GetResult\", \"Guess the value between 0 and 10.\");\r\n    if (result == randomValue)\r\n    {\r\n        await context.Clients.Client(connectionId).SendAsync(\"EndResult\", \"You guessed correctly!\");\r\n    }\r\n    else\r\n    {\r\n        await context.Client(connectionId).SendAsync(\"EndResult\", $\"You guessed incorrectly, value was {randomValue}\");\r\n    }\r\n}<\/code><\/pre>\n<p>Strongly-typed hubs can also return values from interface methods:<\/p>\n<pre><code class=\"language-csharp\">public interface IClient\r\n{\r\n    Task&lt;int&gt; GetResult();\r\n}\r\n\r\npublic class GameHub : Hub&lt;IClient&gt;\r\n{\r\n    public async Task WaitForMessage(string connectionId)\r\n    {\r\n        int message = await Clients.Single(connectionId).GetResult();\r\n    }\r\n}<\/code><\/pre>\n<p>Clients return results in their <code>.On()<\/code> handlers, as shown below:<\/p>\n<p><strong>.NET client<\/strong><\/p>\n<pre><code class=\"language-csharp\">hubConnection.On(\"GetResult\", async (string message) =&gt;\r\n{\r\n    Console.WriteLine($\"{message} Enter guess:\");\r\n    var result = await Console.In.ReadLineAsync();\r\n    return result;\r\n});<\/code><\/pre>\n<p><strong>TypeScript client<\/strong><\/p>\n<pre><code class=\"language-typescript\">hubConnection.on(\"GetResult\", async (message) =&gt; {\r\n    console.log(message);\r\n    let promise = new Promise((resolve, reject) =&gt; {\r\n        setTimeout(() =&gt; {\r\n            resolve(2);\r\n        }, 100);\r\n    });\r\n    return promise;\r\n});<\/code><\/pre>\n<p>SignalR client results don&#8217;t work with the Azure SignalR Service yet.<\/p>\n<p>You can refer to https:\/\/github.com\/dotnet\/aspnetcore\/issues\/5280 for more details and planned future improvements.<\/p>\n<h2>gRPC JSON transcoding<\/h2>\n<p>The first preview of gRPC JSON transcoding is now available with .NET 7 Preview 4. gRPC JSON transcoding allows gRPC services to be called as RESTful APIs. This enables apps to support gRPC and REST without duplication.<\/p>\n<p>We&#8217;ll share more information about JSON transcoding in an upcoming blog post. For more information right now, check out the <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/grpc\/httpapi\">documentation<\/a> or try out <a href=\"https:\/\/github.com\/grpc\/grpc-dotnet\/tree\/master\/examples#transcoder\">a sample app that uses JSON transcoding<\/a>.<\/p>\n<h2>Project template option to use <code>Program.Main<\/code> method instead of top-level statements<\/h2>\n<p>In .NET 6, we updated the ASP.NET Core project templates to use modern C# features from C# 8 through to C# 10. One of these features was <a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/fundamentals\/program-structure\/top-level-statements\">top-level statements<\/a>, a feature from C# 9, which removes the need to explicitly define an application entry-point, typically in the form of a <code>Main<\/code> method declared on a <code>Program<\/code> class (<code>Program.Main<\/code>).<\/p>\n<p>While many people appreciate the reduced boilerplate code this feature results in, others expressed frustration that this change was made in the project templates without an option to continue using the traditional <code>Program.Main<\/code> structure instead.<\/p>\n<p>In this preview, we&#8217;ve added a template option that allows the creation of new projects without using top-level statements. If using the .NET CLI, you can specify the <code>--use-program-main<\/code> option like so:<\/p>\n<pre><code class=\"language-console\">&gt; dotnet new web --use-program-main<\/code><\/pre>\n<p>If creating projects with Visual Studio, you can select the new &#8220;Do not use top-level statements&#8221; checkbox during project creation:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/05\/VS-template-no-top-level-statements.png\" alt=\"Create new projects without using top-level statements\" \/><\/p>\n<h2>Rate limiting middleware<\/h2>\n<p>The new rate limiting middleware in .NET 7 Preview 4 provides a convenient way to limit the rate of incoming HTTP requests.<\/p>\n<p>The following example applies a <code>ConcurrencyLimiter<\/code> globally with a maximum of 1 concurrent lease:<\/p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\r\nvar app = builder.Build();\r\n\r\napp.UseRateLimiter(new RateLimiterOptions\r\n{\r\n    Limiter = PartitionedRateLimiter.Create&lt;HttpContext, string&gt;(resource =&gt;\r\n    {\r\n        return RateLimitPartition.CreateConcurrencyLimiter(\"MyLimiter\",\r\n            _ =&gt; new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1));\r\n    })\r\n});\r\n\r\napp.Run();<\/code><\/pre>\n<p>The <code>string<\/code> passed as the first argument to <code>CreateConcurrencyLimiter<\/code> is a key used to distinguish different component limiters in the <code>PartitionedRateLimiter<\/code>.<\/p>\n<p>You can also configure the limiting behavior based on attributes of the resource passed in:<\/p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\r\nvar app = builder.Build();\r\n\r\napp.UseRateLimiter(new RateLimiterOptions\r\n{\r\n    Limiter = PartitionedRateLimiter.Create&lt;HttpContext, string&gt;(resource =&gt;\r\n    {\r\n        if (resource.Request.Path.StartsWithSegment(\"\/api\")\r\n        {\r\n            return RateLimitPartition.CreateConcurrencyLimiter(\"WebApiLimiter\",\r\n                _ =&gt; new ConcurrencyLimiterOptions(2, QueueProcessingOrder.NewestFirst, 2));\r\n        }\r\n        else\r\n        {\r\n            return RateLimitPartition.CreateConcurrencyLimiter(\"DefaultLimiter\",\r\n                _ =&gt; new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1));\r\n        }\r\n    })\r\n});\r\n\r\napp.Run();<\/code><\/pre>\n<p>This example uses a <code>ConcurrencyLimiter<\/code> with a limit of 2 concurrent leases and a queue depth of 2 whenever the request path points to a web API and will use a <code>ConcurrencyLimiter<\/code> with a limit of 1 concurrent lease and a queue depth of 1 in all other cases.<\/p>\n<p><code>RateLimitPartition<\/code> today contains convenience methods for creating <code>ConcurrencyLimiter<\/code>s, <code>TokenBucketRateLimiter<\/code>s, and <code>NoopLimiter<\/code>s which can easily be swapped in to the examples above.<\/p>\n<p>The implementation of the rate limiting middleware in .NET 7 Preview 4 is minimal and not endpoint aware. In a future preview we plan to make the middleware endpoint aware by default and add additional configuration options. A user could today use the APIs in <code>System.Threading.RateLimiting<\/code> to create a <code>PartitionedRateLimiter<\/code> that applies different rate limits to different endpoints.<\/p>\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 4 is now available! Check out what&#8217;s new in ASP.NET Core in this update.<\/p>\n","protected":false},"author":417,"featured_media":39841,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,197,7509,7251],"tags":[7611],"class_list":["post-39840","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-aspnet","category-aspnetcore","category-blazor","tag-dotnet-7"],"acf":[],"blog_post_summary":"<p>.NET 7 Preview 4 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\/39840","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=39840"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/39840\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/39841"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=39840"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=39840"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=39840"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}