{"id":40473,"date":"2022-06-14T07:33:17","date_gmt":"2022-06-14T14:33:17","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=40473"},"modified":"2022-07-05T08:46:43","modified_gmt":"2022-07-05T15:46:43","slug":"asp-net-core-updates-in-dotnet-7-preview-5","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/asp-net-core-updates-in-dotnet-7-preview-5\/","title":{"rendered":"ASP.NET Core updates in .NET 7 Preview 5"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-dotnet-7-preview-5\">.NET 7 Preview 5 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>JWT authentication improvements &amp; automatic authentication configuration<\/li>\n<li>Minimal API&#8217;s parameter binding support for argument list simplification<\/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 5, <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-sh\">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 4 to .NET 7 Preview 5:<\/p>\n<ul>\n<li>Update all Microsoft.AspNetCore.* package references to <code>7.0.0-preview.5.*<\/code>.<\/li>\n<li>Update all Microsoft.Extensions.* package references to <code>7.0.0-preview.5.*<\/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>JWT authentication improvements &amp; automatic authentication configuration<\/h2>\n<p>Configuring authentication (AuthN) and authorization (AuthZ) for an ASP.NET Core app today requires numerous changes, including adding and configuring services, and adding middleware at different stages of the app startup process. We&#8217;ve received feedback that users find configuring authentication and authorization one of the hardest things about building APIs with ASP.NET Core. Given how critically important correctly configuring authentication and authorization are to securing web apps, we&#8217;ve made some improvements aimed at simplifying the most common aspects of this area for ASP.NET Core, with an initial focus on JWT bearer authentication, which is commonly used to protect web APIs.<\/p>\n<h3>Simplified authentication configuration<\/h3>\n<p>Authentication options can now be automatically configured directly from the app&#8217;s configuration system, due to the addition of a default configuration section when configuring authentication via the new <code>Authentication<\/code> property on <code>WebApplicationBuilder<\/code> like so:<\/p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\r\n\r\nbuilder.Authentication.AddJwtBearer(); \/\/ New top-level property for setting up authentication\r\n\r\nvar app = builder.Build();<\/code><\/pre>\n<p>This new property provides a central place in your app&#8217;s code to setup authentication, providing easy access to the <code>AuthenticationBuilder<\/code> instance from which authentication schemes can be added and configured. Setting up authentication via this new property will also take care of automatically adding the required middleware to the request pipeline, similar to how <code>WebApplicationBuilder<\/code> already does this for routing.<\/p>\n<p>Here&#8217;s an example of an app setup to use JWT bearer authentication with two endpoints, one that requires authorization and one that doesn&#8217;t (requires the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package):<\/p>\n<pre><code class=\"language-csharp\">using System.Security.Claims;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\nbuilder.Authentication.AddJwtBearer();\r\n\r\nvar app = builder.Build();\r\n\r\napp.MapGet(\"\/\", () =&gt; \"Hello, World!\");\r\napp.MapGet(\"\/secret\", (ClaimsPrincipal user) =&gt; $\"Hello {user.Identity?.Name}. This is a secret!\")\r\n    .RequireAuthorization();\r\n\r\napp.Run();<\/code><\/pre>\n<p>Furthermore, individual authentication schemes can have their options be automatically set from the app&#8217;s configuration, making it easier to configure them between different environments (e.g. local development vs. production). For this release, only the JWT bearer scheme has been updated to support this mechanism but we&#8217;ll update more authentication schemes to support this in the future.<\/p>\n<p>Here&#8217;s an example of an app&#8217;s <code>appsettings.Development.json<\/code> file, updated to set authentication options via the new <code>\"Authentication\"<\/code> section:<\/p>\n<pre><code class=\"language-json\">{\r\n  \"Logging\": {\r\n    \"LogLevel\": {\r\n      \"Default\": \"Information\",\r\n      \"Microsoft.AspNetCore\": \"Warning\"\r\n    }\r\n  },\r\n  \"AllowedHosts\": \"*\",\r\n  \"Authentication\": {\r\n    \"DefaultScheme\" : \"JwtBearer\",\r\n    \"Schemes\": {\r\n      \"JwtBearer\": {\r\n        \"Audiences\": [ \"http:\/\/localhost:5000\", \"https:\/\/localhost:5001\" ],\r\n        \"ClaimsIssuer\": \"dotnet-user-jwts\"\r\n      }\r\n    }\r\n  }\r\n}<\/code><\/pre>\n<p>Note that authentication schemes must still be added by code for them to have their options set via the new configuration section.<\/p>\n<h3>Endpoint-specific authorization policies<\/h3>\n<p>The previous example included an endpoint definition that only allowed authenticated users to access it (<code>RequireAuthorization()<\/code>). But what if the endpoint&#8217;s authorization requirements are slightly more complex, e.g. only allowing users with a specific &#8220;scope&#8221; claim? A set of authorization requirements is defined in a &#8220;policy&#8221; which is normally defined globally as part of setting up authorization in the app&#8217;s services and then referred to by its name when configuring the endpoint. This is good for reuse but can be difficult to discover and overly complex for some scenarios.<\/p>\n<p>For cases where an authorization policy doesn&#8217;t need to be shared between endpoints, you can now easily define an authorization policy directly on an endpoint via metadata like so:<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/special-secret\", () =&gt; \"This is a special secret!\")\r\n    .RequireAuthorization(p =&gt; p.RequireClaim(\"scope\", \"myapi:secrets\"));<\/code><\/pre>\n<p>Now that we have endpoints protected by JWT authentication, it would be nice to easily verify that they&#8217;re configured correctly in a local development environment, without the need for a full identity and user management service. For that, we&#8217;ll need something to issue JWTs to use with our app locally, which is the job of the new <code>dotnet user-jwts<\/code> command line tool.<\/p>\n<h3>Managing development-time JWTs with <code>dotnet user-jwts<\/code><\/h3>\n<p>If we try to access the protected endpoints from our previous examples using a tool like <a href=\"https:\/\/www.postman.com\/\">Postman<\/a>, <a href=\"https:\/\/curl.se\/\">curl<\/a>, or <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/web-api\/http-repl\/?view=aspnetcore-6.0&amp;tabs=windows\"><code>dotnet httprepl<\/code><\/a>, we&#8217;ll receive an error in the form of a response with an HTTP 401 (unauthorized) status code, indicating that the request didn&#8217;t provide any authentication details and thus is unauthorized to access that resource:<\/p>\n<pre><code class=\"language-sh\">MyWebApi$ curl -i http:\/\/localhost:5000\r\nHTTP\/1.1 200 OK\r\nContent-Type: text\/plain; charset=utf-8\r\nDate: Tue, 07 Jun 2022 23:39:10 GMT\r\nServer: Kestrel\r\nTransfer-Encoding: chunked\r\n\r\nHello, World!\r\nMyWebApi$ curl -i http:\/\/localhost:5000\/secret\r\nHTTP\/1.1 401 Unauthorized\r\nContent-Length: 0\r\nDate: Tue, 07 Jun 2022 23:38:07 GMT\r\nServer: Kestrel\r\nWWW-Authenticate: Bearer\r\nMyWebApi$<\/code><\/pre>\n<p>As the app is configured to use JWT bearer authentication, we need to provide a JWT with the request. In fully deployed systems, the JWT would typically be provided by a server acting as a Security token service (STS), perhaps in response to logging in via a set of credentials. But for the purpose of working with our API during local development, we can use the new <code>dotnet user-jwts<\/code> command line tool to create and manage app-specific local JWTs.<\/p>\n<p>The <code>user-jwts<\/code> tool is similar in concept to the existing <code>user-secrets<\/code> tools, in that it can be used to manage values for the app that are only valid for the current user (the developer) on the current machine. In fact, the <code>user-jwts<\/code> tool utilizes the <code>user-secrets<\/code> infrastructure to manage the key that the JWTs will be signed with, ensuring it&#8217;s stored safely in the user profile.<\/p>\n<p>First, we have to initialize the user secrets system for our project by calling <code>dotnet user-secrets init<\/code> and <code>dotnet user-secrets list<\/code> (note this will be done for you automatically by <code>dotnet user-jwts<\/code> in a future preview release):<\/p>\n<pre><code class=\"language-sh\">MyWebApi$ dotnet user-jwts create\r\nProject does not contain a user secrets ID.\r\nMyWebApi$ dotnet user-secrets init\r\nSet UserSecretsId to 'b78e7e01-e648-421a-9ccd-e1a31dd58529' for MSBuild project 'MyWebApi.csproj'. \r\nMyWebApi$ dotnet user-secrets list\r\nNo secrets configured for this application.<\/code><\/pre>\n<p>Now we can use the <code>user-jwts<\/code> tool to create a JWT to use with our example app:<\/p>\n<pre><code class=\"language-sh\">MyWebApi$ dotnet user-jwts create\r\nNew JWT saved with ID '643a8abc'.\r\nMyWebApi$ dotnet user-jwts print 643a8abc --show-full\r\nFound JWT with ID 'b0498b94'\r\n{\r\n  \"Id\": \"b0498b94\",\r\n  \"Scheme\": \"Bearer\",\r\n  \"Name\": \"damia\",\r\n  \"Audience\": \"https:\/\/localhost:7188\",\r\n  \"NotBefore\": \"2022-06-08T00:03:31+00:00\",\r\n  \"Expires\": \"2022-09-08T00:03:31+00:00\",\r\n  \"Issued\": \"2022-06-08T00:03:31+00:00\",\r\n  \"Token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM\",\r\n  \"Scopes\": [],\r\n  \"Roles\": [],\r\n  \"CustomClaims\": {}\r\n}\r\nToken Header: {\"alg\":\"HS256\",\"typ\":\"JWT\"}\r\nToken Payload: {\"unique_name\":\"damia\",\"sub\":\"damia\",\"jti\":\"b0498b94\",\"aud\":[\"https:\/\/localhost:7188\",\"http:\/\/localhost:5056\"],\"nbf\":1654646611,\"exp\":1662595411,\"iat\":1654646611,\"iss\":\"dotnet-user-jwts\"}\r\nCompact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM\r\nMyWebApi$ <\/code><\/pre>\n<p>The <code>create<\/code> command took care of updating our project&#8217;s <code>appsettings.Development.json<\/code> file and user secrets store with the required configuration values for the JWT bearer authentication scheme to recognize the user JWTs. The <code>print<\/code> command printed out the JWT value we can include in our requests&#8217; <code>Authorization<\/code> header to test out our protected APIs (note the <code>--show-full<\/code> option is required right now to retrieve the JWT value but in a future release this will be shown by default when using <code>user-jwt create<\/code> and <code>user-jwt print<\/code>).<\/p>\n<p>Now let&#8217;s try accessing our protected API again, this time with the JWT value created by the tool:<\/p>\n<pre><code class=\"language-sh\">MyWebApi$ curl -i -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM\" http:\/\/localhost:5000\/secret\r\nHello damian. This is a secret!\r\nMyWebApi$ <\/code><\/pre>\n<p>You can create JWTs with different claims to explore and verify your app&#8217;s authorization configuration. Let&#8217;s create and use a JWT with a custom user name:<\/p>\n<pre><code class=\"language-sh\">MyWebApi$ dotnet user-jwts create --name MyTestUser\r\nNew JWT saved with ID '5d285409'.\r\nMyWebApi$ dotnet user-jwts print 5d285409 --show-full\r\nFound JWT with ID '5d285409'\r\n{\r\n  \"Id\": \"5d285409\",\r\n  \"Scheme\": \"Bearer\",\r\n  \"Name\": \"MyTestUser\",\r\n  \"Audience\": \"https:\/\/localhost:7188\",\r\n  \"NotBefore\": \"2022-06-08T00:53:57+00:00\",\r\n  \"Expires\": \"2022-09-08T00:53:57+00:00\",\r\n  \"Issued\": \"2022-06-08T00:53:57+00:00\",\r\n  \"Token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc\",\r\n  \"Scopes\": [],\r\n  \"Roles\": [],\r\n  \"CustomClaims\": {}\r\n}\r\nToken Header: {\"alg\":\"HS256\",\"typ\":\"JWT\"}\r\nToken Payload: {\"unique_name\":\"MyTestUser\",\"sub\":\"MyTestUser\",\"jti\":\"5d285409\",\"aud\":[\"https:\/\/localhost:7188\",\"http:\/\/localhost:5056\"],\"nbf\":1654649637,\"exp\":1662598437,\"iat\":1654649637,\"iss\":\"dotnet-user-jwts\"}\r\nCompact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc\r\nMyWebApi$ curl -i -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc\" http:\/\/localhost:5000\/secret\r\nHello MyTestUser. This is a secret!\r\nMyWebApi$ <\/code><\/pre>\n<p>Finally, let&#8217;s create a JWT with a custom claim allowing access to the most secret API in our example app:<\/p>\n<pre><code class=\"language-sh\">MyWebApi$ dotnet user-jwts create --name AnotherUser --scope \"myapi:secrets\"\r\nJWT for user 'AnotherUser' created with id 'e9b480cb'.\r\nMyWebApi$ dotnet user-jwts print e9b480cb\r\nFound JWT with ID 'e9b480cb'\r\n{\r\n  \"Id\": \"e9b480cb\",\r\n  \"Scheme\": \"Bearer\",\r\n  \"Name\": \"AnotherUser\",\r\n  \"Audience\": \"https:\/\/localhost:7188\",\r\n  \"NotBefore\": \"2022-06-08T00:57:43+00:00\",\r\n  \"Expires\": \"2022-09-08T00:57:43+00:00\",\r\n  \"Issued\": \"2022-06-08T00:57:43+00:00\",\r\n  \"Token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE\",\r\n  \"Scopes\": [\r\n    \"myapi:secrets\"\r\n  ],\r\n  \"Roles\": [],\r\n  \"CustomClaims\": {}\r\n}\r\nToken Header: {\"alg\":\"HS256\",\"typ\":\"JWT\"}\r\nToken Payload: {\"unique_name\":\"AnotherUser\",\"sub\":\"AnotherUser\",\"jti\":\"e9b480cb\",\"scope\":\"myapi:secrets\",\"aud\":[\"https:\/\/localhost:7188\",\"http:\/\/localhost:5056\"],\"nbf\":1654649863,\"exp\":1662598663,\"iat\":1654649863,\"iss\":\"dotnet-user-jwts\"}\r\nCompact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE\r\nMyWebApi$ curl -i -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE\" http:\/\/localhost:5000\/special-secret\r\nThis is a special secret!\r\nMyWebApi$ <\/code><\/pre>\n<p>Using the new <code>user-jwts<\/code> tool we&#8217;ve been able to verify the APIs in our example app are configured for authorization in the way we intended. The app could of course now be configured to support an actual JWT provider according to that provider&#8217;s requirements.<\/p>\n<p>You can explore the commands and options available on <code>user-jwts<\/code> with the <code>--help<\/code> option, e.g.:<\/p>\n<ul>\n<li><code>dotnet user-jwts --help<\/code><\/li>\n<li><code>dotnet user-jwts create --help<\/code><\/li>\n<\/ul>\n<h2>Minimal API parameter binding for argument lists<\/h2>\n<p>This preview extends parameter binding for minimal APIs to support refactoring a minimal API that takes a set of parameters into one that takes a single object with top level properties that represent what were once arguments.<\/p>\n<p>For example, the following API lists all the products in a given category:<\/p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\n\/\/ Requires the Microsoft.EntityFrameworkCore.InMemory package\r\nbuilder.Services.AddDbContext&lt;MyDb&gt;(options =&gt; options.UseInMemoryDatabase(\"products\"));\r\nvar app = builder.Build();\r\n\r\napp.MapGet(\"\/categories\/{categoryId}\/products\", (int categoryId, int pageSize, int page, ILogger&lt;Program&gt; logger, MyDb db) =&gt;\r\n{\r\n    logger.LogInformation(\"Getting products for page {Page}\", page);\r\n    return db.Products.Where(p =&gt; p.CategoryId == categoryId).Skip((page - 1) * pageSize).Take(pageSize);\r\n});\r\n\r\napp.Run();\r\n\r\nrecord Product (int Id, string Name, int CategoryId);\r\n\r\nclass MyDb : DbContext\r\n{\r\n    public MyDb(DbContextOptions options) : base(options) { }\r\n    public DbSet&lt;Product&gt; Products { get; set; }\r\n}<\/code><\/pre>\n<p>You can now refactor your API parameters to a type and add the new attribute <code>AsParameters<\/code> to your parameter, like this:<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/categories\/{categoryId}\/products\", ([AsParameters] ProductRequest req) =&gt;\r\n{\r\n    req.Logger.LogInformation(\"Getting products for page {Page}\", req.Page);\r\n    return req.Db.Products.Where(p =&gt; p.CategoryId == req.CategoryId).Skip((req.Page - 1) * req.PageSize).Take(req.PageSize);\r\n});\r\n\r\nrecord struct ProductRequest(\r\n    int CategoryId, \r\n    int PageSize, \r\n    int Page, \r\n    ILogger&lt;ProductRequest&gt; Logger, \r\n    MyDb Db);<\/code><\/pre>\n<p>The <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/minimal-apis#parameter-binding\">parameter binding<\/a> rules will be applied to the new type&#8217;s <strong>top level properties<\/strong> or <strong>parameterized constructor parameters<\/strong>. Also, the same binding attributes (<code>FromRoute<\/code>, <code>FromQuery<\/code>, <code>FromServices<\/code>, etc.) are supported and can be applied to them.<\/p>\n<p>Let&#8217;s update the previous example to bind both <code>Page<\/code> and <code>PageSize<\/code> from the request headers instead of query string:<\/p>\n<pre><code class=\"language-csharp\">\/\/ You will need to include 'using Microsoft.AspNetCore.Mvc;'\r\nrecord struct ProductRequest(\r\n    int CategoryId,\r\n    [FromHeader(Name = \"PageSize\")] int PageSize,\r\n    [FromHeader(Name = \"Page\")] int Page,\r\n    ILogger&lt;ProductRequest&gt; Logger, \r\n    MyDb Db);<\/code><\/pre>\n<p>Both <code>classes<\/code> and <code>structs<\/code> are supported (the usage of <code>structs<\/code> is recommended to avoid additional memory allocation). However, <code>abstract<\/code> types and <code>interfaces<\/code> are not supported. In our previous example, the same type (currently a <code>record struct<\/code>) could be define as a <code>class<\/code>:<\/p>\n<pre><code class=\"language-csharp\">class ProductRequest\r\n{\r\n    public int CategoryId { get; set; }\r\n    [FromHeader(Name = \"PageSize\")]\r\n    public int PageSize { get; set; }\r\n    [FromHeader(Name = \"Page\")]\r\n    public int Page { get; set; }\r\n    public ILogger&lt;ProductRequest&gt; Logger { get; set; }\r\n    public MyDb Db { get; set; }\r\n}<\/code><\/pre>\n<p>The following rules are applied during the parameter binding:<\/p>\n<p><strong>Classes<\/strong><\/p>\n<ul>\n<li>A public parameterless constructor will be used if present<\/li>\n<li>A public parameterized constructor will be used if a single constructor is present and all arguments have a matching (case-insensitive) public property.\n<ul>\n<li>If a constructor parameter does not match with a property, <code>InvalidOperationException<\/code> will be thrown if binding is attempted.<\/li>\n<\/ul>\n<\/li>\n<li>Throw <code>InvalidOperationException<\/code> when more than one parameter is declared and the parameterless constructor is not present.<\/li>\n<li>Throw <code>InvalidOperationException<\/code> if a suitable constructor cannot be found.<\/li>\n<\/ul>\n<p><strong>Structs<\/strong><\/p>\n<ul>\n<li>A declared public parameterless constructor will always be used if present<\/li>\n<li>A public parameterized constructor will be use if a single constructor is present and all arguments have a matching (case-insensitive) public property.\n<ul>\n<li>If a constructor parameter does not match with a property, <code>InvalidOperationException<\/code> will be thrown if binding is attempted.<\/li>\n<\/ul>\n<\/li>\n<li>Since <code>struct<\/code> always has a default constructor, the default constructor will be used if it is the only one present or more than one parameterized constructor is present.<\/li>\n<\/ul>\n<blockquote><p>Note: When binding using a parameterless constructor all <strong>public settable properties<\/strong> will be bound.<\/p><\/blockquote>\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 5 is now available! Check out what&#8217;s new in ASP.NET Core in this update.<\/p>\n","protected":false},"author":417,"featured_media":40474,"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-40473","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 5 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\/40473","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=40473"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/40473\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/40474"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=40473"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=40473"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=40473"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}