-
Notifications
You must be signed in to change notification settings - Fork 502
Add lambda.DurableFunction blueprint (vs2026) #2445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
GarrettBeatty
wants to merge
4
commits into
durabletesting2
Choose a base branch
from
gcbeatty/durable-function-blueprint
base: durabletesting2
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
be96fc0
Add lambda.DurableFunction blueprint (vs2026)
GarrettBeatty 50107b5
update template
GarrettBeatty 3583655
Split durable blueprint into lambda.* and serverless.* variants
GarrettBeatty 136e31c
Trim verbose doc-comments from durable blueprint Function.cs
GarrettBeatty File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
13 changes: 13 additions & 0 deletions
13
Blueprints/BlueprintDefinitions/vs2026/DurableFunction/blueprint-manifest.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "display-name": "Durable Function", | ||
| "system-name": "DurableFunction", | ||
| "description": "A durable execution workflow that checkpoints every step, so it can be suspended during waits and resumed after a crash without re-running completed work. Deploys straight to Lambda with the 'dotnet lambda deploy-function' command.", | ||
| "sort-order": 130, | ||
| "hidden-tags": [ | ||
| "C#", | ||
| "LambdaProject" | ||
| ], | ||
| "tags": [ | ||
| "Durable" | ||
| ] | ||
| } |
39 changes: 39 additions & 0 deletions
39
...ns/vs2026/DurableFunction/template/src/BlueprintBaseName.1/.template.config/template.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| { | ||
| "author": "AWS", | ||
| "classifications": [ | ||
| "AWS", | ||
| "Lambda", | ||
| "Serverless" | ||
| ], | ||
| "name": "Lambda Durable Function", | ||
| "identity": "AWS.Lambda.Function.Durable.CSharp", | ||
| "groupIdentity": "AWS.Lambda.Function.Durable", | ||
| "shortName": "lambda.DurableFunction", | ||
| "tags": { | ||
| "language": "C#", | ||
| "type": "project" | ||
| }, | ||
| "sourceName": "BlueprintBaseName.1", | ||
| "preferNameDirectory": true, | ||
| "symbols": { | ||
| "profile": { | ||
| "type": "parameter", | ||
| "description": "The AWS credentials profile set in aws-lambda-tools-defaults.json and used as the default profile when interacting with AWS.", | ||
| "datatype": "string", | ||
| "replaces": "DefaultProfile", | ||
| "defaultValue": "" | ||
| }, | ||
| "region": { | ||
| "type": "parameter", | ||
| "description": "The AWS region set in aws-lambda-tools-defaults.json and used as the default region when interacting with AWS.", | ||
| "datatype": "string", | ||
| "replaces": "DefaultRegion", | ||
| "defaultValue": "" | ||
| } | ||
| }, | ||
| "primaryOutputs": [ | ||
| { | ||
| "path": "./BlueprintBaseName.1.csproj" | ||
| } | ||
| ] | ||
| } | ||
25 changes: 25 additions & 0 deletions
25
...itions/vs2026/DurableFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <!-- | ||
| Class-library programming model: there is NO OutputType=Exe and NO hand-written Main. | ||
| The handler delegates to DurableFunction.WrapAsync (the static wrapper model), and the managed | ||
| dotnet10 runtime hosts its own bootstrap and invokes the handler via an | ||
| Assembly::Type::Method handler string. Durable execution requires the managed dotnet10 runtime. | ||
| --> | ||
| <OutputType>Library</OutputType> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> | ||
| <AWSProjectType>Lambda</AWSProjectType> | ||
| <!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. --> | ||
| <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> | ||
| <!-- Generate ready to run images during publishing to improvement cold starts. --> | ||
| <PublishReadyToRun>true</PublishReadyToRun> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="Amazon.Lambda.Core" Version="3.1.1" /> | ||
| <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="3.0.0" /> | ||
| <PackageReference Include="Amazon.Lambda.DurableExecution" Version="0.1.1-preview" /> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will update to ga version once released |
||
| </ItemGroup> | ||
| </Project> | ||
108 changes: 108 additions & 0 deletions
108
.../BlueprintDefinitions/vs2026/DurableFunction/template/src/BlueprintBaseName.1/Function.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| using Amazon.Lambda.Core; | ||
| using Amazon.Lambda.DurableExecution; | ||
| using Amazon.Lambda.Serialization.SystemTextJson; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| // The durable runtime reads this serializer off ILambdaContext.Serializer to (de)serialize the | ||
| // invocation envelope and every checkpointed step input/output. | ||
| [assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))] | ||
|
|
||
| namespace BlueprintBaseName._1; | ||
|
|
||
| public class Function | ||
| { | ||
| /// <summary> | ||
| /// The Lambda entry point. The managed runtime invokes this method directly (via the | ||
| /// <c>Assembly::Type::Method</c> handler string in aws-lambda-tools-defaults.json) and | ||
| /// <see cref="DurableFunction.WrapAsync"/> bridges the durable invocation envelope to the | ||
| /// strongly-typed <see cref="ProcessOrder"/> workflow below. | ||
| /// </summary> | ||
| public Task<DurableExecutionInvocationOutput> Handler( | ||
| DurableExecutionInvocationInput input, ILambdaContext context) | ||
| => DurableFunction.WrapAsync<OrderRequest, OrderResult>(ProcessOrder, input, context); | ||
|
|
||
| public async Task<OrderResult> ProcessOrder(OrderRequest order, IDurableContext context) | ||
| { | ||
| // The durable logger is replay-aware: this line is emitted once, not once per replay. | ||
| context.Logger.LogInformation("Processing order {OrderId}", order.OrderId); | ||
|
|
||
| // 1) VALIDATE — a plain step. The result is checkpointed; on replay the cached value is | ||
| // returned instead of re-running the body. | ||
| var itemCount = await context.StepAsync( | ||
| async (_, _) => | ||
| { | ||
| await Task.CompletedTask; | ||
| if (order.Items is null || order.Items.Length == 0) | ||
| throw new InvalidOperationException("Order has no items."); | ||
| return order.Items.Length; | ||
| }, | ||
| name: "validate_order"); | ||
|
|
||
| // 2) CHARGE PAYMENT — a step with a retry policy. Payment gateways are flaky, so the SDK | ||
| // transparently retries with exponential backoff and checkpoints only the successful | ||
| // attempt. AtMostOncePerRetry avoids double-charging if Lambda is re-invoked mid-attempt. | ||
| var transactionId = await context.StepAsync( | ||
| async (_, _) => | ||
| { | ||
| await Task.CompletedTask; | ||
| return $"txn-{order.OrderId}"; | ||
| }, | ||
| name: "charge_payment", | ||
| config: new StepConfig | ||
| { | ||
| RetryStrategy = RetryStrategy.Exponential( | ||
| maxAttempts: 5, | ||
| initialDelay: TimeSpan.FromSeconds(2), | ||
| maxDelay: TimeSpan.FromSeconds(30), | ||
| backoffRate: 2.0), | ||
| Semantics = StepSemantics.AtMostOncePerRetry, | ||
| }); | ||
|
|
||
| // 3) SETTLEMENT WAIT — suspend the workflow for a fixed delay. While suspended there is no | ||
| // compute charge; the runtime re-invokes the function when the timer fires. | ||
| await context.WaitAsync(TimeSpan.FromSeconds(5), name: "settlement_delay"); | ||
|
|
||
| // 4) SHIP — group related steps into a single child context. The packing and labeling steps | ||
| // are checkpointed together as one logical operation. | ||
| var trackingId = await context.RunInChildContextAsync( | ||
| async (childContext, _) => | ||
| { | ||
| await childContext.StepAsync( | ||
| async (_, _) => { await Task.CompletedTask; return "packed"; }, | ||
| name: "pack"); | ||
|
|
||
| return await childContext.StepAsync( | ||
| async (_, _) => { await Task.CompletedTask; return $"trk-{order.OrderId}"; }, | ||
| name: "label"); | ||
| }, | ||
| name: "ship_order"); | ||
|
|
||
| context.Logger.LogInformation("Order {OrderId} shipped: {TrackingId}", order.OrderId, trackingId); | ||
|
|
||
| return new OrderResult | ||
| { | ||
| OrderId = order.OrderId, | ||
| Status = "shipped", | ||
| ItemCount = itemCount, | ||
| TransactionId = transactionId, | ||
| TrackingId = trackingId, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| /// <summary>Input payload for the workflow.</summary> | ||
| public class OrderRequest | ||
| { | ||
| public string? OrderId { get; set; } | ||
| public string[]? Items { get; set; } | ||
| } | ||
|
|
||
| /// <summary>Output payload returned when the workflow completes.</summary> | ||
| public class OrderResult | ||
| { | ||
| public string? OrderId { get; set; } | ||
| public string? Status { get; set; } | ||
| public int ItemCount { get; set; } | ||
| public string? TransactionId { get; set; } | ||
| public string? TrackingId { get; set; } | ||
| } |
88 changes: 88 additions & 0 deletions
88
...ntDefinitions/vs2026/DurableFunction/template/src/BlueprintBaseName.1/Readme.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # Durable Lambda Function | ||
|
|
||
| This project contains a Lambda **durable execution** workflow built with the | ||
| [Amazon.Lambda.DurableExecution](https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon.Lambda.DurableExecution) | ||
| **static wrapper** programming model, deployed straight to Lambda with `dotnet lambda deploy-function`. | ||
|
|
||
| Durable execution lets you write a multi-step workflow as a single straight-line method. The | ||
| runtime checkpoints every operation, so the function can be **suspended** during waits and | ||
| **resumed after a crash** without re-running completed work. | ||
|
|
||
| > Looking for the CloudFormation/Annotations variant? Use the **`serverless.DurableFunction`** | ||
| > template, which uses `[DurableExecution]` + a `serverless.template` and deploys with | ||
| > `dotnet lambda deploy-serverless`. | ||
|
|
||
| ## How it works | ||
|
|
||
| `Function.Handler` is the Lambda entry point. It delegates to `DurableFunction.WrapAsync`, which | ||
| bridges the durable invocation envelope to the strongly-typed `ProcessOrder` workflow: | ||
|
|
||
| ```csharp | ||
| public Task<DurableExecutionInvocationOutput> Handler( | ||
| DurableExecutionInvocationInput input, ILambdaContext context) | ||
| => DurableFunction.WrapAsync<OrderRequest, OrderResult>(ProcessOrder, input, context); | ||
| ``` | ||
|
|
||
| This is the **class-library** hosting model on the managed `dotnet10` runtime: there is no | ||
| `Main`/`LambdaBootstrap` loop and no `[DurableExecution]` annotation. The runtime hosts the | ||
| bootstrap and invokes `Handler` directly via the `Assembly::Type::Method` handler string in | ||
| `aws-lambda-tools-defaults.json`. The serializer is declared with an assembly attribute: | ||
|
|
||
| ```csharp | ||
| [assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))] | ||
| ``` | ||
|
|
||
| The workflow uses the core durable primitives on `IDurableContext`: | ||
|
|
||
| | Primitive | Used for | | ||
| |-----------|----------| | ||
| | `StepAsync` | A checkpointed unit of work. On replay the cached result is returned instead of re-running the body. | | ||
| | `StepAsync` + `StepConfig.RetryStrategy` | Retry a flaky step with exponential backoff; only the successful attempt is checkpointed. | | ||
| | `StepSemantics.AtMostOncePerRetry` | Avoid re-running a side-effecting step (e.g. charging a card) if Lambda is re-invoked mid-attempt. | | ||
| | `WaitAsync` | Suspend the workflow for a delay. There is no compute charge while suspended. | | ||
| | `RunInChildContextAsync` | Group related operations into a single logical operation. | | ||
|
|
||
| > **Note:** Durable execution requires the managed **`dotnet10`** runtime. | ||
|
|
||
| ## Requirements | ||
|
|
||
| * [.NET 10 SDK](https://dotnet.microsoft.com/download) | ||
| * [Amazon.Lambda.Tools](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) | ||
|
|
||
| ```bash | ||
| dotnet tool install -g Amazon.Lambda.Tools | ||
| ``` | ||
|
|
||
| ## Deploy | ||
|
|
||
| `aws-lambda-tools-defaults.json` sets the runtime (`dotnet10`), handler, and the durable execution | ||
| timeout (`durable-execution-timeout`). Deploy the function directly to Lambda with: | ||
|
|
||
| ```bash | ||
| dotnet lambda deploy-function | ||
| ``` | ||
|
|
||
| When the tool creates the function's execution role for you, it automatically attaches the | ||
| `AWSLambdaBasicDurableExecutionRolePolicy` managed policy, which grants the durable-execution | ||
| checkpoint permissions the function needs at runtime. If you supply your own role | ||
| (`--function-role`), make sure that policy is attached to it. | ||
|
|
||
| ## Invoke | ||
|
|
||
| Durable functions are invoked with a qualified function reference and a durable execution name: | ||
|
|
||
| ```bash | ||
| dotnet lambda invoke-function BlueprintBaseName.1 --payload '{"OrderId":"order-123","Items":["sku-1","sku-2"]}' | ||
| ``` | ||
|
|
||
| The workflow validates the order, charges payment, waits out a short settlement period, ships the | ||
| order in a child context, and returns the result. | ||
|
|
||
| ## Test | ||
|
|
||
| The included test project drives the workflow locally with the | ||
| `Amazon.Lambda.DurableExecution.Testing` runner — no AWS resources required: | ||
|
|
||
| ```bash | ||
| dotnet test | ||
| ``` |
16 changes: 16 additions & 0 deletions
16
...ns/vs2026/DurableFunction/template/src/BlueprintBaseName.1/aws-lambda-tools-defaults.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| "Information": [ | ||
| "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", | ||
| "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", | ||
| "dotnet lambda help", | ||
| "All the command line options for the Lambda command can be specified in this file." | ||
| ], | ||
| "profile": "DefaultProfile", | ||
| "region": "DefaultRegion", | ||
| "configuration": "Release", | ||
| "function-runtime": "dotnet10", | ||
| "function-memory-size": 512, | ||
| "function-timeout": 30, | ||
| "function-handler": "BlueprintBaseName.1::BlueprintBaseName._1.Function::Handler", | ||
| "durable-execution-timeout": 86400 | ||
| } |
19 changes: 19 additions & 0 deletions
19
.../DurableFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <IsTestProject>true</IsTestProject> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="Amazon.Lambda.Core" Version="3.1.1" /> | ||
| <PackageReference Include="Amazon.Lambda.TestUtilities" Version="4.1.0" /> | ||
| <PackageReference Include="Amazon.Lambda.DurableExecution.Testing" Version="0.0.1-preview" /> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this pr isnt merged yet, i tested with local nuget feed. |
||
| <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" /> | ||
| <PackageReference Include="xunit.v3" Version="3.2.2" /> | ||
| <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\BlueprintBaseName.1\BlueprintBaseName.1.csproj" /> | ||
| </ItemGroup> | ||
| </Project> | ||
56 changes: 56 additions & 0 deletions
56
...efinitions/vs2026/DurableFunction/template/test/BlueprintBaseName.1.Tests/FunctionTest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| using Amazon.Lambda.DurableExecution.Testing; | ||
| using Xunit; | ||
|
|
||
| namespace BlueprintBaseName._1.Tests; | ||
|
|
||
| public class FunctionTest | ||
| { | ||
| [Fact] | ||
| public async Task ProcessOrder_ShipsOrder() | ||
| { | ||
| var function = new Function(); | ||
|
|
||
| // The local runner drives the workflow to completion in-process using the real durable | ||
| // runtime with an in-memory backend. SkipTime collapses the settlement WaitAsync delay so | ||
| // the test does not actually block for 5 seconds. | ||
| await using var runner = new DurableTestRunner<OrderRequest, OrderResult>( | ||
| handler: function.ProcessOrder, | ||
| options: new TestRunnerOptions { SkipTime = true }); | ||
|
|
||
| var input = new OrderRequest | ||
| { | ||
| OrderId = "order-123", | ||
| Items = new[] { "sku-1", "sku-2" }, | ||
| }; | ||
|
|
||
| var result = await runner.RunAsync(input, cancellationToken: TestContext.Current.CancellationToken); | ||
|
|
||
| result.EnsureSucceeded(); | ||
| Assert.NotNull(result.Result); | ||
| Assert.Equal("order-123", result.Result!.OrderId); | ||
| Assert.Equal("shipped", result.Result.Status); | ||
| Assert.Equal(2, result.Result.ItemCount); | ||
| Assert.Equal("txn-order-123", result.Result.TransactionId); | ||
| Assert.Equal("trk-order-123", result.Result.TrackingId); | ||
|
|
||
| // Each named operation is checkpointed and inspectable. | ||
| Assert.Equal(OperationStatus.Succeeded, result.GetStep("validate_order").Status); | ||
| Assert.Equal(OperationStatus.Succeeded, result.GetStep("charge_payment").Status); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ProcessOrder_EmptyOrder_Fails() | ||
| { | ||
| var function = new Function(); | ||
|
|
||
| await using var runner = new DurableTestRunner<OrderRequest, OrderResult>( | ||
| handler: function.ProcessOrder, | ||
| options: new TestRunnerOptions { SkipTime = true }); | ||
|
|
||
| var input = new OrderRequest { OrderId = "order-456", Items = System.Array.Empty<string>() }; | ||
|
|
||
| var result = await runner.RunAsync(input, cancellationToken: TestContext.Current.CancellationToken); | ||
|
|
||
| Assert.True(result.IsFailed); | ||
| } | ||
| } |
13 changes: 13 additions & 0 deletions
13
Blueprints/BlueprintDefinitions/vs2026/DurableFunctionServerless/blueprint-manifest.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "display-name": "Durable Function", | ||
| "system-name": "DurableFunction", | ||
| "description": "A durable execution workflow that checkpoints every step, so it can be suspended during waits and resumed after a crash without re-running completed work.", | ||
| "sort-order": 130, | ||
| "hidden-tags": [ | ||
| "C#", | ||
| "ServerlessProject" | ||
| ], | ||
| "tags": [ | ||
| "Durable" | ||
| ] | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
intentionally only added to vs2026. didnt feel like it was worth adding to 2024 folder since dotnet8 will be end of life soon