From 955f5643400125ea08e6d84456ae32865d751f86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:03:01 +0000 Subject: [PATCH 01/11] Bump actions/checkout from 5.0.0 to 5.0.1 in the dotnet group Bumps the dotnet group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 5.0.0 to 5.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5...v5.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnet-code-metrics.yml | 2 +- .github/workflows/markdownlint.yml | 2 +- .github/workflows/publish-mono-samples.yml | 2 +- .github/workflows/snippets5000.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet-code-metrics.yml b/.github/workflows/dotnet-code-metrics.yml index 9b20ab9ebf6..086f9834aff 100644 --- a/.github/workflows/dotnet-code-metrics.yml +++ b/.github/workflows/dotnet-code-metrics.yml @@ -22,7 +22,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v5.0.0 + - uses: actions/checkout@v5.0.1 - name: 'Print manual run reason' if: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index b4230869ac8..61bd8315902 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 #@v2 + - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e #@v2 - name: Use Node.js uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 #@v1 with: diff --git a/.github/workflows/publish-mono-samples.yml b/.github/workflows/publish-mono-samples.yml index 637ccebba30..f8fdf4ff05b 100644 --- a/.github/workflows/publish-mono-samples.yml +++ b/.github/workflows/publish-mono-samples.yml @@ -23,7 +23,7 @@ jobs: build-mono: runs-on: macos-latest steps: - - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 #@v2 + - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e #@v2 - name: Setup .NET SDK 6 if: ${{ env.DOTNET_DO_INSTALL == 'true' }} run: | diff --git a/.github/workflows/snippets5000.yml b/.github/workflows/snippets5000.yml index be9f896175a..9c019da2528 100644 --- a/.github/workflows/snippets5000.yml +++ b/.github/workflows/snippets5000.yml @@ -32,7 +32,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 #@v4.2.2 + - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e #@v4.2.2 # Get the latest preview SDK (or sdk not installed by the runner) - name: Setup .NET SDK From f8e34329122c2684541bbbd4437633700af3e484 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 20 Nov 2025 13:58:38 -0500 Subject: [PATCH 02/11] import options (#7077) Import options into env. --- .github/workflows/dotnet-code-metrics.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-code-metrics.yml b/.github/workflows/dotnet-code-metrics.yml index 086f9834aff..f2c315dea72 100644 --- a/.github/workflows/dotnet-code-metrics.yml +++ b/.github/workflows/dotnet-code-metrics.yml @@ -27,7 +27,9 @@ jobs: - name: 'Print manual run reason' if: ${{ github.event_name == 'workflow_dispatch' }} run: | - echo 'Reason: ${{ github.event.inputs.reason }}' + echo 'Reason: $REASON' + env: + REASON: ${{ github.event.inputs.reason }} - name: .NET code metrics id: dotnet-code-metrics From a49d0a1367acd1c278cf87d2de3d7db893d2b30e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:13:21 +0000 Subject: [PATCH 03/11] Bump actions/checkout from 5.0.1 to 6.0.0 in the dotnet group Bumps the dotnet group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 5.0.1 to 6.0.0 - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: dotnet ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnet-code-metrics.yml | 2 +- .github/workflows/markdownlint.yml | 2 +- .github/workflows/publish-mono-samples.yml | 2 +- .github/workflows/snippets5000.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet-code-metrics.yml b/.github/workflows/dotnet-code-metrics.yml index f2c315dea72..6ec053c9bce 100644 --- a/.github/workflows/dotnet-code-metrics.yml +++ b/.github/workflows/dotnet-code-metrics.yml @@ -22,7 +22,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v5.0.1 + - uses: actions/checkout@v6.0.0 - name: 'Print manual run reason' if: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 61bd8315902..c8bd431a585 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e #@v2 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd #@v2 - name: Use Node.js uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 #@v1 with: diff --git a/.github/workflows/publish-mono-samples.yml b/.github/workflows/publish-mono-samples.yml index f8fdf4ff05b..7cc45aea8f2 100644 --- a/.github/workflows/publish-mono-samples.yml +++ b/.github/workflows/publish-mono-samples.yml @@ -23,7 +23,7 @@ jobs: build-mono: runs-on: macos-latest steps: - - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e #@v2 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd #@v2 - name: Setup .NET SDK 6 if: ${{ env.DOTNET_DO_INSTALL == 'true' }} run: | diff --git a/.github/workflows/snippets5000.yml b/.github/workflows/snippets5000.yml index 9c019da2528..51ca2b5d14f 100644 --- a/.github/workflows/snippets5000.yml +++ b/.github/workflows/snippets5000.yml @@ -32,7 +32,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e #@v4.2.2 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd #@v4.2.2 # Get the latest preview SDK (or sdk not installed by the runner) - name: Setup .NET SDK From 603808c8e814a40a859044e128884bd040938f99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:03:10 +0000 Subject: [PATCH 04/11] Bump the dotnet group with 2 updates Bumps the dotnet group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-node](https://github.com/actions/setup-node). Updates `actions/checkout` from 5.0.1 to 6.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6.0.1) Updates `actions/setup-node` from 6.0.0 to 6.1.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/2028fbc5c25fe9cf00d9f06a71cc4710d4507903...395ad3262231945c25e8478fd5baf05154b1d79f) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: dotnet - dependency-name: actions/setup-node dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dotnet ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnet-code-metrics.yml | 2 +- .github/workflows/markdownlint.yml | 4 ++-- .github/workflows/publish-mono-samples.yml | 2 +- .github/workflows/snippets5000.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dotnet-code-metrics.yml b/.github/workflows/dotnet-code-metrics.yml index 6ec053c9bce..24032f17829 100644 --- a/.github/workflows/dotnet-code-metrics.yml +++ b/.github/workflows/dotnet-code-metrics.yml @@ -22,7 +22,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.1 - name: 'Print manual run reason' if: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index c8bd431a585..167c3496075 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -14,9 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd #@v2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #@v2 - name: Use Node.js - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 #@v1 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #@v1 with: node-version: 12.x - name: Run Markdownlint diff --git a/.github/workflows/publish-mono-samples.yml b/.github/workflows/publish-mono-samples.yml index 7cc45aea8f2..5f923d8b2bb 100644 --- a/.github/workflows/publish-mono-samples.yml +++ b/.github/workflows/publish-mono-samples.yml @@ -23,7 +23,7 @@ jobs: build-mono: runs-on: macos-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd #@v2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #@v2 - name: Setup .NET SDK 6 if: ${{ env.DOTNET_DO_INSTALL == 'true' }} run: | diff --git a/.github/workflows/snippets5000.yml b/.github/workflows/snippets5000.yml index 51ca2b5d14f..ae2994173d8 100644 --- a/.github/workflows/snippets5000.yml +++ b/.github/workflows/snippets5000.yml @@ -32,7 +32,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd #@v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #@v4.2.2 # Get the latest preview SDK (or sdk not installed by the runner) - name: Setup .NET SDK From ef400677b97b2f906b21a927670d3362565aec1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:03:09 +0000 Subject: [PATCH 05/11] Bump actions/upload-artifact from 5.0.0 to 6.0.0 in the dotnet group Bumps the dotnet group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/upload-artifact` from 5.0.0 to 6.0.0 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/330a01c490aca151604b8cf639adc76d48f6c5d4...b7c566a772e6b6bfb58ed0dc250532a479d7789f) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: dotnet ... Signed-off-by: dependabot[bot] --- .github/workflows/snippets5000.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snippets5000.yml b/.github/workflows/snippets5000.yml index ae2994173d8..d96479ade58 100644 --- a/.github/workflows/snippets5000.yml +++ b/.github/workflows/snippets5000.yml @@ -62,7 +62,7 @@ jobs: # Update build output json file - name: Upload build results - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 #@v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #@v6.0.0 with: name: build path: ./output.json From 7021da13ac0ad6c61191426158925a4c7c3c1533 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 00:14:09 +0000 Subject: [PATCH 06/11] Bump dependabot/fetch-metadata from 2.4.0 to 2.5.0 in the dotnet group Bumps the dotnet group with 1 update: [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata). Updates `dependabot/fetch-metadata` from 2.4.0 to 2.5.0 - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v2.4.0...v2.5.0) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dotnet ... Signed-off-by: dependabot[bot] --- .github/workflows/dependabot-approve-and-automerge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-approve-and-automerge.yml b/.github/workflows/dependabot-approve-and-automerge.yml index dca6079c023..c72d4ccc575 100644 --- a/.github/workflows/dependabot-approve-and-automerge.yml +++ b/.github/workflows/dependabot-approve-and-automerge.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.4.0 + uses: dependabot/fetch-metadata@v2.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Approve a PR From 4f41ac04c1ecc007dbd60fb8784621b3875bd71e Mon Sep 17 00:00:00 2001 From: Reuben Bond <203839+ReubenBond@users.noreply.github.com> Date: Wed, 21 Jan 2026 08:32:44 -0800 Subject: [PATCH 07/11] Update Orleans samples to 10.0.0 (#7085) * Update Orleans samples to 10.0.0 * Remove Orleans.Samples.slnx solution file * Use CancellationToken overload for grain timer callback * Fix OpenApi namespace for Swashbuckle.AspNetCore 10.x --- .../AdventureClient/AdventureClient.csproj | 13 +- .../AdventureGrainInterfaces.csproj | 8 +- .../AdventureGrains/AdventureGrains.csproj | 12 +- .../AdventureServer/AdventureServer.csproj | 15 +- orleans/Adventure/AdventureServer/Program.cs | 5 +- orleans/Adventure/Directory.Build.props | 9 +- orleans/Adventure/Directory.Packages.props | 14 -- orleans/Adventure/README.md | 2 +- orleans/Adventure/run.cmd | 10 ++ orleans/Adventure/run.sh | 14 ++ .../AccountTransfer.Grains.csproj | 14 +- .../AccountTransfer.Interfaces.csproj | 10 +- .../BankAccount/BankClient/BankClient.csproj | 14 +- orleans/BankAccount/BankClient/Program.cs | 9 +- .../BankAccount/BankServer/BankServer.csproj | 16 +- orleans/BankAccount/BankServer/Program.cs | 9 +- orleans/BankAccount/Directory.Build.props | 8 +- orleans/BankAccount/Directory.Packages.props | 15 -- orleans/BankAccount/README.md | 2 +- orleans/BankAccount/run.cmd | 10 ++ orleans/BankAccount/run.sh | 14 ++ .../Blazor/BlazorServer/BlazorServer.csproj | 10 +- .../Blazor/BlazorServer/Components/App.razor | 9 +- orleans/Blazor/BlazorServer/Program.cs | 4 +- orleans/Blazor/BlazorServer/README.md | 2 +- orleans/Blazor/BlazorServer/run.cmd | 4 + orleans/Blazor/BlazorServer/run.sh | 4 + .../BlazorWasm.Client.csproj | 12 +- .../BlazorWasm.Server.csproj | 14 +- .../BlazorWasm/BlazorWasm.Server/Program.cs | 2 +- orleans/Blazor/BlazorWasm/README.md | 2 +- orleans/Blazor/BlazorWasm/run.cmd | 4 + orleans/Blazor/BlazorWasm/run.sh | 4 + orleans/Blazor/run.cmd | 4 + orleans/Blazor/run.sh | 4 + .../ChatRoom.Client/ChatRoom.Client.csproj | 17 +- orleans/ChatRoom/ChatRoom.Client/Program.cs | 27 ++-- .../ChatRoom.Common/ChatRoom.Common.csproj | 11 +- .../ChatRoom.Service/ChatRoom.Service.csproj | 10 +- orleans/ChatRoom/ChatRoom.Service/Program.cs | 7 +- orleans/ChatRoom/README.md | 2 +- orleans/ChatRoom/run.cmd | 10 ++ orleans/ChatRoom/run.sh | 14 ++ .../Chirper.Client/Chirper.Client.csproj | 19 +-- .../Chirper.Client/ShellHostedService.cs | 28 ++-- .../Chirper.Grains.Interfaces.csproj | 6 +- .../Models/ChirperMessage.cs | 7 +- .../Chirper.Grains/Chirper.Grains.csproj | 7 +- .../Chirper.Server/Chirper.Server.csproj | 10 +- orleans/Chirper/Chirper.Server/Program.cs | 3 +- orleans/Chirper/README.md | 2 +- orleans/Chirper/run.cmd | 16 ++ orleans/Chirper/run.sh | 14 ++ .../FSharpHelloWorld/Directory.Build.props | 6 +- orleans/FSharpHelloWorld/Grains/Grains.fsproj | 9 +- .../HelloWorld/HelloWorld.csproj | 17 +- .../FSharpHelloWorld/HelloWorld/Program.cs | 7 +- .../HelloWorldInterfaces.csproj | 8 +- orleans/FSharpHelloWorld/README.md | 2 +- orleans/FSharpHelloWorld/run.cmd | 3 + orleans/FSharpHelloWorld/run.sh | 3 + .../GPSTracker.Common.csproj | 9 +- .../GPSTracker.Common/LoadDriver.cs | 11 +- .../GPSTracker.FakeDeviceGateway.csproj | 10 +- .../GPSTracker.Service.csproj | 13 +- .../Grains/PushNotifierGrain.cs | 26 +-- .../GPSTracker/GPSTracker.Service/Program.cs | 14 +- orleans/GPSTracker/README.md | 2 +- orleans/GPSTracker/run.cmd | 11 ++ orleans/GPSTracker/run.sh | 15 ++ orleans/HelloWorld/HelloWorld.csproj | 10 +- orleans/HelloWorld/README.md | 2 +- orleans/HelloWorld/run.cmd | 3 + orleans/HelloWorld/run.sh | 3 + orleans/NuGet.config | 7 + orleans/Presence/Directory.Build.props | 8 +- orleans/Presence/README.md | 2 +- orleans/Presence/run.cmd | 15 ++ orleans/Presence/run.sh | 20 +++ .../Grains.Interfaces.csproj | 12 +- orleans/Presence/src/Grains/Grains.csproj | 13 +- .../src/LoadGenerator/LoadGenerator.csproj | 12 +- orleans/Presence/src/LoadGenerator/Program.cs | 11 +- .../src/PlayerWatcher/PlayerWatcher.csproj | 12 +- orleans/Presence/src/PlayerWatcher/Program.cs | 7 +- .../PresenceService/PresenceService.csproj | 11 +- .../Presence/src/PresenceService/Program.cs | 7 +- .../Orleans.ShoppingCart.Abstractions.csproj | 8 +- orleans/ShoppingCart/Directory.Build.props | 6 +- .../Grains/Orleans.ShoppingCart.Grains.csproj | 11 +- orleans/ShoppingCart/README.md | 6 +- .../Silo/Components/ManageProductModal.razor | 10 +- .../Silo/Components/ProductTable.razor | 4 +- .../Components/PurchasableProductTable.razor | 6 +- .../Silo/Components/ShoppingCartSummary.razor | 2 +- .../Silo/Orleans.ShoppingCart.Silo.csproj | 25 +-- orleans/ShoppingCart/Silo/Pages/Cart.razor | 2 +- orleans/ShoppingCart/Silo/Pages/Index.razor | 8 +- .../ShoppingCart/Silo/Pages/Products.razor | 2 +- .../ShoppingCart/Silo/Pages/Products.razor.cs | 6 +- orleans/ShoppingCart/Silo/Pages/_Host.cshtml | 1 - orleans/ShoppingCart/Silo/Program.cs | 7 +- .../ShoppingCart/Silo/Shared/MainLayout.razor | 9 +- .../Silo/Shared/MainLayout.razor.cs | 7 +- orleans/ShoppingCart/run.cmd | 4 + orleans/ShoppingCart/run.sh | 4 + orleans/Stocks/README.md | 2 +- orleans/Stocks/StockGrain.cs | 26 +-- orleans/Stocks/Stocks.csproj | 13 +- orleans/Stocks/run.cmd | 3 + orleans/Stocks/run.sh | 3 + orleans/Streaming/Common/Common.csproj | 10 +- orleans/Streaming/Common/Secrets.cs | 24 +++ .../GrainInterfaces/GrainInterfaces.csproj | 10 +- .../CustomDataAdapter/Grains/Grains.csproj | 13 +- .../NonOrleansClient/NonOrleansClient.csproj | 10 +- .../NonOrleansClient/Program.cs | 21 ++- .../CustomDataAdapter/Silo/Program.cs | 26 ++- .../CustomDataAdapter/Silo/Silo.csproj | 15 +- orleans/Streaming/CustomDataAdapter/run.cmd | 10 ++ orleans/Streaming/CustomDataAdapter/run.sh | 14 ++ orleans/Streaming/README.md | 2 +- orleans/Streaming/Simple/Client/Client.csproj | 17 +- orleans/Streaming/Simple/Client/Program.cs | 63 ++++++-- .../GrainInterfaces/GrainInterfaces.csproj | 10 +- orleans/Streaming/Simple/Grains/Grains.csproj | 14 +- .../Streaming/Simple/Grains/ProducerGrain.cs | 13 +- orleans/Streaming/Simple/SiloHost/Program.cs | 59 ++++--- .../Streaming/Simple/SiloHost/SiloHost.csproj | 17 +- orleans/Streaming/Simple/run.cmd | 10 ++ orleans/Streaming/Simple/run.sh | 14 ++ orleans/Streaming/run.cmd | 15 ++ orleans/Streaming/run.sh | 14 ++ orleans/TicTacToe/Grains/IGameGrain.cs | 5 +- .../TicTacToe/Properties/launchSettings.json | 15 +- orleans/TicTacToe/README.md | 2 +- orleans/TicTacToe/Startup.cs | 7 +- orleans/TicTacToe/TicTacToe.csproj | 16 +- orleans/TicTacToe/run.cmd | 4 + orleans/TicTacToe/run.sh | 4 + orleans/TransportLayerSecurity/README.md | 103 +++++++----- .../TLS.Client/Program.cs | 24 +-- .../TLS.Client/TLS.Client.csproj | 17 +- .../TLS.Contracts/CertificateHelper.cs | 148 ++++++++++++++++++ .../TLS.Contracts/TLS.Contracts.csproj | 9 +- .../TLS.Server/Program.cs | 21 +-- .../TLS.Server/TLS.Server.csproj | 17 +- orleans/TransportLayerSecurity/run.cmd | 10 ++ orleans/TransportLayerSecurity/run.sh | 14 ++ .../VBHelloWorld/HelloWorld/HelloWorld.csproj | 15 +- orleans/VBHelloWorld/HelloWorld/Program.cs | 7 +- .../VBHelloWorld/Interfaces/Interfaces.vbproj | 9 +- orleans/VBHelloWorld/README.md | 2 +- orleans/VBHelloWorld/VBGrains/VBGrains.vbproj | 11 +- orleans/VBHelloWorld/run.cmd | 3 + orleans/VBHelloWorld/run.sh | 3 + orleans/Voting/App.razor | 2 +- orleans/Voting/Data/PollService.cs | 29 +++- orleans/Voting/Grains/IPollGrain.cs | 4 +- orleans/Voting/Helpers/ObserverManager.cs | 2 +- orleans/Voting/Helpers/ThrottlingException.cs | 4 +- orleans/Voting/Pages/Error.cshtml.cs | 2 +- orleans/Voting/Pages/Poll.razor | 16 +- orleans/Voting/Pages/PollEditor.razor | 6 +- orleans/Voting/Pages/_Host.cshtml | 2 +- orleans/Voting/Program.cs | 29 +--- orleans/Voting/README.md | 2 +- orleans/Voting/Shared/NavMenu.razor | 2 +- orleans/Voting/Voting.csproj | 12 +- orleans/Voting/run.cmd | 5 + orleans/Voting/run.sh | 5 + 171 files changed, 1320 insertions(+), 677 deletions(-) delete mode 100644 orleans/Adventure/Directory.Packages.props create mode 100644 orleans/Adventure/run.cmd create mode 100644 orleans/Adventure/run.sh delete mode 100644 orleans/BankAccount/Directory.Packages.props create mode 100644 orleans/BankAccount/run.cmd create mode 100644 orleans/BankAccount/run.sh create mode 100644 orleans/Blazor/BlazorServer/run.cmd create mode 100644 orleans/Blazor/BlazorServer/run.sh create mode 100644 orleans/Blazor/BlazorWasm/run.cmd create mode 100644 orleans/Blazor/BlazorWasm/run.sh create mode 100644 orleans/Blazor/run.cmd create mode 100644 orleans/Blazor/run.sh create mode 100644 orleans/ChatRoom/run.cmd create mode 100644 orleans/ChatRoom/run.sh create mode 100644 orleans/Chirper/run.cmd create mode 100644 orleans/Chirper/run.sh create mode 100644 orleans/FSharpHelloWorld/run.cmd create mode 100644 orleans/FSharpHelloWorld/run.sh create mode 100644 orleans/GPSTracker/run.cmd create mode 100644 orleans/GPSTracker/run.sh create mode 100644 orleans/HelloWorld/run.cmd create mode 100644 orleans/HelloWorld/run.sh create mode 100644 orleans/NuGet.config create mode 100644 orleans/Presence/run.cmd create mode 100644 orleans/Presence/run.sh create mode 100644 orleans/ShoppingCart/run.cmd create mode 100644 orleans/ShoppingCart/run.sh create mode 100644 orleans/Stocks/run.cmd create mode 100644 orleans/Stocks/run.sh create mode 100644 orleans/Streaming/CustomDataAdapter/run.cmd create mode 100644 orleans/Streaming/CustomDataAdapter/run.sh create mode 100644 orleans/Streaming/Simple/run.cmd create mode 100644 orleans/Streaming/Simple/run.sh create mode 100644 orleans/Streaming/run.cmd create mode 100644 orleans/Streaming/run.sh create mode 100644 orleans/TicTacToe/run.cmd create mode 100644 orleans/TicTacToe/run.sh create mode 100644 orleans/TransportLayerSecurity/TLS.Contracts/CertificateHelper.cs create mode 100644 orleans/TransportLayerSecurity/run.cmd create mode 100644 orleans/TransportLayerSecurity/run.sh create mode 100644 orleans/VBHelloWorld/run.cmd create mode 100644 orleans/VBHelloWorld/run.sh create mode 100644 orleans/Voting/run.cmd create mode 100644 orleans/Voting/run.sh diff --git a/orleans/Adventure/AdventureClient/AdventureClient.csproj b/orleans/Adventure/AdventureClient/AdventureClient.csproj index b9c57297b3e..b60102928b8 100644 --- a/orleans/Adventure/AdventureClient/AdventureClient.csproj +++ b/orleans/Adventure/AdventureClient/AdventureClient.csproj @@ -1,14 +1,17 @@ - Exe + net10.0 + enable + enable true + Exe - - - + + + - + diff --git a/orleans/Adventure/AdventureGrainInterfaces/AdventureGrainInterfaces.csproj b/orleans/Adventure/AdventureGrainInterfaces/AdventureGrainInterfaces.csproj index 8ca498bd9a1..81f02211b19 100644 --- a/orleans/Adventure/AdventureGrainInterfaces/AdventureGrainInterfaces.csproj +++ b/orleans/Adventure/AdventureGrainInterfaces/AdventureGrainInterfaces.csproj @@ -1,6 +1,12 @@ + + net10.0 + enable + enable + true + - + diff --git a/orleans/Adventure/AdventureGrains/AdventureGrains.csproj b/orleans/Adventure/AdventureGrains/AdventureGrains.csproj index ec8e1f2c7a6..b2c070b69a3 100644 --- a/orleans/Adventure/AdventureGrains/AdventureGrains.csproj +++ b/orleans/Adventure/AdventureGrains/AdventureGrains.csproj @@ -1,8 +1,16 @@ + + net10.0 + enable + enable + true + + - + + - + diff --git a/orleans/Adventure/AdventureServer/AdventureServer.csproj b/orleans/Adventure/AdventureServer/AdventureServer.csproj index dfc10bace35..097521c15b0 100644 --- a/orleans/Adventure/AdventureServer/AdventureServer.csproj +++ b/orleans/Adventure/AdventureServer/AdventureServer.csproj @@ -1,16 +1,19 @@ - Exe + net10.0 + enable + enable true + Exe - - - + + + - - + + diff --git a/orleans/Adventure/AdventureServer/Program.cs b/orleans/Adventure/AdventureServer/Program.cs index 384a496eb19..1c8736ef45d 100644 --- a/orleans/Adventure/AdventureServer/Program.cs +++ b/orleans/Adventure/AdventureServer/Program.cs @@ -26,10 +26,7 @@ // Configure the host using var host = Host.CreateDefaultBuilder(args) - .UseOrleans(siloBuilder => - { - siloBuilder.UseLocalhostClustering(); - }) + .UseOrleans(siloBuilder => siloBuilder.UseLocalhostClustering()) .Build(); // Start the host diff --git a/orleans/Adventure/Directory.Build.props b/orleans/Adventure/Directory.Build.props index 365ae5d9f9f..12d9ad073ec 100644 --- a/orleans/Adventure/Directory.Build.props +++ b/orleans/Adventure/Directory.Build.props @@ -1,8 +1,3 @@ - - - net9.0 - enable - enable - true - + + diff --git a/orleans/Adventure/Directory.Packages.props b/orleans/Adventure/Directory.Packages.props deleted file mode 100644 index f668184b9e6..00000000000 --- a/orleans/Adventure/Directory.Packages.props +++ /dev/null @@ -1,14 +0,0 @@ - - - - true - - - - - - - - - - diff --git a/orleans/Adventure/README.md b/orleans/Adventure/README.md index 755d15a2913..67042909bac 100644 --- a/orleans/Adventure/README.md +++ b/orleans/Adventure/README.md @@ -34,7 +34,7 @@ This is a simple game and there are only a few verbs which the game understands: ## Sample prerequisites -This sample is written in C# and targets .NET 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Adventure/run.cmd b/orleans/Adventure/run.cmd new file mode 100644 index 00000000000..c9bdbb0127f --- /dev/null +++ b/orleans/Adventure/run.cmd @@ -0,0 +1,10 @@ +@echo off +echo === Adventure Sample === +echo This sample requires running both server and client. +echo. +echo Starting server... +start "Adventure Server" cmd /k dotnet run --project "%~dp0AdventureServer\AdventureServer.csproj" +echo Waiting for server to start... +timeout /t 5 /nobreak >nul +echo Starting client... +dotnet run --project "%~dp0AdventureClient\AdventureClient.csproj" diff --git a/orleans/Adventure/run.sh b/orleans/Adventure/run.sh new file mode 100644 index 00000000000..00bc24d7b63 --- /dev/null +++ b/orleans/Adventure/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== Adventure Sample ===" +echo "This sample requires running both server and client." +echo "" +echo "Starting server in background..." +dotnet run --project "$SCRIPT_DIR/AdventureServer/AdventureServer.csproj" & +SERVER_PID=$! +echo "Waiting for server to start..." +sleep 5 +echo "Starting client..." +dotnet run --project "$SCRIPT_DIR/AdventureClient/AdventureClient.csproj" +echo "Stopping server..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/BankAccount/AccountTransfer.Grains/AccountTransfer.Grains.csproj b/orleans/BankAccount/AccountTransfer.Grains/AccountTransfer.Grains.csproj index 665532f959c..c58efcf1aca 100644 --- a/orleans/BankAccount/AccountTransfer.Grains/AccountTransfer.Grains.csproj +++ b/orleans/BankAccount/AccountTransfer.Grains/AccountTransfer.Grains.csproj @@ -1,9 +1,17 @@ + + net10.0 + enable + enable + true + + - - + + + - + diff --git a/orleans/BankAccount/AccountTransfer.Interfaces/AccountTransfer.Interfaces.csproj b/orleans/BankAccount/AccountTransfer.Interfaces/AccountTransfer.Interfaces.csproj index 0590d7121d8..ccb0b307e3d 100644 --- a/orleans/BankAccount/AccountTransfer.Interfaces/AccountTransfer.Interfaces.csproj +++ b/orleans/BankAccount/AccountTransfer.Interfaces/AccountTransfer.Interfaces.csproj @@ -1,6 +1,12 @@ + + net10.0 + enable + enable + true + - - + + diff --git a/orleans/BankAccount/BankClient/BankClient.csproj b/orleans/BankAccount/BankClient/BankClient.csproj index b0b64843afb..eeede79be09 100644 --- a/orleans/BankAccount/BankClient/BankClient.csproj +++ b/orleans/BankAccount/BankClient/BankClient.csproj @@ -1,14 +1,18 @@ + net10.0 + enable + enable + true Exe - - - - + + + + - + diff --git a/orleans/BankAccount/BankClient/Program.cs b/orleans/BankAccount/BankClient/Program.cs index db9e8ccc25e..d60c7ddd0b8 100644 --- a/orleans/BankAccount/BankClient/Program.cs +++ b/orleans/BankAccount/BankClient/Program.cs @@ -1,13 +1,10 @@ -using AccountTransfer.Interfaces; +using AccountTransfer.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using IHost host = Host.CreateDefaultBuilder(args) - .UseOrleansClient(client => - { - client.UseLocalhostClustering() - .UseTransactions(); - }) + .UseOrleansClient(client => client.UseLocalhostClustering() + .UseTransactions()) .UseConsoleLifetime() .Build(); diff --git a/orleans/BankAccount/BankServer/BankServer.csproj b/orleans/BankAccount/BankServer/BankServer.csproj index 4619daefdd3..94818b79017 100644 --- a/orleans/BankAccount/BankServer/BankServer.csproj +++ b/orleans/BankAccount/BankServer/BankServer.csproj @@ -1,15 +1,19 @@ + net10.0 + enable + enable + true Exe - - - - + + + + - - + + diff --git a/orleans/BankAccount/BankServer/Program.cs b/orleans/BankAccount/BankServer/Program.cs index 8d1b976ab33..d9c6278e655 100644 --- a/orleans/BankAccount/BankServer/Program.cs +++ b/orleans/BankAccount/BankServer/Program.cs @@ -1,11 +1,8 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; await Host.CreateDefaultBuilder(args) - .UseOrleans(siloBuilder => - { - siloBuilder + .UseOrleans(siloBuilder => siloBuilder .UseLocalhostClustering() .AddMemoryGrainStorageAsDefault() - .UseTransactions(); - }) + .UseTransactions()) .RunConsoleAsync(); \ No newline at end of file diff --git a/orleans/BankAccount/Directory.Build.props b/orleans/BankAccount/Directory.Build.props index d9275eb5a9f..12d9ad073ec 100644 --- a/orleans/BankAccount/Directory.Build.props +++ b/orleans/BankAccount/Directory.Build.props @@ -1,7 +1,3 @@ - - - net9.0 - enable - enable - + + diff --git a/orleans/BankAccount/Directory.Packages.props b/orleans/BankAccount/Directory.Packages.props deleted file mode 100644 index f4a8b59f264..00000000000 --- a/orleans/BankAccount/Directory.Packages.props +++ /dev/null @@ -1,15 +0,0 @@ - - - - true - - - - - - - - - - - diff --git a/orleans/BankAccount/README.md b/orleans/BankAccount/README.md index b7a1a78328f..30333433195 100644 --- a/orleans/BankAccount/README.md +++ b/orleans/BankAccount/README.md @@ -89,7 +89,7 @@ public Task Withdraw(uint amount) => _balance.PerformUpdate(x => ## Sample prerequisites -This sample is written in C# and targets .NET 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/BankAccount/run.cmd b/orleans/BankAccount/run.cmd new file mode 100644 index 00000000000..7afbade8485 --- /dev/null +++ b/orleans/BankAccount/run.cmd @@ -0,0 +1,10 @@ +@echo off +echo === BankAccount Sample === +echo This sample requires running both server and client. +echo. +echo Starting server... +start "Bank Server" cmd /k dotnet run --project "%~dp0BankServer\BankServer.csproj" +echo Waiting for server to start... +timeout /t 5 /nobreak >nul +echo Starting client... +dotnet run --project "%~dp0BankClient\BankClient.csproj" diff --git a/orleans/BankAccount/run.sh b/orleans/BankAccount/run.sh new file mode 100644 index 00000000000..db294e37358 --- /dev/null +++ b/orleans/BankAccount/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== BankAccount Sample ===" +echo "This sample requires running both server and client." +echo "" +echo "Starting server in background..." +dotnet run --project "$SCRIPT_DIR/BankServer/BankServer.csproj" & +SERVER_PID=$! +echo "Waiting for server to start..." +sleep 5 +echo "Starting client..." +dotnet run --project "$SCRIPT_DIR/BankClient/BankClient.csproj" +echo "Stopping server..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/Blazor/BlazorServer/BlazorServer.csproj b/orleans/Blazor/BlazorServer/BlazorServer.csproj index 201a068c7df..b1d66974597 100644 --- a/orleans/Blazor/BlazorServer/BlazorServer.csproj +++ b/orleans/Blazor/BlazorServer/BlazorServer.csproj @@ -1,14 +1,14 @@ - - net9.0 - enable + net10.0 enable + enable + true - - + + diff --git a/orleans/Blazor/BlazorServer/Components/App.razor b/orleans/Blazor/BlazorServer/Components/App.razor index ae4eb7c6948..c3ea66640d5 100644 --- a/orleans/Blazor/BlazorServer/Components/App.razor +++ b/orleans/Blazor/BlazorServer/Components/App.razor @@ -1,14 +1,13 @@ - + - - - - + + + diff --git a/orleans/Blazor/BlazorServer/Program.cs b/orleans/Blazor/BlazorServer/Program.cs index 8cf2921fa18..97d56021c25 100644 --- a/orleans/Blazor/BlazorServer/Program.cs +++ b/orleans/Blazor/BlazorServer/Program.cs @@ -1,4 +1,4 @@ -using BlazorServer.Components; +using BlazorServer.Components; using BlazorServer.Services; using Orleans.Providers; @@ -33,7 +33,7 @@ app.UseAntiforgery(); -app.MapStaticAssets(); +app.UseStaticFiles(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); diff --git a/orleans/Blazor/BlazorServer/README.md b/orleans/Blazor/BlazorServer/README.md index 90f4942e64a..f0dc4dc6d59 100644 --- a/orleans/Blazor/BlazorServer/README.md +++ b/orleans/Blazor/BlazorServer/README.md @@ -22,7 +22,7 @@ The application is based on the [official tutorial](https://dotnet.microsoft.com ## Sample prerequisites -This sample is written in C# and targets .NET 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Blazor/BlazorServer/run.cmd b/orleans/Blazor/BlazorServer/run.cmd new file mode 100644 index 00000000000..a10ebf87547 --- /dev/null +++ b/orleans/Blazor/BlazorServer/run.cmd @@ -0,0 +1,4 @@ +@echo off +echo Building and running Blazor Server sample... +echo Open https://localhost:5001 in your browser +dotnet run --project "%~dp0BlazorServer.csproj" diff --git a/orleans/Blazor/BlazorServer/run.sh b/orleans/Blazor/BlazorServer/run.sh new file mode 100644 index 00000000000..afed3f93069 --- /dev/null +++ b/orleans/Blazor/BlazorServer/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Building and running Blazor Server sample..." +echo "Open https://localhost:5001 in your browser" +dotnet run --project "$(dirname "$0")/BlazorServer.csproj" diff --git a/orleans/Blazor/BlazorWasm/BlazorWasm.Client/BlazorWasm.Client.csproj b/orleans/Blazor/BlazorWasm/BlazorWasm.Client/BlazorWasm.Client.csproj index 4d10208a25e..fa1795d2deb 100644 --- a/orleans/Blazor/BlazorWasm/BlazorWasm.Client/BlazorWasm.Client.csproj +++ b/orleans/Blazor/BlazorWasm/BlazorWasm.Client/BlazorWasm.Client.csproj @@ -1,14 +1,14 @@ - - net9.0 + net10.0 + BlazorWasm enable enable - BlazorWasm + true - - + + - \ No newline at end of file + diff --git a/orleans/Blazor/BlazorWasm/BlazorWasm.Server/BlazorWasm.Server.csproj b/orleans/Blazor/BlazorWasm/BlazorWasm.Server/BlazorWasm.Server.csproj index 6cdda9b2f82..fb5d73b1c02 100644 --- a/orleans/Blazor/BlazorWasm/BlazorWasm.Server/BlazorWasm.Server.csproj +++ b/orleans/Blazor/BlazorWasm/BlazorWasm.Server/BlazorWasm.Server.csproj @@ -1,17 +1,17 @@ - + - Exe - net9.0 + net10.0 enable enable true + Exe true - - - + + + - \ No newline at end of file + diff --git a/orleans/Blazor/BlazorWasm/BlazorWasm.Server/Program.cs b/orleans/Blazor/BlazorWasm/BlazorWasm.Server/Program.cs index 70e75cf6fb8..c13c0a5de62 100644 --- a/orleans/Blazor/BlazorWasm/BlazorWasm.Server/Program.cs +++ b/orleans/Blazor/BlazorWasm/BlazorWasm.Server/Program.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Sample.Silo.Api; using Orleans.Providers; diff --git a/orleans/Blazor/BlazorWasm/README.md b/orleans/Blazor/BlazorWasm/README.md index 1061056a8bc..c0a6ce51ec3 100644 --- a/orleans/Blazor/BlazorWasm/README.md +++ b/orleans/Blazor/BlazorWasm/README.md @@ -21,7 +21,7 @@ The application is based on the [official tutorial](https://dotnet.microsoft.com ## Sample prerequisites -This sample is written in C# and targets .NET 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Blazor/BlazorWasm/run.cmd b/orleans/Blazor/BlazorWasm/run.cmd new file mode 100644 index 00000000000..bff84f0e9ec --- /dev/null +++ b/orleans/Blazor/BlazorWasm/run.cmd @@ -0,0 +1,4 @@ +@echo off +echo Building and running Blazor WebAssembly sample... +echo Open https://localhost:5001 in your browser +dotnet run --project "%~dp0BlazorWasm.Server.csproj" diff --git a/orleans/Blazor/BlazorWasm/run.sh b/orleans/Blazor/BlazorWasm/run.sh new file mode 100644 index 00000000000..eaffe2f5990 --- /dev/null +++ b/orleans/Blazor/BlazorWasm/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Building and running Blazor WebAssembly sample..." +echo "Open https://localhost:5001 in your browser" +dotnet run --project "$(dirname "$0")/BlazorWasm.Server.csproj" diff --git a/orleans/Blazor/run.cmd b/orleans/Blazor/run.cmd new file mode 100644 index 00000000000..151d63c353b --- /dev/null +++ b/orleans/Blazor/run.cmd @@ -0,0 +1,4 @@ +@echo off +echo Building and running Blazor Server sample... +echo Open https://localhost:5001 in your browser +dotnet run --project "%~dp0BlazorServer\BlazorServer.csproj" diff --git a/orleans/Blazor/run.sh b/orleans/Blazor/run.sh new file mode 100644 index 00000000000..d4cc9f95f92 --- /dev/null +++ b/orleans/Blazor/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Building and running Blazor Server sample..." +echo "Open https://localhost:5001 in your browser" +dotnet run --project "$(dirname "$0")/BlazorServer/BlazorServer.csproj" diff --git a/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj b/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj index 99788456524..12cb8c16f2f 100644 --- a/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj +++ b/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj @@ -1,11 +1,12 @@ - + - net9.0 + net10.0 enable enable + true Exe - + @@ -16,14 +17,14 @@ - - - - + + + + - \ No newline at end of file + diff --git a/orleans/ChatRoom/ChatRoom.Client/Program.cs b/orleans/ChatRoom/ChatRoom.Client/Program.cs index b6c39d8b5bc..585cc0008fd 100644 --- a/orleans/ChatRoom/ChatRoom.Client/Program.cs +++ b/orleans/ChatRoom/ChatRoom.Client/Program.cs @@ -5,12 +5,9 @@ using Spectre.Console; using var host = new HostBuilder() - .UseOrleansClient(clientBuilder => - { - clientBuilder + .UseOrleansClient(clientBuilder => clientBuilder .UseLocalhostClustering() - .AddMemoryStreams("chat"); - }) + .AddMemoryStreams("chat")) .Build(); PrintUsage(); @@ -152,14 +149,13 @@ static void PrintUsage() static async Task ShowChannelMembers(ClientContext context) { - var room = context.Client.GetGrain(context.CurrentChannel); - - if (!context.IsConnectedToChannel) + if (!context.IsConnectedToChannel || context.CurrentChannel is null) { AnsiConsole.MarkupLine("[bold red]You are not connected to any channel[/]"); return; } + var room = context.Client.GetGrain(context.CurrentChannel); var members = await room.GetMembers(); AnsiConsole.Write(new Rule($"Members for '{context.CurrentChannel}'") @@ -182,14 +178,13 @@ static async Task ShowChannelMembers(ClientContext context) static async Task ShowCurrentChannelHistory(ClientContext context) { - var room = context.Client.GetGrain(context.CurrentChannel); - - if (!context.IsConnectedToChannel) + if (!context.IsConnectedToChannel || context.CurrentChannel is null) { AnsiConsole.MarkupLine("[bold red]You are not connected to any channel[/]"); return; } + var room = context.Client.GetGrain(context.CurrentChannel); var history = await room.ReadHistory(1_000); AnsiConsole.Write(new Rule($"History for '{context.CurrentChannel}'") @@ -215,6 +210,7 @@ static async Task SendMessage( ClientContext context, string messageText) { + if (context.CurrentChannel is null) return; var room = context.Client.GetGrain(context.CurrentChannel); await room.Message(new ChatMsg(context.UserName, messageText)); } @@ -253,19 +249,20 @@ await AnsiConsole.Status().StartAsync("Joining channel...", async ctx => static async Task LeaveChannel(ClientContext context) { - if (!context.IsConnectedToChannel) + if (!context.IsConnectedToChannel || context.CurrentChannel is null) { AnsiConsole.MarkupLine("[bold red]You are not connected to any channel[/]"); return context; } + var channelName = context.CurrentChannel; AnsiConsole.MarkupLine( "[bold olive]Leaving channel [/]{0}", - context.CurrentChannel!); + channelName); await AnsiConsole.Status().StartAsync("Leaving channel...", async ctx => { - var room = context.Client.GetGrain(context.CurrentChannel); + var room = context.Client.GetGrain(channelName); var streamId = await room.Leave(context.UserName!); var stream = context.Client @@ -281,7 +278,7 @@ await AnsiConsole.Status().StartAsync("Leaving channel...", async ctx => } }); - AnsiConsole.MarkupLine("[bold olive]Left channel [/]{0}", context.CurrentChannel!); + AnsiConsole.MarkupLine("[bold olive]Left channel [/]{0}", channelName); return context with { CurrentChannel = null }; } diff --git a/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj b/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj index 67398f6cd68..127993c90d8 100644 --- a/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj +++ b/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj @@ -1,15 +1,16 @@ - + - net9.0 + net10.0 enable enable + true ChatRoomt - - + + - \ No newline at end of file + diff --git a/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj b/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj index 758c234526a..84f67b70a40 100644 --- a/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj +++ b/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj @@ -1,19 +1,19 @@ - + - net9.0 + net10.0 enable enable - Exe true + Exe - + - \ No newline at end of file + diff --git a/orleans/ChatRoom/ChatRoom.Service/Program.cs b/orleans/ChatRoom/ChatRoom.Service/Program.cs index 3f9a80f6cab..ba9c80d00fa 100644 --- a/orleans/ChatRoom/ChatRoom.Service/Program.cs +++ b/orleans/ChatRoom/ChatRoom.Service/Program.cs @@ -2,12 +2,9 @@ using Microsoft.Extensions.Logging; await Host.CreateDefaultBuilder(args) - .UseOrleans(siloBuilder => - { - siloBuilder.UseLocalhostClustering() + .UseOrleans(siloBuilder => siloBuilder.UseLocalhostClustering() .AddMemoryGrainStorage("PubSubStore") .AddMemoryStreams("chat") - .ConfigureLogging(logging => logging.AddConsole()); - }) + .ConfigureLogging(logging => logging.AddConsole())) .RunConsoleAsync(); diff --git a/orleans/ChatRoom/README.md b/orleans/ChatRoom/README.md index e60f4aa5d66..cf34b6ada39 100644 --- a/orleans/ChatRoom/README.md +++ b/orleans/ChatRoom/README.md @@ -26,7 +26,7 @@ Each chat channel has a corresponding `ChannelGrain` which is identified by the ## Sample prerequisites -This sample is written in C# and targets .NET 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/ChatRoom/run.cmd b/orleans/ChatRoom/run.cmd new file mode 100644 index 00000000000..1dee6881370 --- /dev/null +++ b/orleans/ChatRoom/run.cmd @@ -0,0 +1,10 @@ +@echo off +echo === ChatRoom Sample === +echo This sample requires running both service and client. +echo. +echo Starting service... +start "ChatRoom Service" cmd /k dotnet run --project "%~dp0ChatRoom.Service\ChatRoom.Service.csproj" +echo Waiting for service to start... +timeout /t 5 /nobreak >nul +echo Starting client... +dotnet run --project "%~dp0ChatRoom.Client\ChatRoom.Client.csproj" diff --git a/orleans/ChatRoom/run.sh b/orleans/ChatRoom/run.sh new file mode 100644 index 00000000000..c28a4ac01e3 --- /dev/null +++ b/orleans/ChatRoom/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== ChatRoom Sample ===" +echo "This sample requires running both service and client." +echo "" +echo "Starting service in background..." +dotnet run --project "$SCRIPT_DIR/ChatRoom.Service/ChatRoom.Service.csproj" & +SERVER_PID=$! +echo "Waiting for service to start..." +sleep 5 +echo "Starting client..." +dotnet run --project "$SCRIPT_DIR/ChatRoom.Client/ChatRoom.Client.csproj" +echo "Stopping service..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/Chirper/Chirper.Client/Chirper.Client.csproj b/orleans/Chirper/Chirper.Client/Chirper.Client.csproj index 7a56fc8f4b3..9fb15d417be 100644 --- a/orleans/Chirper/Chirper.Client/Chirper.Client.csproj +++ b/orleans/Chirper/Chirper.Client/Chirper.Client.csproj @@ -1,23 +1,16 @@ - + - Exe - net8.0 + net10.0 enable enable + true + Exe - - - - - - - - - - + + diff --git a/orleans/Chirper/Chirper.Client/ShellHostedService.cs b/orleans/Chirper/Chirper.Client/ShellHostedService.cs index 5ae6626c2bd..f71ea9c1f0b 100644 --- a/orleans/Chirper/Chirper.Client/ShellHostedService.cs +++ b/orleans/Chirper/Chirper.Client/ShellHostedService.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Chirper.Grains; using Microsoft.Extensions.Hosting; @@ -214,14 +213,23 @@ private static void ShowHelp(bool title = false) """); if (title) { - // Add some flair for the title screen - using var logoStream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream("Chirper.Client.logo.png"); - - var logo = new CanvasImage(logoStream!) - { - MaxWidth = 25 - }; + // ASCII art bird logo (replaces image-based logo to avoid SixLabors.ImageSharp dependency) + var logo = new Markup(""" + [yellow] ▄▄▄▄▄▄▄ [/] + [yellow] ▄██[/][white]░░░░░░░[/][yellow]██▄ [/] + [yellow] ▄██[/][white]░░░░░░░░░░░[/][yellow]██▄ [/] + [yellow] ██[/][white]░░░░░░░░░░░░░░░[/][yellow]██ [/] + [yellow] ██[/][white]░░░░[/][black]●[/][white]░░░░░░░░░░░[/][yellow]██ [/] + [yellow]██[/][white]░░░░░░░░░░░░░░░░░░[/][yellow]██[/] + [yellow]██[/][white]░░░░░░░░░░░░░░░░░░[/][yellow]██[/] + [darkorange]█████████[/][yellow]██[/][white]░░░░░░░░░[/][yellow]██[/] + [yellow]██[/][white]░░░░░░░░░░░░░░░░░░[/][yellow]██[/] + [yellow] ██[/][white]░░░░░░░░░░░░░░░░[/][yellow]██ [/] + [yellow] ██[/][white]░░░░░░░░░░░░░░[/][yellow]██ [/] + [yellow] ▀██[/][white]░░░░░░░░░░[/][yellow]██▀ [/] + [yellow] ▀██[/][white]░░░░░░[/][yellow]██▀ [/] + [yellow] ▀██████▀ [/] + """); var table = new Table { diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj b/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj index 1e2bd3c7486..ee5543fc01c 100644 --- a/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj +++ b/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj @@ -1,13 +1,13 @@ - - net8.0 + net10.0 enable enable + true - + diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs b/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs index 4d8f79745b8..9efc15f06a2 100644 --- a/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs +++ b/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs @@ -8,21 +8,22 @@ public record class ChirperMessage( /// /// The message content for this chirp message entry. /// - string Message, + [property: Id(0)] string Message, /// /// The timestamp of when this chirp message entry was originally republished. /// - DateTimeOffset Timestamp, + [property: Id(1)] DateTimeOffset Timestamp, /// /// The user name of the publisher of this chirp message. /// - string PublisherUserName) + [property: Id(2)] string PublisherUserName) { /// /// The unique id of this chirp message. /// + [Id(3)] public Guid MessageId { get; } = Guid.NewGuid(); /// diff --git a/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj b/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj index ea74d6737f7..3b5cd5340d2 100644 --- a/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj +++ b/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj @@ -1,17 +1,16 @@ - - net8.0 + net10.0 enable enable + true - + - diff --git a/orleans/Chirper/Chirper.Server/Chirper.Server.csproj b/orleans/Chirper/Chirper.Server/Chirper.Server.csproj index ed23905ba9d..c6b00e9917f 100644 --- a/orleans/Chirper/Chirper.Server/Chirper.Server.csproj +++ b/orleans/Chirper/Chirper.Server/Chirper.Server.csproj @@ -1,15 +1,15 @@ - + - Exe - net8.0 + net10.0 enable enable + true + Exe - - + diff --git a/orleans/Chirper/Chirper.Server/Program.cs b/orleans/Chirper/Chirper.Server/Program.cs index 76979953dbe..40db6054995 100644 --- a/orleans/Chirper/Chirper.Server/Program.cs +++ b/orleans/Chirper/Chirper.Server/Program.cs @@ -6,6 +6,5 @@ await Host.CreateDefaultBuilder(args) .UseOrleans( builder => builder .UseLocalhostClustering() - .AddMemoryGrainStorage("AccountState") - .UseDashboard()) + .AddMemoryGrainStorage("AccountState")) .RunConsoleAsync(); diff --git a/orleans/Chirper/README.md b/orleans/Chirper/README.md index 82ed63e882f..0696dd2369c 100644 --- a/orleans/Chirper/README.md +++ b/orleans/Chirper/README.md @@ -43,7 +43,7 @@ There is also an `IChirperViewer` observer interface for applications to subscri ## Sample prerequisites -This sample is written in C# and targets .NET 8.0. It requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Chirper/run.cmd b/orleans/Chirper/run.cmd new file mode 100644 index 00000000000..bc4d3758e11 --- /dev/null +++ b/orleans/Chirper/run.cmd @@ -0,0 +1,16 @@ +@echo off +echo === Chirper Sample === +echo This sample requires running both server and client. +echo. +echo Option 1: Run server only (open another terminal for client) +echo Option 2: Run client only (requires server running) +echo. +echo To run server: dotnet run --project Chirper.Server\Chirper.Server.csproj +echo To run client: dotnet run --project Chirper.Client\Chirper.Client.csproj +echo. +echo Starting server... +start "Chirper Server" cmd /k dotnet run --project "%~dp0Chirper.Server\Chirper.Server.csproj" +echo Waiting for server to start... +timeout /t 5 /nobreak >nul +echo Starting client... +dotnet run --project "%~dp0Chirper.Client\Chirper.Client.csproj" diff --git a/orleans/Chirper/run.sh b/orleans/Chirper/run.sh new file mode 100644 index 00000000000..a101136c0a8 --- /dev/null +++ b/orleans/Chirper/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== Chirper Sample ===" +echo "This sample requires running both server and client." +echo "" +echo "Starting server in background..." +dotnet run --project "$SCRIPT_DIR/Chirper.Server/Chirper.Server.csproj" & +SERVER_PID=$! +echo "Waiting for server to start..." +sleep 5 +echo "Starting client..." +dotnet run --project "$SCRIPT_DIR/Chirper.Client/Chirper.Client.csproj" +echo "Stopping server..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/FSharpHelloWorld/Directory.Build.props b/orleans/FSharpHelloWorld/Directory.Build.props index 4a8b0ce4420..bf987b61ede 100644 --- a/orleans/FSharpHelloWorld/Directory.Build.props +++ b/orleans/FSharpHelloWorld/Directory.Build.props @@ -1,7 +1,3 @@ - - <_ParentDirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('..', '$(_DirectoryBuildPropsFile)')) - - - + \ No newline at end of file diff --git a/orleans/FSharpHelloWorld/Grains/Grains.fsproj b/orleans/FSharpHelloWorld/Grains/Grains.fsproj index c3883493941..af03b548e13 100644 --- a/orleans/FSharpHelloWorld/Grains/Grains.fsproj +++ b/orleans/FSharpHelloWorld/Grains/Grains.fsproj @@ -1,6 +1,9 @@ - net8.0 + net10.0 + enable + enable + true Library latest @@ -11,6 +14,6 @@ - + - \ No newline at end of file + diff --git a/orleans/FSharpHelloWorld/HelloWorld/HelloWorld.csproj b/orleans/FSharpHelloWorld/HelloWorld/HelloWorld.csproj index 62f70574a35..bb2f19c0014 100644 --- a/orleans/FSharpHelloWorld/HelloWorld/HelloWorld.csproj +++ b/orleans/FSharpHelloWorld/HelloWorld/HelloWorld.csproj @@ -1,16 +1,21 @@ - net8.0 + net10.0 Exe + enable + enable + true + + - - - - + + + + - \ No newline at end of file + diff --git a/orleans/FSharpHelloWorld/HelloWorld/Program.cs b/orleans/FSharpHelloWorld/HelloWorld/Program.cs index 090ace2802f..2d9b8e1a929 100644 --- a/orleans/FSharpHelloWorld/HelloWorld/Program.cs +++ b/orleans/FSharpHelloWorld/HelloWorld/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using HelloWorldInterfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -8,10 +8,7 @@ [assembly: GenerateCodeForDeclaringAssembly(typeof(Grains.HelloGrain))] using var host = new HostBuilder() - .UseOrleans(builder => - { - builder.UseLocalhostClustering(); - }) + .UseOrleans(builder => builder.UseLocalhostClustering()) .UseConsoleLifetime() .Build(); diff --git a/orleans/FSharpHelloWorld/HelloWorldInterfaces/HelloWorldInterfaces.csproj b/orleans/FSharpHelloWorld/HelloWorldInterfaces/HelloWorldInterfaces.csproj index c8903911250..6dfa8beddb9 100644 --- a/orleans/FSharpHelloWorld/HelloWorldInterfaces/HelloWorldInterfaces.csproj +++ b/orleans/FSharpHelloWorld/HelloWorldInterfaces/HelloWorldInterfaces.csproj @@ -1,9 +1,11 @@ - net8.0 + net10.0 enable + enable + true - + - \ No newline at end of file + diff --git a/orleans/FSharpHelloWorld/README.md b/orleans/FSharpHelloWorld/README.md index 2d6e922b15a..4015f25ad4c 100644 --- a/orleans/FSharpHelloWorld/README.md +++ b/orleans/FSharpHelloWorld/README.md @@ -28,7 +28,7 @@ With the above attribute in place, the code generator analyzes the F# assembly a ## Sample prerequisites -This sample is written in C# and targets .NET 8.0. It requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later. +This sample is written in C# and F# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/FSharpHelloWorld/run.cmd b/orleans/FSharpHelloWorld/run.cmd new file mode 100644 index 00000000000..d051bcffd76 --- /dev/null +++ b/orleans/FSharpHelloWorld/run.cmd @@ -0,0 +1,3 @@ +@echo off +echo Building and running FSharpHelloWorld sample... +dotnet run --project "%~dp0HelloWorld\HelloWorld.csproj" diff --git a/orleans/FSharpHelloWorld/run.sh b/orleans/FSharpHelloWorld/run.sh new file mode 100644 index 00000000000..5189497ca61 --- /dev/null +++ b/orleans/FSharpHelloWorld/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Building and running FSharpHelloWorld sample..." +dotnet run --project "$(dirname "$0")/HelloWorld/HelloWorld.csproj" diff --git a/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj b/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj index 5274db20142..e1100640ff1 100644 --- a/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj +++ b/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj @@ -1,11 +1,12 @@ - + - net8.0 + net10.0 enable enable + true - + - \ No newline at end of file + diff --git a/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs b/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs index 4a8fea79004..17d7373c0c9 100644 --- a/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs +++ b/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using GPSTracker.GrainInterface; namespace GPSTracker.Common; @@ -40,7 +40,14 @@ public static async Task DriveLoad(IGrainFactory client, int numDevices, Cancell tasks.Add(DeviceLoop(client, model, cancellationToken)); } - await Task.WhenAll(tasks); + try + { + await Task.WhenAll(tasks).WaitAsync(cancellationToken); + } + catch (OperationCanceledException) + { + break; + } tasks.Clear(); } } diff --git a/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj b/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj index f986c60fff6..96dd5179661 100644 --- a/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj +++ b/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj @@ -1,11 +1,15 @@ - + - net8.0 + net10.0 enable enable + true Exe + + + - \ No newline at end of file + diff --git a/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj b/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj index 619c89e22eb..37cec45d295 100644 --- a/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj +++ b/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj @@ -1,8 +1,9 @@ - net8.0 + net10.0 enable enable + true Exe @@ -11,10 +12,10 @@ - - - - + + + + - \ No newline at end of file + diff --git a/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs b/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs index d179caebc8f..fdf19d7e41c 100644 --- a/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs +++ b/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs @@ -1,4 +1,4 @@ -using GPSTracker.Common; +using GPSTracker.Common; using GPSTracker.GrainInterface; using Orleans.Concurrency; using Orleans.Runtime; @@ -18,23 +18,29 @@ public class PushNotifierGrain : Grain, IPushNotifierGrain public override async Task OnActivateAsync(CancellationToken cancellationToken) { // Set up a timer to regularly flush the message queue - RegisterTimer( + this.RegisterGrainTimer( _ => { Flush(); return Task.CompletedTask; }, - null, - TimeSpan.FromMilliseconds(15), - TimeSpan.FromMilliseconds(15)); + new GrainTimerCreationOptions + { + DueTime = TimeSpan.FromMilliseconds(15), + Period = TimeSpan.FromMilliseconds(15), + Interleave = true + }); // Set up a timer to regularly refresh the hubs, to respond to azure infrastructure changes await RefreshHubs(); - RegisterTimer( - asyncCallback: async _ => await RefreshHubs(), - state: null, - dueTime: TimeSpan.FromSeconds(60), - period: TimeSpan.FromSeconds(60)); + this.RegisterGrainTimer( + async _ => await RefreshHubs(), + new GrainTimerCreationOptions + { + DueTime = TimeSpan.FromSeconds(60), + Period = TimeSpan.FromSeconds(60), + Interleave = true + }); await base.OnActivateAsync(cancellationToken); } diff --git a/orleans/GPSTracker/GPSTracker.Service/Program.cs b/orleans/GPSTracker/GPSTracker.Service/Program.cs index d7ef77561bb..a368c855c3b 100644 --- a/orleans/GPSTracker/GPSTracker.Service/Program.cs +++ b/orleans/GPSTracker/GPSTracker.Service/Program.cs @@ -7,12 +7,9 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenTelemetry() - .WithMetrics(metrics => - { - metrics + .WithMetrics(metrics => metrics .AddPrometheusExporter() - .AddMeter("Microsoft.Orleans"); - }) + .AddMeter("Microsoft.Orleans")) .WithTracing(tracing => { // Set a service name @@ -23,10 +20,7 @@ tracing.AddSource("Microsoft.Orleans.Runtime"); tracing.AddSource("Microsoft.Orleans.Application"); - tracing.AddZipkinExporter(zipkin => - { - zipkin.Endpoint = new Uri("http://localhost:9411/api/v2/spans"); - }); + tracing.AddZipkinExporter(zipkin => zipkin.Endpoint = new Uri("http://localhost:9411/api/v2/spans")); }); builder.Host.UseOrleans((ctx, siloBuilder) => { @@ -55,8 +49,8 @@ { app.UseDeveloperExceptionPage(); } -app.UseStaticFiles(); app.UseDefaultFiles(); +app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapHub("/locationHub"); diff --git a/orleans/GPSTracker/README.md b/orleans/GPSTracker/README.md index c69d69465ae..3fe5e1798b2 100644 --- a/orleans/GPSTracker/README.md +++ b/orleans/GPSTracker/README.md @@ -39,7 +39,7 @@ var notifier = GrainFactory.GetGrain(0); ## Sample prerequisites -This sample is written in C# and targets .NET 8.0. It requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/GPSTracker/run.cmd b/orleans/GPSTracker/run.cmd new file mode 100644 index 00000000000..763e5b897f0 --- /dev/null +++ b/orleans/GPSTracker/run.cmd @@ -0,0 +1,11 @@ +@echo off +echo === GPSTracker Sample === +echo This sample runs a web service and a fake device gateway. +echo Open http://localhost:5001 in your browser to view the map. +echo. +echo Starting service... +start "GPSTracker Service" cmd /k dotnet run --project "%~dp0GPSTracker.Service\GPSTracker.Service.csproj" +echo Waiting for service to start... +timeout /t 5 /nobreak >nul +echo Starting fake device gateway... +dotnet run --project "%~dp0GPSTracker.FakeDeviceGateway\GPSTracker.FakeDeviceGateway.csproj" diff --git a/orleans/GPSTracker/run.sh b/orleans/GPSTracker/run.sh new file mode 100644 index 00000000000..d60cc6cadab --- /dev/null +++ b/orleans/GPSTracker/run.sh @@ -0,0 +1,15 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== GPSTracker Sample ===" +echo "This sample runs a web service and a fake device gateway." +echo "Open http://localhost:5001 in your browser to view the map." +echo "" +echo "Starting service in background..." +dotnet run --project "$SCRIPT_DIR/GPSTracker.Service/GPSTracker.Service.csproj" & +SERVER_PID=$! +echo "Waiting for service to start..." +sleep 5 +echo "Starting fake device gateway..." +dotnet run --project "$SCRIPT_DIR/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj" +echo "Stopping service..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/HelloWorld/HelloWorld.csproj b/orleans/HelloWorld/HelloWorld.csproj index 785c6a04b2d..fa0d717eb07 100644 --- a/orleans/HelloWorld/HelloWorld.csproj +++ b/orleans/HelloWorld/HelloWorld.csproj @@ -1,13 +1,15 @@ - net8.0 + net10.0 Exe enable enable + true + - - - + + + diff --git a/orleans/HelloWorld/README.md b/orleans/HelloWorld/README.md index 4a206393df5..c1a6cfd23e7 100644 --- a/orleans/HelloWorld/README.md +++ b/orleans/HelloWorld/README.md @@ -89,7 +89,7 @@ Once we have a reference, we can put it to use and call `friend.SayHello("Good m ## Sample prerequisites -This sample is written in C# and targets .NET 8.0. It requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/HelloWorld/run.cmd b/orleans/HelloWorld/run.cmd new file mode 100644 index 00000000000..6955efc3ba1 --- /dev/null +++ b/orleans/HelloWorld/run.cmd @@ -0,0 +1,3 @@ +@echo off +echo Building and running HelloWorld sample... +dotnet run --project "%~dp0HelloWorld.csproj" diff --git a/orleans/HelloWorld/run.sh b/orleans/HelloWorld/run.sh new file mode 100644 index 00000000000..9b00ce8bcc5 --- /dev/null +++ b/orleans/HelloWorld/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Building and running HelloWorld sample..." +dotnet run --project "$(dirname "$0")/HelloWorld.csproj" diff --git a/orleans/NuGet.config b/orleans/NuGet.config new file mode 100644 index 00000000000..fbcef101129 --- /dev/null +++ b/orleans/NuGet.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/orleans/Presence/Directory.Build.props b/orleans/Presence/Directory.Build.props index 24a05c5a791..12d9ad073ec 100644 --- a/orleans/Presence/Directory.Build.props +++ b/orleans/Presence/Directory.Build.props @@ -1,7 +1,3 @@ - - - net8.0 - enable - enable - + + diff --git a/orleans/Presence/README.md b/orleans/Presence/README.md index 4074b5ea771..c92ddbf9fe1 100644 --- a/orleans/Presence/README.md +++ b/orleans/Presence/README.md @@ -18,7 +18,7 @@ This sample demonstrates a gaming presence service in which a game server (repre ## Sample prerequisites -This sample is written in C# and targets .NET 8.0. It requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Presence/run.cmd b/orleans/Presence/run.cmd new file mode 100644 index 00000000000..a4984e4c619 --- /dev/null +++ b/orleans/Presence/run.cmd @@ -0,0 +1,15 @@ +@echo off +echo === Presence Sample === +echo This sample has 3 components: +echo - PresenceService: The Orleans silo +echo - LoadGenerator: Generates simulated player activity +echo - PlayerWatcher: Watches player presence changes +echo. +echo Starting PresenceService... +start "Presence Service" cmd /k dotnet run --project "%~dp0src\PresenceService\PresenceService.csproj" +echo Waiting for service to start... +timeout /t 5 /nobreak >nul +echo Starting LoadGenerator... +start "Load Generator" cmd /k dotnet run --project "%~dp0src\LoadGenerator\LoadGenerator.csproj" +echo Starting PlayerWatcher... +dotnet run --project "%~dp0src\PlayerWatcher\PlayerWatcher.csproj" diff --git a/orleans/Presence/run.sh b/orleans/Presence/run.sh new file mode 100644 index 00000000000..cc76ad622eb --- /dev/null +++ b/orleans/Presence/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== Presence Sample ===" +echo "This sample has 3 components:" +echo " - PresenceService: The Orleans silo" +echo " - LoadGenerator: Generates simulated player activity" +echo " - PlayerWatcher: Watches player presence changes" +echo "" +echo "Starting PresenceService in background..." +dotnet run --project "$SCRIPT_DIR/src/PresenceService/PresenceService.csproj" & +SERVICE_PID=$! +echo "Waiting for service to start..." +sleep 5 +echo "Starting LoadGenerator in background..." +dotnet run --project "$SCRIPT_DIR/src/LoadGenerator/LoadGenerator.csproj" & +LOAD_PID=$! +echo "Starting PlayerWatcher..." +dotnet run --project "$SCRIPT_DIR/src/PlayerWatcher/PlayerWatcher.csproj" +echo "Stopping background processes..." +kill $LOAD_PID $SERVICE_PID 2>/dev/null diff --git a/orleans/Presence/src/Grains.Interfaces/Grains.Interfaces.csproj b/orleans/Presence/src/Grains.Interfaces/Grains.Interfaces.csproj index 5fa12bb20e7..f0070837234 100644 --- a/orleans/Presence/src/Grains.Interfaces/Grains.Interfaces.csproj +++ b/orleans/Presence/src/Grains.Interfaces/Grains.Interfaces.csproj @@ -1,7 +1,13 @@ - - + + + net10.0 + enable + enable + true + + - + diff --git a/orleans/Presence/src/Grains/Grains.csproj b/orleans/Presence/src/Grains/Grains.csproj index 0015df18ff0..90dbc872451 100644 --- a/orleans/Presence/src/Grains/Grains.csproj +++ b/orleans/Presence/src/Grains/Grains.csproj @@ -1,13 +1,16 @@ - + + + net10.0 + enable + enable + true + - - - + - diff --git a/orleans/Presence/src/LoadGenerator/LoadGenerator.csproj b/orleans/Presence/src/LoadGenerator/LoadGenerator.csproj index bb91bb0120b..7c495d26f14 100644 --- a/orleans/Presence/src/LoadGenerator/LoadGenerator.csproj +++ b/orleans/Presence/src/LoadGenerator/LoadGenerator.csproj @@ -1,17 +1,19 @@ - + net10.0 Exe + enable + enable + true - - - + + + - diff --git a/orleans/Presence/src/LoadGenerator/Program.cs b/orleans/Presence/src/LoadGenerator/Program.cs index c913a27952d..6bdfe7a3da7 100644 --- a/orleans/Presence/src/LoadGenerator/Program.cs +++ b/orleans/Presence/src/LoadGenerator/Program.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Presence.LoadGenerator; @@ -6,14 +6,9 @@ Console.Title = nameof(Presence.LoadGenerator); await new HostBuilder() - .UseOrleansClient(builder => - { - builder.UseLocalhostClustering(); - }) + .UseOrleansClient(builder => builder.UseLocalhostClustering()) .ConfigureServices(services => - { // This hosted service run the load generation using the available cluster client - services.AddSingleton(); - }) + services.AddSingleton()) .ConfigureLogging(builder => builder.AddConsole()) .RunConsoleAsync(); diff --git a/orleans/Presence/src/PlayerWatcher/PlayerWatcher.csproj b/orleans/Presence/src/PlayerWatcher/PlayerWatcher.csproj index bb91bb0120b..7c495d26f14 100644 --- a/orleans/Presence/src/PlayerWatcher/PlayerWatcher.csproj +++ b/orleans/Presence/src/PlayerWatcher/PlayerWatcher.csproj @@ -1,17 +1,19 @@ - + net10.0 Exe + enable + enable + true - - - + + + - diff --git a/orleans/Presence/src/PlayerWatcher/Program.cs b/orleans/Presence/src/PlayerWatcher/Program.cs index f0dbb9c2a7f..d22a3b3fc08 100644 --- a/orleans/Presence/src/PlayerWatcher/Program.cs +++ b/orleans/Presence/src/PlayerWatcher/Program.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Presence.Grains; using Presence.PlayerWatcher; @@ -6,10 +6,7 @@ Console.Title = "PlayerWatcher"; await Host.CreateDefaultBuilder() - .UseOrleansClient(builder => - { - builder.UseLocalhostClustering(); - }) + .UseOrleansClient(builder => builder.UseLocalhostClustering()) .ConfigureServices(services => { // Add regular services diff --git a/orleans/Presence/src/PresenceService/PresenceService.csproj b/orleans/Presence/src/PresenceService/PresenceService.csproj index 866b5582bf1..377fbfe6145 100644 --- a/orleans/Presence/src/PresenceService/PresenceService.csproj +++ b/orleans/Presence/src/PresenceService/PresenceService.csproj @@ -1,19 +1,20 @@ - + net10.0 Exe + enable + enable true true - - - + + + - diff --git a/orleans/Presence/src/PresenceService/Program.cs b/orleans/Presence/src/PresenceService/Program.cs index 4d7e8522d50..e4da456ae88 100644 --- a/orleans/Presence/src/PresenceService/Program.cs +++ b/orleans/Presence/src/PresenceService/Program.cs @@ -1,12 +1,9 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; Console.Title = "Presence Server"; await Host.CreateDefaultBuilder() - .UseOrleans(builder => - { - builder.UseLocalhostClustering(); - }) + .UseOrleans(builder => builder.UseLocalhostClustering()) .ConfigureLogging(builder => builder.AddConsole()) .RunConsoleAsync(); \ No newline at end of file diff --git a/orleans/ShoppingCart/Abstractions/Orleans.ShoppingCart.Abstractions.csproj b/orleans/ShoppingCart/Abstractions/Orleans.ShoppingCart.Abstractions.csproj index 1fcb8b1802d..ee5543fc01c 100644 --- a/orleans/ShoppingCart/Abstractions/Orleans.ShoppingCart.Abstractions.csproj +++ b/orleans/ShoppingCart/Abstractions/Orleans.ShoppingCart.Abstractions.csproj @@ -1,7 +1,13 @@ + + net10.0 + enable + enable + true + - + diff --git a/orleans/ShoppingCart/Directory.Build.props b/orleans/ShoppingCart/Directory.Build.props index bb8dce99261..2474b65c2bc 100644 --- a/orleans/ShoppingCart/Directory.Build.props +++ b/orleans/ShoppingCart/Directory.Build.props @@ -1,8 +1,6 @@ - + + - net8.0 - enable - enable true diff --git a/orleans/ShoppingCart/Grains/Orleans.ShoppingCart.Grains.csproj b/orleans/ShoppingCart/Grains/Orleans.ShoppingCart.Grains.csproj index 5dbae8d0eaa..fb25e36db68 100644 --- a/orleans/ShoppingCart/Grains/Orleans.ShoppingCart.Grains.csproj +++ b/orleans/ShoppingCart/Grains/Orleans.ShoppingCart.Grains.csproj @@ -1,12 +1,17 @@ + + net10.0 + enable + enable + true + - - + + - diff --git a/orleans/ShoppingCart/README.md b/orleans/ShoppingCart/README.md index 0e27d9e58e0..fdba9081e70 100644 --- a/orleans/ShoppingCart/README.md +++ b/orleans/ShoppingCart/README.md @@ -24,8 +24,8 @@ A canonical shopping cart sample application, built using Microsoft Orleans. Thi ## Features -- [.NET 8](https://docs.microsoft.com/dotnet/core/whats-new/dotnet-8) -- [ASP.NET Core Blazor](https://docs.microsoft.com/aspnet/core/blazor/?view=aspnetcore-7.0) +- [.NET 10](https://docs.microsoft.com/dotnet/core/whats-new/dotnet-10) +- [ASP.NET Core Blazor](https://docs.microsoft.com/aspnet/core/blazor) - [Orleans: Grain persistence](https://docs.microsoft.com/dotnet/orleans/grains/grain-persistence) - [Azure Storage grain persistence](https://docs.microsoft.com/dotnet/orleans/grains/grain-persistence/azure-storage) - [Orleans: Cluster management](https://docs.microsoft.com/dotnet/orleans/implementation/cluster-management) @@ -44,7 +44,7 @@ The app is architected as follows: ### Prerequisites - A [GitHub account](https://github.com/join) -- The [.NET 8 SDK or later](https://dotnet.microsoft.com/download/dotnet) +- The [.NET 10 SDK or later](https://dotnet.microsoft.com/download/dotnet) - The [Azure CLI](/cli/azure/install-azure-cli) - A .NET integrated development environment (IDE) - Feel free to use the [Visual Studio IDE](https://visualstudio.microsoft.com) or the [Visual Studio Code](https://code.visualstudio.com) diff --git a/orleans/ShoppingCart/Silo/Components/ManageProductModal.razor b/orleans/ShoppingCart/Silo/Components/ManageProductModal.razor index d509717b541..fd915d8e779 100644 --- a/orleans/ShoppingCart/Silo/Components/ManageProductModal.razor +++ b/orleans/ShoppingCart/Silo/Components/ManageProductModal.razor @@ -42,7 +42,7 @@ public ProductDetails Product { get; set; } = new(); - [CascadingParameter] MudDialogInstance MudDialog { get; set; } = null!; + [CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!; [Parameter, EditorRequired] public EventCallback ProductUpdated { get; set; } @@ -50,12 +50,12 @@ [Inject] public IDialogService DialogService { get; set; } = null!; - public void Open(string title, Func onProductUpdated) => - DialogService.Show( - title, new DialogParameters() + public async Task Open(string title, Func onProductUpdated) => + await DialogService.ShowAsync( + title, new DialogParameters() { { - nameof(ProductUpdated), + x => x.ProductUpdated, new EventCallbackFactory().Create( this, new Func(onProductUpdated)) } diff --git a/orleans/ShoppingCart/Silo/Components/ProductTable.razor b/orleans/ShoppingCart/Silo/Components/ProductTable.razor index 23e171ce50e..757f879211a 100644 --- a/orleans/ShoppingCart/Silo/Components/ProductTable.razor +++ b/orleans/ShoppingCart/Silo/Components/ProductTable.razor @@ -1,13 +1,13 @@ @using Blazor.Serialization.Extensions - + @Title @ChildContent + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-n2" Underline="false"> diff --git a/orleans/ShoppingCart/Silo/Components/PurchasableProductTable.razor b/orleans/ShoppingCart/Silo/Components/PurchasableProductTable.razor index 2dd3ca6ea90..e909b832503 100644 --- a/orleans/ShoppingCart/Silo/Components/PurchasableProductTable.razor +++ b/orleans/ShoppingCart/Silo/Components/PurchasableProductTable.razor @@ -1,10 +1,10 @@ - + @Title + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-n2" Underline="false"> @@ -36,7 +36,7 @@ await AddToCartAsync(product.Id))/> @product.Name diff --git a/orleans/ShoppingCart/Silo/Components/ShoppingCartSummary.razor b/orleans/ShoppingCart/Silo/Components/ShoppingCartSummary.razor index 835bd917ae2..9b93731202a 100644 --- a/orleans/ShoppingCart/Silo/Components/ShoppingCartSummary.razor +++ b/orleans/ShoppingCart/Silo/Components/ShoppingCartSummary.razor @@ -31,7 +31,7 @@ @_totalCost Checkout diff --git a/orleans/ShoppingCart/Silo/Orleans.ShoppingCart.Silo.csproj b/orleans/ShoppingCart/Silo/Orleans.ShoppingCart.Silo.csproj index 7f55c2f88eb..ab415d2ec34 100644 --- a/orleans/ShoppingCart/Silo/Orleans.ShoppingCart.Silo.csproj +++ b/orleans/ShoppingCart/Silo/Orleans.ShoppingCart.Silo.csproj @@ -1,22 +1,25 @@ - + + net10.0 + enable + enable + true false 98f9767c-ea86-409e-bde7-f6d352a55cce Linux - true - - - - - - - - - + + + + + + + + + diff --git a/orleans/ShoppingCart/Silo/Pages/Cart.razor b/orleans/ShoppingCart/Silo/Pages/Cart.razor index 39e1680a539..efd548d830b 100644 --- a/orleans/ShoppingCart/Silo/Pages/Cart.razor +++ b/orleans/ShoppingCart/Silo/Pages/Cart.razor @@ -1,6 +1,6 @@ @page "/cart" - + Shopping Cart Welcome to the Orleans Shopping Cart - - + + Use the Shop Inventory link to add items to your cart. - + Use the Product Management link to manage or create new products. - + Use the Cart link to manage or view all the items you've placed in your cart. diff --git a/orleans/ShoppingCart/Silo/Pages/Products.razor b/orleans/ShoppingCart/Silo/Pages/Products.razor index 2ea1df7704a..93b6d87ca54 100644 --- a/orleans/ShoppingCart/Silo/Pages/Products.razor +++ b/orleans/ShoppingCart/Silo/Pages/Products.razor @@ -2,7 +2,7 @@ + Color="Color.Primary" Size="Size.Large" OnClick=@CreateNewProductAsync> Create New Product diff --git a/orleans/ShoppingCart/Silo/Pages/Products.razor.cs b/orleans/ShoppingCart/Silo/Pages/Products.razor.cs index 65020ab7168..e2df63ba8aa 100644 --- a/orleans/ShoppingCart/Silo/Pages/Products.razor.cs +++ b/orleans/ShoppingCart/Silo/Pages/Products.razor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT License. using Orleans.ShoppingCart.Silo.Components; @@ -25,7 +25,7 @@ public sealed partial class Products protected override async Task OnInitializedAsync() => _products = await InventoryService.GetAllProductsAsync(); - private void CreateNewProduct() + private async Task CreateNewProductAsync() { if (_modal is not null) { @@ -38,7 +38,7 @@ private void CreateNewProduct() ImageUrl = fake.ImageUrl, DetailsUrl = fake.DetailsUrl }; - _modal.Open("Create Product", OnProductUpdated); + await _modal.Open("Create Product", OnProductUpdated); } } diff --git a/orleans/ShoppingCart/Silo/Pages/_Host.cshtml b/orleans/ShoppingCart/Silo/Pages/_Host.cshtml index c134856779c..fec90ef6320 100644 --- a/orleans/ShoppingCart/Silo/Pages/_Host.cshtml +++ b/orleans/ShoppingCart/Silo/Pages/_Host.cshtml @@ -12,7 +12,6 @@ Orleans Shopping Cart - diff --git a/orleans/ShoppingCart/Silo/Program.cs b/orleans/ShoppingCart/Silo/Program.cs index 8fd7ee1f15a..d07be7807d3 100644 --- a/orleans/ShoppingCart/Silo/Program.cs +++ b/orleans/ShoppingCart/Silo/Program.cs @@ -16,13 +16,10 @@ if (builder.Environment.IsDevelopment()) { - builder.Host.UseOrleans((_, builder) => - { - builder + builder.Host.UseOrleans((_, builder) => builder .UseLocalhostClustering() .AddMemoryGrainStorage("shopping-cart") - .AddStartupTask(); - }); + .AddStartupTask()); } else { diff --git a/orleans/ShoppingCart/Silo/Shared/MainLayout.razor b/orleans/ShoppingCart/Silo/Shared/MainLayout.razor index b488a377070..0d03e663c66 100644 --- a/orleans/ShoppingCart/Silo/Shared/MainLayout.razor +++ b/orleans/ShoppingCart/Silo/Shared/MainLayout.razor @@ -5,11 +5,12 @@ + @@ -19,9 +20,9 @@ - + Icon=@Icons.Material.Outlined.DarkMode Color=@Color.Inherit + ToggledIcon=@Icons.Material.Filled.WbSunny ToggledColor=@Color.Default /> + diff --git a/orleans/ShoppingCart/Silo/Shared/MainLayout.razor.cs b/orleans/ShoppingCart/Silo/Shared/MainLayout.razor.cs index 778a7118905..fe7063f38aa 100644 --- a/orleans/ShoppingCart/Silo/Shared/MainLayout.razor.cs +++ b/orleans/ShoppingCart/Silo/Shared/MainLayout.razor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT License. using MudSeverity = MudBlazor.Severity; @@ -11,7 +11,7 @@ public partial class MainLayout readonly MudTheme _theme = new() { - Palette = new PaletteLight() + PaletteLight = new PaletteLight() { Tertiary = "#7e6fff", DrawerIcon = "#aaa9b9", @@ -24,7 +24,6 @@ public partial class MainLayout Tertiary = "#7e6fff", Surface = "#1e1e2d", Background = "#1a1a27", - BackgroundGrey = "#151521", AppbarText = "#92929f", AppbarBackground = "rgba(26,26,39,0.8)", DrawerBackground = "#1a1a27", @@ -64,7 +63,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { ToastService.OnToastedRequested += OnToastRequested; - if (await LocalStorage.GetItemAsync(PrefersDarkThemeKey) + if (await LocalStorage.GetItemAsync(PrefersDarkThemeKey) is { Length: > 0 } isDarkTheme && bool.TryParse(isDarkTheme, out var parsedValue)) { diff --git a/orleans/ShoppingCart/run.cmd b/orleans/ShoppingCart/run.cmd new file mode 100644 index 00000000000..b6dfa37a774 --- /dev/null +++ b/orleans/ShoppingCart/run.cmd @@ -0,0 +1,4 @@ +@echo off +echo Building and running ShoppingCart sample... +echo Open https://localhost:52419 in your browser +dotnet run --project "%~dp0Silo\Orleans.ShoppingCart.Silo.csproj" diff --git a/orleans/ShoppingCart/run.sh b/orleans/ShoppingCart/run.sh new file mode 100644 index 00000000000..00bebeb7bf1 --- /dev/null +++ b/orleans/ShoppingCart/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Building and running ShoppingCart sample..." +echo "Open https://localhost:52419 in your browser" +dotnet run --project "$(dirname "$0")/Silo/Orleans.ShoppingCart.Silo.csproj" diff --git a/orleans/Stocks/README.md b/orleans/Stocks/README.md index d63bfd2559a..bbe4485f5c6 100644 --- a/orleans/Stocks/README.md +++ b/orleans/Stocks/README.md @@ -28,7 +28,7 @@ The sample can be run without replacing this key, but a warning message may be p ## Sample prerequisites -This sample is written in C# and targets .NET 8.0. It requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Stocks/StockGrain.cs b/orleans/Stocks/StockGrain.cs index e37598a21a9..9056d32d18a 100644 --- a/orleans/Stocks/StockGrain.cs +++ b/orleans/Stocks/StockGrain.cs @@ -13,32 +13,34 @@ public sealed class StockGrain : Grain, IStockGrain public override async Task OnActivateAsync(CancellationToken cancellationToken) { var stock = this.GetPrimaryKeyString(); - await UpdatePrice(stock); + await UpdatePrice(stock, cancellationToken); - RegisterTimer( + this.RegisterGrainTimer( UpdatePrice, stock, - TimeSpan.FromMinutes(2), - TimeSpan.FromMinutes(2)); + new GrainTimerCreationOptions + { + DueTime = TimeSpan.FromMinutes(2), + Period = TimeSpan.FromMinutes(2), + Interleave = true + }); await base.OnActivateAsync(cancellationToken); } - private async Task UpdatePrice(object stock) + private async Task UpdatePrice(string stock, CancellationToken cancellationToken) { - var priceTask = GetPriceQuote((string)stock); - - // read the results - _price = await priceTask; + _price = await GetPriceQuote(stock, cancellationToken); } - private async Task GetPriceQuote(string stock) + private async Task GetPriceQuote(string stock, CancellationToken cancellationToken) { using var resp = await _httpClient.GetAsync( - $"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={stock}&apikey={ApiKey}&datatype=csv"); + $"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={stock}&apikey={ApiKey}&datatype=csv", + cancellationToken); - return await resp.Content.ReadAsStringAsync(); + return await resp.Content.ReadAsStringAsync(cancellationToken); } public Task GetPrice() => Task.FromResult(_price); diff --git a/orleans/Stocks/Stocks.csproj b/orleans/Stocks/Stocks.csproj index 9557f91afaf..ef2230f659f 100644 --- a/orleans/Stocks/Stocks.csproj +++ b/orleans/Stocks/Stocks.csproj @@ -1,16 +1,15 @@ + net10.0 Exe - net8.0 - enable enable + enable true - - - + + + - - \ No newline at end of file + diff --git a/orleans/Stocks/run.cmd b/orleans/Stocks/run.cmd new file mode 100644 index 00000000000..6d115b5851b --- /dev/null +++ b/orleans/Stocks/run.cmd @@ -0,0 +1,3 @@ +@echo off +echo Building and running Stocks sample... +dotnet run --project "%~dp0Stocks.csproj" diff --git a/orleans/Stocks/run.sh b/orleans/Stocks/run.sh new file mode 100644 index 00000000000..5f5e2ad6420 --- /dev/null +++ b/orleans/Stocks/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Building and running Stocks sample..." +dotnet run --project "$(dirname "$0")/Stocks.csproj" diff --git a/orleans/Streaming/Common/Common.csproj b/orleans/Streaming/Common/Common.csproj index 45d1ecded67..17a60ac6b4d 100644 --- a/orleans/Streaming/Common/Common.csproj +++ b/orleans/Streaming/Common/Common.csproj @@ -1,13 +1,9 @@ - - net7.0 - enable + net10.0 enable + enable + true - - - - diff --git a/orleans/Streaming/Common/Secrets.cs b/orleans/Streaming/Common/Secrets.cs index 7bca7ea01b2..6e587817d02 100644 --- a/orleans/Streaming/Common/Secrets.cs +++ b/orleans/Streaming/Common/Secrets.cs @@ -37,4 +37,28 @@ public Secrets(string dataConnectionString, string eventHubConnectionString) } throw new FileNotFoundException($"Cannot find file {filename}"); } + + public static Secrets? TryLoadFromFile(string filename = "Secrets.json") + { + var currentDir = new DirectoryInfo(Directory.GetCurrentDirectory()); + while (currentDir != null && currentDir.Exists) + { + var filePath = Path.Combine(currentDir.FullName, filename); + if (File.Exists(filePath)) + { + var secrets = JsonSerializer.Deserialize(File.ReadAllText(filePath)); + // Return null if secrets file exists but has empty/missing values + if (secrets is null || + string.IsNullOrWhiteSpace(secrets.DataConnectionString) || + string.IsNullOrWhiteSpace(secrets.EventHubConnectionString)) + { + return null; + } + return secrets; + } + + currentDir = currentDir.Parent; + } + return null; + } } diff --git a/orleans/Streaming/CustomDataAdapter/GrainInterfaces/GrainInterfaces.csproj b/orleans/Streaming/CustomDataAdapter/GrainInterfaces/GrainInterfaces.csproj index 73cbafe1324..5fcbc2497bf 100644 --- a/orleans/Streaming/CustomDataAdapter/GrainInterfaces/GrainInterfaces.csproj +++ b/orleans/Streaming/CustomDataAdapter/GrainInterfaces/GrainInterfaces.csproj @@ -1,14 +1,14 @@ - - net7.0 - enable + net10.0 enable + enable + true - - + + diff --git a/orleans/Streaming/CustomDataAdapter/Grains/Grains.csproj b/orleans/Streaming/CustomDataAdapter/Grains/Grains.csproj index 021e0cb2073..ca85e65334c 100644 --- a/orleans/Streaming/CustomDataAdapter/Grains/Grains.csproj +++ b/orleans/Streaming/CustomDataAdapter/Grains/Grains.csproj @@ -1,20 +1,19 @@ - - net7.0 - enable + net10.0 enable + enable + true - - - + + + - diff --git a/orleans/Streaming/CustomDataAdapter/NonOrleansClient/NonOrleansClient.csproj b/orleans/Streaming/CustomDataAdapter/NonOrleansClient/NonOrleansClient.csproj index eeb6b856904..1223e932c41 100644 --- a/orleans/Streaming/CustomDataAdapter/NonOrleansClient/NonOrleansClient.csproj +++ b/orleans/Streaming/CustomDataAdapter/NonOrleansClient/NonOrleansClient.csproj @@ -1,15 +1,15 @@ - Exe - net7.0 - enable + net10.0 enable + enable + true + Exe - - + diff --git a/orleans/Streaming/CustomDataAdapter/NonOrleansClient/Program.cs b/orleans/Streaming/CustomDataAdapter/NonOrleansClient/Program.cs index 27185065eaf..7dc8f0f6a24 100644 --- a/orleans/Streaming/CustomDataAdapter/NonOrleansClient/Program.cs +++ b/orleans/Streaming/CustomDataAdapter/NonOrleansClient/Program.cs @@ -3,7 +3,23 @@ using Azure.Messaging.EventHubs.Producer; using Common; -var secrets = Secrets.LoadFromFile()!; +var secrets = Secrets.TryLoadFromFile(); +if (secrets is null) +{ + Console.Error.WriteLine("ERROR: This sample requires Azure Event Hub configuration."); + Console.Error.WriteLine(); + Console.Error.WriteLine("Please create a Secrets.json file with the following structure:"); + Console.Error.WriteLine(""" + { + "DataConnectionString": "", + "EventHubConnectionString": "" + } + """); + Console.Error.WriteLine(); + Console.Error.WriteLine("For local development without Azure, use the 'Simple' streaming sample instead,"); + Console.Error.WriteLine("which supports in-memory streaming."); + return 1; +} // Sending event to a stream // Here the StreamGuid will be encoded as the PartitionKey, and the namespace as a property of the event @@ -26,4 +42,5 @@ await Task.Delay(TimeSpan.FromSeconds(1)); } } -Console.WriteLine("Done!"); \ No newline at end of file +Console.WriteLine("Done!"); +return 0; diff --git a/orleans/Streaming/CustomDataAdapter/Silo/Program.cs b/orleans/Streaming/CustomDataAdapter/Silo/Program.cs index eb547e3b22a..7f2314acce8 100644 --- a/orleans/Streaming/CustomDataAdapter/Silo/Program.cs +++ b/orleans/Streaming/CustomDataAdapter/Silo/Program.cs @@ -1,3 +1,4 @@ +using Azure.Data.Tables; using Common; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -5,6 +6,24 @@ using Silo; +var secrets = Secrets.TryLoadFromFile(); +if (secrets is null) +{ + Console.Error.WriteLine("ERROR: This sample requires Azure Event Hub configuration."); + Console.Error.WriteLine(); + Console.Error.WriteLine("Please create a Secrets.json file with the following structure:"); + Console.Error.WriteLine(""" + { + "DataConnectionString": "", + "EventHubConnectionString": "" + } + """); + Console.Error.WriteLine(); + Console.Error.WriteLine("For local development without Azure, use the 'Simple' streaming sample instead,"); + Console.Error.WriteLine("which supports in-memory streaming."); + return 1; +} + try { var host = new HostBuilder() @@ -22,14 +41,13 @@ return 1; } -static void ConfigureSilo(HostBuilderContext context, ISiloBuilder siloBuilder) +void ConfigureSilo(HostBuilderContext context, ISiloBuilder siloBuilder) { - var secrets = Secrets.LoadFromFile()!; siloBuilder .UseLocalhostClustering(serviceId: Constants.ServiceId, clusterId: Constants.ServiceId) .AddAzureTableGrainStorage( "PubSubStore", - options => options.ConfigureTableServiceClient(secrets.DataConnectionString)) + options => options.TableServiceClient = new TableServiceClient(secrets.DataConnectionString)) .AddEventHubStreams( Constants.StreamProvider, (ISiloEventHubStreamConfigurator configurator) => @@ -48,7 +66,7 @@ static void ConfigureSilo(HostBuilderContext context, ISiloBuilder siloBuilder) configurator.UseAzureTableCheckpointer( builder => builder.Configure(options => { - options.ConfigureTableServiceClient(secrets.DataConnectionString); + options.TableServiceClient = new TableServiceClient(secrets.DataConnectionString); options.PersistInterval = TimeSpan.FromSeconds(10); })); }); diff --git a/orleans/Streaming/CustomDataAdapter/Silo/Silo.csproj b/orleans/Streaming/CustomDataAdapter/Silo/Silo.csproj index 659241d2058..c2392f4680f 100644 --- a/orleans/Streaming/CustomDataAdapter/Silo/Silo.csproj +++ b/orleans/Streaming/CustomDataAdapter/Silo/Silo.csproj @@ -1,17 +1,17 @@ - + net10.0 Exe - net7.0 - enable enable + enable + true - - - - + + + + @@ -19,5 +19,4 @@ - diff --git a/orleans/Streaming/CustomDataAdapter/run.cmd b/orleans/Streaming/CustomDataAdapter/run.cmd new file mode 100644 index 00000000000..741d9042923 --- /dev/null +++ b/orleans/Streaming/CustomDataAdapter/run.cmd @@ -0,0 +1,10 @@ +@echo off +echo === CustomDataAdapter Streaming Sample === +echo This sample demonstrates custom data adapters for non-Orleans clients. +echo. +echo Starting Silo... +start "Silo" cmd /k dotnet run --project "%~dp0Silo\Silo.csproj" +echo Waiting for silo to start... +timeout /t 5 /nobreak >nul +echo Starting NonOrleansClient... +dotnet run --project "%~dp0NonOrleansClient\NonOrleansClient.csproj" diff --git a/orleans/Streaming/CustomDataAdapter/run.sh b/orleans/Streaming/CustomDataAdapter/run.sh new file mode 100644 index 00000000000..9f55b7ab52a --- /dev/null +++ b/orleans/Streaming/CustomDataAdapter/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== CustomDataAdapter Streaming Sample ===" +echo "This sample demonstrates custom data adapters for non-Orleans clients." +echo "" +echo "Starting Silo in background..." +dotnet run --project "$SCRIPT_DIR/Silo/Silo.csproj" & +SERVER_PID=$! +echo "Waiting for silo to start..." +sleep 5 +echo "Starting NonOrleansClient..." +dotnet run --project "$SCRIPT_DIR/NonOrleansClient/NonOrleansClient.csproj" +echo "Stopping silo..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/Streaming/README.md b/orleans/Streaming/README.md index 85c125993f9..11053c19a6f 100644 --- a/orleans/Streaming/README.md +++ b/orleans/Streaming/README.md @@ -20,7 +20,7 @@ The second sample, in the `CustomDataAdapter` folder, demonstrates how to config ## Sample prerequisites -This sample is written in C# and targets .NET 7.0. It requires the [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Streaming/Simple/Client/Client.csproj b/orleans/Streaming/Simple/Client/Client.csproj index b1e9d0ece66..6ab174fc300 100644 --- a/orleans/Streaming/Simple/Client/Client.csproj +++ b/orleans/Streaming/Simple/Client/Client.csproj @@ -1,23 +1,22 @@ - + net10.0 Exe - net7.0 - enable enable + enable + true - - - - - + + + + + - diff --git a/orleans/Streaming/Simple/Client/Program.cs b/orleans/Streaming/Simple/Client/Program.cs index a4189d90695..1556ab8bdca 100644 --- a/orleans/Streaming/Simple/Client/Program.cs +++ b/orleans/Streaming/Simple/Client/Program.cs @@ -13,62 +13,97 @@ private static async Task Main(string[] args) const int maxAttempts = 5; var attempt = 0; + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (s, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + try { - var secrets = Secrets.LoadFromFile()!; + var secrets = Secrets.TryLoadFromFile(); + var useEventHub = secrets is not null; + var host = new HostBuilder() .ConfigureLogging(logging => logging.AddConsole()) .UseOrleansClient((context, client) => { client .UseLocalhostClustering(serviceId: Constants.ServiceId, clusterId: Constants.ClusterId) - .UseConnectionRetryFilter(RetryFilter) - .AddEventHubStreams( + .UseConnectionRetryFilter(RetryFilter); + + if (useEventHub) + { + // Use Azure Event Hub streaming + client.AddEventHubStreams( Constants.StreamProvider, - (configurator) => configurator.ConfigureEventHub( + configurator => configurator.ConfigureEventHub( builder => builder.Configure(options => { options.ConfigureEventHubConnection( - secrets.EventHubConnectionString, + secrets!.EventHubConnectionString, Constants.EHPath, Constants.EHConsumerGroup); }))); + Console.WriteLine("Using Azure Event Hub streaming"); + } + else + { + // Use in-memory streaming for local development + // Memory streams are handled by the silo, client just needs the provider registered + client.AddMemoryStreams(Constants.StreamProvider); + Console.WriteLine("Using in-memory streaming (no Secrets.json found)"); + } }) .Build(); - await host.StartAsync(); - Console.WriteLine("Client successfully connect to silo host"); + await host.StartAsync(cts.Token); + Console.WriteLine("Client successfully connected to silo host"); + Console.WriteLine("Press Ctrl+C to stop"); - var client = host.Services.GetRequiredService(); + var clusterClient = host.Services.GetRequiredService(); // Use the connected client to ask a grain to start producing events var key = Guid.NewGuid(); - var producer = client.GetGrain("my-producer"); + var producer = clusterClient.GetGrain("my-producer"); await producer.StartProducing(Constants.StreamNamespace, key); // Now you should see that a consumer grain was activated on the silo, and is logging when it is receiving event // Client can also subscribe to streams var streamId = StreamId.Create(Constants.StreamNamespace, key); - var stream = client + var stream = clusterClient .GetStreamProvider(Constants.StreamProvider) .GetStream(streamId); await stream.SubscribeAsync(OnNextAsync); // Now the client will also log received events - await Task.Delay(TimeSpan.FromSeconds(15)); + // Wait until cancelled + try + { + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (OperationCanceledException) + { + // Expected when Ctrl+C is pressed + } // Stop producing + Console.WriteLine("Stopping producer..."); await producer.StopProducing(); - Console.ReadKey(); + await host.StopAsync(); + return 0; + } + catch (OperationCanceledException) + { return 0; } catch (Exception e) { Console.WriteLine(e); - Console.ReadKey(); return 1; } @@ -90,4 +125,4 @@ async Task RetryFilter(Exception exception, CancellationToken cancellation return true; } } -} \ No newline at end of file +} diff --git a/orleans/Streaming/Simple/GrainInterfaces/GrainInterfaces.csproj b/orleans/Streaming/Simple/GrainInterfaces/GrainInterfaces.csproj index 73cbafe1324..5fcbc2497bf 100644 --- a/orleans/Streaming/Simple/GrainInterfaces/GrainInterfaces.csproj +++ b/orleans/Streaming/Simple/GrainInterfaces/GrainInterfaces.csproj @@ -1,14 +1,14 @@ - - net7.0 - enable + net10.0 enable + enable + true - - + + diff --git a/orleans/Streaming/Simple/Grains/Grains.csproj b/orleans/Streaming/Simple/Grains/Grains.csproj index 2667b637f77..ca85e65334c 100644 --- a/orleans/Streaming/Simple/Grains/Grains.csproj +++ b/orleans/Streaming/Simple/Grains/Grains.csproj @@ -1,21 +1,19 @@ - - net7.0 - enable + net10.0 enable + enable + true - - - - + + + - diff --git a/orleans/Streaming/Simple/Grains/ProducerGrain.cs b/orleans/Streaming/Simple/Grains/ProducerGrain.cs index 7b6891ffdfa..b3e0c6d0478 100644 --- a/orleans/Streaming/Simple/Grains/ProducerGrain.cs +++ b/orleans/Streaming/Simple/Grains/ProducerGrain.cs @@ -11,7 +11,7 @@ public class ProducerGrain : Grain, IProducerGrain private readonly ILogger _logger; private IAsyncStream? _stream; - private IDisposable? _timer; + private IGrainTimer? _timer; private int _counter = 0; @@ -30,16 +30,21 @@ public Task StartProducing(string ns, Guid key) _stream = this.GetStreamProvider(Constants.StreamProvider) .GetStream(streamId); - // Register a timer that produce an event every second + // Register a timer that produces an event every second var period = TimeSpan.FromSeconds(1); - _timer = RegisterTimer(TimerTick, null, period, period); + _timer = this.RegisterGrainTimer(TimerTick, new GrainTimerCreationOptions + { + DueTime = period, + Period = period, + Interleave = true + }); _logger.LogInformation("I will produce a new event every {Period}", period); return Task.CompletedTask; } - private async Task TimerTick(object _) + private async Task TimerTick(CancellationToken cancellationToken) { var value = _counter++; _logger.LogInformation("Sending event {EventNumber}", value); diff --git a/orleans/Streaming/Simple/SiloHost/Program.cs b/orleans/Streaming/Simple/SiloHost/Program.cs index 7448dc335f7..f50ac92320b 100644 --- a/orleans/Streaming/Simple/SiloHost/Program.cs +++ b/orleans/Streaming/Simple/SiloHost/Program.cs @@ -1,3 +1,4 @@ +using Azure.Data.Tables; using Common; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -21,26 +22,40 @@ static void ConfigureSilo(HostBuilderContext context, ISiloBuilder siloBuilder) { - var secrets = Secrets.LoadFromFile()!; - siloBuilder - .UseLocalhostClustering(serviceId: Constants.ServiceId, clusterId: Constants.ClusterId) - .AddAzureTableGrainStorage( - "PubSubStore", - options => options.ConfigureTableServiceClient(secrets.DataConnectionString)) - .AddEventHubStreams(Constants.StreamProvider, (ISiloEventHubStreamConfigurator configurator) => - { - configurator.ConfigureEventHub(builder => builder.Configure(options => - { - options.ConfigureEventHubConnection( - secrets.EventHubConnectionString, - Constants.EHPath, - Constants.EHConsumerGroup); - })); - configurator.UseAzureTableCheckpointer( - builder => builder.Configure(options => + siloBuilder.UseLocalhostClustering(serviceId: Constants.ServiceId, clusterId: Constants.ClusterId); + + var secrets = Secrets.TryLoadFromFile(); + if (secrets is not null) + { + // Use Azure Event Hub streaming with Azure Table Storage for PubSub and checkpointing + siloBuilder + .AddAzureTableGrainStorage( + "PubSubStore", + options => options.TableServiceClient = new TableServiceClient(secrets.DataConnectionString)) + .AddEventHubStreams(Constants.StreamProvider, configurator => { - options.ConfigureTableServiceClient(secrets.DataConnectionString); - options.PersistInterval = TimeSpan.FromSeconds(10); - })); - }); -} \ No newline at end of file + configurator.ConfigureEventHub(builder => builder.Configure(options => + { + options.ConfigureEventHubConnection( + secrets.EventHubConnectionString, + Constants.EHPath, + Constants.EHConsumerGroup); + })); + configurator.UseAzureTableCheckpointer( + builder => builder.Configure(options => + { + options.TableServiceClient = new TableServiceClient(secrets.DataConnectionString); + options.PersistInterval = TimeSpan.FromSeconds(10); + })); + }); + Console.WriteLine("Using Azure Event Hub streaming"); + } + else + { + // Use in-memory streaming for local development + siloBuilder + .AddMemoryGrainStorage("PubSubStore") + .AddMemoryStreams(Constants.StreamProvider); + Console.WriteLine("Using in-memory streaming (no Secrets.json found)"); + } +} diff --git a/orleans/Streaming/Simple/SiloHost/SiloHost.csproj b/orleans/Streaming/Simple/SiloHost/SiloHost.csproj index f07aeff7ce3..6dc793916a0 100644 --- a/orleans/Streaming/Simple/SiloHost/SiloHost.csproj +++ b/orleans/Streaming/Simple/SiloHost/SiloHost.csproj @@ -1,18 +1,18 @@ - + net10.0 Exe - net7.0 - enable enable + enable + true - - - - - + + + + + @@ -20,5 +20,4 @@ - diff --git a/orleans/Streaming/Simple/run.cmd b/orleans/Streaming/Simple/run.cmd new file mode 100644 index 00000000000..58e307b4d78 --- /dev/null +++ b/orleans/Streaming/Simple/run.cmd @@ -0,0 +1,10 @@ +@echo off +echo === Simple Streaming Sample === +echo This sample demonstrates basic Orleans streaming. +echo. +echo Starting SiloHost... +start "Silo Host" cmd /k dotnet run --project "%~dp0SiloHost\SiloHost.csproj" +echo Waiting for silo to start... +timeout /t 5 /nobreak >nul +echo Starting Client... +dotnet run --project "%~dp0Client\Client.csproj" diff --git a/orleans/Streaming/Simple/run.sh b/orleans/Streaming/Simple/run.sh new file mode 100644 index 00000000000..b67e9e51a26 --- /dev/null +++ b/orleans/Streaming/Simple/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== Simple Streaming Sample ===" +echo "This sample demonstrates basic Orleans streaming." +echo "" +echo "Starting SiloHost in background..." +dotnet run --project "$SCRIPT_DIR/SiloHost/SiloHost.csproj" & +SERVER_PID=$! +echo "Waiting for silo to start..." +sleep 5 +echo "Starting Client..." +dotnet run --project "$SCRIPT_DIR/Client/Client.csproj" +echo "Stopping silo..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/Streaming/run.cmd b/orleans/Streaming/run.cmd new file mode 100644 index 00000000000..a7b8a34cf00 --- /dev/null +++ b/orleans/Streaming/run.cmd @@ -0,0 +1,15 @@ +@echo off +echo === Streaming Samples === +echo Choose which streaming sample to run: +echo 1. Simple - Basic streaming with Orleans streams +echo 2. CustomDataAdapter - Custom data adapter for non-Orleans clients +echo. +set /p choice="Enter choice (1 or 2): " +if "%choice%"=="1" ( + call "%~dp0Simple\run.cmd" +) else if "%choice%"=="2" ( + call "%~dp0CustomDataAdapter\run.cmd" +) else ( + echo Invalid choice. Running Simple sample by default... + call "%~dp0Simple\run.cmd" +) diff --git a/orleans/Streaming/run.sh b/orleans/Streaming/run.sh new file mode 100644 index 00000000000..57c27ab1598 --- /dev/null +++ b/orleans/Streaming/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== Streaming Samples ===" +echo "Choose which streaming sample to run:" +echo " 1. Simple - Basic streaming with Orleans streams" +echo " 2. CustomDataAdapter - Custom data adapter for non-Orleans clients" +echo "" +read -p "Enter choice (1 or 2): " choice +case $choice in + 1) "$SCRIPT_DIR/Simple/run.sh" ;; + 2) "$SCRIPT_DIR/CustomDataAdapter/run.sh" ;; + *) echo "Invalid choice. Running Simple sample by default..." + "$SCRIPT_DIR/Simple/run.sh" ;; +esac diff --git a/orleans/TicTacToe/Grains/IGameGrain.cs b/orleans/TicTacToe/Grains/IGameGrain.cs index 6430904a190..4437d3e1c4c 100644 --- a/orleans/TicTacToe/Grains/IGameGrain.cs +++ b/orleans/TicTacToe/Grains/IGameGrain.cs @@ -10,7 +10,7 @@ public interface IGameGrain : IGrainWithGuidKey Task SetName(string name); } -[Serializable] +[GenerateSerializer] public enum GameState { AwaitingPlayers, @@ -18,7 +18,7 @@ public enum GameState Finished } -[Serializable] +[GenerateSerializer] public enum GameOutcome { Win, @@ -26,6 +26,7 @@ public enum GameOutcome Draw } + [GenerateSerializer] public struct GameMove { diff --git a/orleans/TicTacToe/Properties/launchSettings.json b/orleans/TicTacToe/Properties/launchSettings.json index cd4858bbed2..2f31df71d05 100644 --- a/orleans/TicTacToe/Properties/launchSettings.json +++ b/orleans/TicTacToe/Properties/launchSettings.json @@ -1,20 +1,9 @@ { "iisSettings": { "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:60474/", - "sslPort": 44360 - } + "anonymousAuthentication": true }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "TicTacToe": { "commandName": "Project", "launchBrowser": true, @@ -24,4 +13,4 @@ "applicationUrl": "http://localhost:5000" } } -} \ No newline at end of file +} diff --git a/orleans/TicTacToe/README.md b/orleans/TicTacToe/README.md index 9ce72513f11..391a320f3b3 100644 --- a/orleans/TicTacToe/README.md +++ b/orleans/TicTacToe/README.md @@ -28,7 +28,7 @@ The call flow is as follows: ## Sample prerequisites -This sample is written in C# and targets .NET 7.0. It requires the [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/TicTacToe/Startup.cs b/orleans/TicTacToe/Startup.cs index fa248e07ef3..192f4a99ceb 100644 --- a/orleans/TicTacToe/Startup.cs +++ b/orleans/TicTacToe/Startup.cs @@ -1,4 +1,4 @@ -public class Startup +public class Startup { public void ConfigureServices(IServiceCollection services) => services.AddControllersWithViews(); @@ -17,10 +17,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - }); + app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute()); } } diff --git a/orleans/TicTacToe/TicTacToe.csproj b/orleans/TicTacToe/TicTacToe.csproj index ddb6d24ce57..34f0ae400c0 100644 --- a/orleans/TicTacToe/TicTacToe.csproj +++ b/orleans/TicTacToe/TicTacToe.csproj @@ -1,14 +1,12 @@ - + - net7.0 - enable + net10.0 enable + enable + true - - - - - + + - \ No newline at end of file + diff --git a/orleans/TicTacToe/run.cmd b/orleans/TicTacToe/run.cmd new file mode 100644 index 00000000000..36412ffbc56 --- /dev/null +++ b/orleans/TicTacToe/run.cmd @@ -0,0 +1,4 @@ +@echo off +echo Building and running TicTacToe sample... +echo Open http://localhost:5000 in your browser +dotnet run --project "%~dp0TicTacToe.csproj" diff --git a/orleans/TicTacToe/run.sh b/orleans/TicTacToe/run.sh new file mode 100644 index 00000000000..349414f7bad --- /dev/null +++ b/orleans/TicTacToe/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Building and running TicTacToe sample..." +echo "Open http://localhost:5000 in your browser" +dotnet run --project "$(dirname "$0")/TicTacToe.csproj" diff --git a/orleans/TransportLayerSecurity/README.md b/orleans/TransportLayerSecurity/README.md index 7f4caccd257..0f162f062f2 100644 --- a/orleans/TransportLayerSecurity/README.md +++ b/orleans/TransportLayerSecurity/README.md @@ -14,26 +14,59 @@ description: "An Orleans sample demonstrating transport layer security (TLS)." This sample demonstrates a client and silo which communicate over a channel secured by mutual Transport Layer Security (mTLS). -The key parts to this sample are: +## Running the sample -* Generating a self-signed certificate (a CA-issued certificate can be used instead) -* Configuring the server and client to use mutual-TLS for authenticating connections. +The sample automatically creates a self-signed certificate if one doesn't exist. Simply run the sample: -The important difference from other samples is the `ISiloBuilder.UseTls(...)` in [`Program.cs`](./TLS.Server/Program.cs) on the server and `IClientBuilder.UseTls` on the client: +```powershell +.\run.cmd +``` + +Or on Linux/macOS: + +```bash +./run.sh +``` + +The sample will: + +1. Check if a certificate named `fakedomain.faketld` exists in the user's certificate store +2. If not found, automatically create a self-signed certificate and store it +3. Start the silo and client using TLS for all communication + +## Key concepts + +The important parts of this sample are: + +* Automatic self-signed certificate generation for development +* Configuring the server and client to use mutual-TLS for authenticating connections + +### Certificate management + +The `CertificateHelper` class in `TLS.Contracts` handles certificate management: ```csharp -siloBuilder.UseTls( - StoreName.My, - "fakedomain.faketld", +// Automatically ensures a certificate exists (creates if missing) +CertificateHelper.EnsureCertificateExists(); +``` + +### TLS configuration + +The `ISiloBuilder.UseTls(...)` in [`Program.cs`](./TLS.Server/Program.cs) on the server and `IClientBuilder.UseTls` on the client configure TLS: + +```csharp +builder.UseTls( + CertificateHelper.StoreName, + CertificateHelper.CertificateSubject, allowInvalid: isDevelopment, - StoreLocation.CurrentUser, + CertificateHelper.StoreLocation, options => { // In this sample there is only one server, however if there are multiple silos then the TargetHost must be set // for each connection which is initiated. options.OnAuthenticateAsClient = (connection, sslOptions) => { - sslOptions.TargetHost = "fakedomain.faketld"; + sslOptions.TargetHost = CertificateHelper.CertificateSubject; }; if (isDevelopment) @@ -44,49 +77,39 @@ siloBuilder.UseTls( }) ``` -## Sample prerequisites - -This sample is written in C# and targets .NET 7.0. It requires the [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) or later. - -## Building the sample - -To download and run the sample, follow these steps: - -1. Download and unzip the sample. -2. In Visual Studio (2022 or later): - 1. On the menu bar, choose **File** > **Open** > **Project/Solution**. - 2. Navigate to the folder that holds the unzipped sample code, and open the C# project (.csproj) file. - 3. Choose the F5 key to run with debugging, or Ctrl+F5 keys to run the project without debugging. -3. From the command line: - 1. Navigate to the folder that holds the unzipped sample code. - 2. At the command line, type [`dotnet run`](https://docs.microsoft.com/dotnet/core/tools/dotnet-run). - -For the sample, we will generate and use a self-signed certificate. +## Manual certificate management -***NOTE:*** Ensure that security best practices are followed when deploying your application to a production environment. +### Creating a certificate manually (optional) -A self-signed certificate can be generated & installed using PowerShell: +If you prefer to create the certificate manually using PowerShell: ```powershell $cert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My -DnsName "fakedomain.faketld" ``` -Now that the certificate configured in the sample is installed, run the client and silo: +### Removing the certificate -Start the silo using the following command: +To remove the self-signed certificate after running the sample: ```powershell -dotnet run --project TLS.Server +Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Subject -like "*fakedomain.faketld*" } | Remove-Item ``` -Start the client in a different command window using the following command: +## Sample prerequisites -```powershell -dotnet run --project TLS.Client -``` +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. -Once you have successfully run the sample, remove the self-signed certificate which was generated above: +## Building the sample -```powershell -Remove-Item "Cert:\CurrentUser\My\$($cert.ThumbPrint)" -``` +To download and run the sample, follow these steps: + +1. Download and unzip the sample. +2. In Visual Studio (2022 or later): + 1. On the menu bar, choose **File** > **Open** > **Project/Solution**. + 2. Navigate to the folder that holds the unzipped sample code, and open the solution file. + 3. Choose the F5 key to run with debugging, or Ctrl+F5 keys to run the project without debugging. +3. From the command line: + 1. Navigate to the folder that holds the unzipped sample code. + 2. Run `.\run.cmd` (Windows) or `./run.sh` (Linux/macOS). + +***NOTE:*** Ensure that security best practices are followed when deploying your application to a production environment. Use CA-issued certificates and do not allow invalid certificates in production. diff --git a/orleans/TransportLayerSecurity/TLS.Client/Program.cs b/orleans/TransportLayerSecurity/TLS.Client/Program.cs index 99840eef058..1538076ae96 100644 --- a/orleans/TransportLayerSecurity/TLS.Client/Program.cs +++ b/orleans/TransportLayerSecurity/TLS.Client/Program.cs @@ -1,20 +1,24 @@ +using HelloWorld; using HelloWorld.Interfaces; -using System.Security.Cryptography.X509Certificates; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Security.Cryptography.X509Certificates; + +// Ensure the self-signed certificate exists (creates one if missing) +CertificateHelper.EnsureCertificateExists(); // Configure a client and connect to the service. using var host = new HostBuilder() .UseOrleansClient(builder => - builder.UseLocalhostClustering(serviceId: "HelloWorldApp", clusterId: "dev") - .UseTls(StoreName.My, "fakedomain.faketld", + builder.UseLocalhostClustering() + .UseTls(CertificateHelper.StoreName, CertificateHelper.CertificateSubject, allowInvalid: true, - StoreLocation.CurrentUser, + CertificateHelper.StoreLocation, options => { options.OnAuthenticateAsClient = (connection, sslOptions) => - sslOptions.TargetHost = "fakedomain.faketld"; + sslOptions.TargetHost = CertificateHelper.CertificateSubject; // NOTE: Do not do this in a production environment, since it is insecure. options.AllowAnyRemoteCertificate(); @@ -23,11 +27,13 @@ .Build(); await host.StartAsync(); -Console.WriteLine("Client successfully connect to silo host"); +Console.WriteLine("Client successfully connected to silo host"); // Use the connected client to call a grain, writing the result to the terminal. var factory = host.Services.GetRequiredService(); var friend = factory.GetGrain(0); var response = await friend.SayHello("Good morning, my friend!"); -Console.WriteLine("\n\n{0}\n\n", response); -Console.ReadKey(); +Console.WriteLine($"\n\n{response}\n\n"); + +Console.WriteLine("Press Enter to exit..."); +Console.ReadLine(); diff --git a/orleans/TransportLayerSecurity/TLS.Client/TLS.Client.csproj b/orleans/TransportLayerSecurity/TLS.Client/TLS.Client.csproj index c120f2536e2..bf47f2a501e 100644 --- a/orleans/TransportLayerSecurity/TLS.Client/TLS.Client.csproj +++ b/orleans/TransportLayerSecurity/TLS.Client/TLS.Client.csproj @@ -1,18 +1,21 @@ + net10.0 Exe - net7.0 - enable enable + enable + true true + - - - - + + + + + - \ No newline at end of file + diff --git a/orleans/TransportLayerSecurity/TLS.Contracts/CertificateHelper.cs b/orleans/TransportLayerSecurity/TLS.Contracts/CertificateHelper.cs new file mode 100644 index 00000000000..e092fc03c7f --- /dev/null +++ b/orleans/TransportLayerSecurity/TLS.Contracts/CertificateHelper.cs @@ -0,0 +1,148 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace HelloWorld; + +/// +/// Helper class for managing self-signed certificates for TLS communication. +/// +public static class CertificateHelper +{ + public const string CertificateSubject = "fakedomain.faketld"; + public const StoreName StoreName = System.Security.Cryptography.X509Certificates.StoreName.My; + public const StoreLocation StoreLocation = System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser; + + /// + /// Ensures a self-signed certificate exists in the certificate store. + /// If the certificate doesn't exist, it creates one. + /// + /// The certificate that was found or created. + public static X509Certificate2 EnsureCertificateExists() + { + var existingCert = FindCertificate(); + if (existingCert is not null) + { + Console.WriteLine($"Found existing certificate: {CertificateSubject}"); + return existingCert; + } + + Console.WriteLine($"Certificate '{CertificateSubject}' not found. Creating a new self-signed certificate..."); + return CreateAndStoreCertificate(); + } + + /// + /// Finds an existing certificate in the store. + /// + public static X509Certificate2? FindCertificate() + { + using var store = new X509Store(StoreName, StoreLocation); + store.Open(OpenFlags.ReadOnly); + + var certs = store.Certificates.Find( + X509FindType.FindBySubjectName, + CertificateSubject, + validOnly: false); + + // Return a valid (not expired) certificate if available + foreach (var cert in certs) + { + if (cert.NotAfter > DateTime.Now && cert.NotBefore <= DateTime.Now) + { + return cert; + } + } + + return null; + } + + /// + /// Creates a self-signed certificate and stores it in the certificate store. + /// + private static X509Certificate2 CreateAndStoreCertificate() + { + // Create RSA key + using var rsa = RSA.Create(2048); + + // Create certificate request + var request = new CertificateRequest( + $"CN={CertificateSubject}", + rsa, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + // Add extensions + request.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + + request.CertificateExtensions.Add( + new X509KeyUsageExtension( + X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, + false)); + + request.CertificateExtensions.Add( + new X509EnhancedKeyUsageExtension( + [ + new Oid("1.3.6.1.5.5.7.3.1"), // Server Authentication + new Oid("1.3.6.1.5.5.7.3.2") // Client Authentication + ], + false)); + + // Add Subject Alternative Name + var sanBuilder = new SubjectAlternativeNameBuilder(); + sanBuilder.AddDnsName(CertificateSubject); + request.CertificateExtensions.Add(sanBuilder.Build()); + + // Create certificate (valid for 1 year) + var notBefore = DateTimeOffset.UtcNow; + var notAfter = notBefore.AddYears(1); + + var certificate = request.CreateSelfSigned(notBefore, notAfter); + + // Export and re-import with exportable private key for storage +#if NET9_0_OR_GREATER + var certWithKey = X509CertificateLoader.LoadPkcs12( + certificate.Export(X509ContentType.Pfx), + null, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#else + var certWithKey = new X509Certificate2( + certificate.Export(X509ContentType.Pfx), + (string?)null, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#endif + + // Store in certificate store + using var store = new X509Store(StoreName, StoreLocation); + store.Open(OpenFlags.ReadWrite); + store.Add(certWithKey); + store.Close(); + + Console.WriteLine($"Created and stored self-signed certificate: {CertificateSubject}"); + Console.WriteLine($" Thumbprint: {certWithKey.Thumbprint}"); + Console.WriteLine($" Valid until: {certWithKey.NotAfter:yyyy-MM-dd}"); + + return certWithKey; + } + + /// + /// Removes the self-signed certificate from the store (for cleanup). + /// + public static void RemoveCertificate() + { + using var store = new X509Store(StoreName, StoreLocation); + store.Open(OpenFlags.ReadWrite); + + var certs = store.Certificates.Find( + X509FindType.FindBySubjectName, + CertificateSubject, + validOnly: false); + + foreach (var cert in certs) + { + Console.WriteLine($"Removing certificate: {cert.Thumbprint}"); + store.Remove(cert); + } + + store.Close(); + } +} diff --git a/orleans/TransportLayerSecurity/TLS.Contracts/TLS.Contracts.csproj b/orleans/TransportLayerSecurity/TLS.Contracts/TLS.Contracts.csproj index 0fbe8868e83..6dfa8beddb9 100644 --- a/orleans/TransportLayerSecurity/TLS.Contracts/TLS.Contracts.csproj +++ b/orleans/TransportLayerSecurity/TLS.Contracts/TLS.Contracts.csproj @@ -1,10 +1,11 @@ - net7.0 - enable + net10.0 enable + enable + true - + - \ No newline at end of file + diff --git a/orleans/TransportLayerSecurity/TLS.Server/Program.cs b/orleans/TransportLayerSecurity/TLS.Server/Program.cs index fd0f7c5cf17..98684c24ba5 100644 --- a/orleans/TransportLayerSecurity/TLS.Server/Program.cs +++ b/orleans/TransportLayerSecurity/TLS.Server/Program.cs @@ -1,8 +1,12 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Hosting; -using System.Security.Cryptography.X509Certificates; +using HelloWorld; using HelloWorld.Grains; using HelloWorld.Interfaces; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Security.Cryptography.X509Certificates; + +// Ensure the self-signed certificate exists (creates one if missing) +CertificateHelper.EnsureCertificateExists(); await new HostBuilder() .UseEnvironment(Environments.Development) @@ -12,18 +16,15 @@ builder .UseLocalhostClustering() .UseTls( - StoreName.My, - "fakedomain.faketld", + CertificateHelper.StoreName, + CertificateHelper.CertificateSubject, allowInvalid: isDevelopment, - StoreLocation.CurrentUser, + CertificateHelper.StoreLocation, options => { // In this sample there is only one silo, however if there are multiple silos then the TargetHost must be set // for each connection which is initiated. - options.OnAuthenticateAsClient = (connection, sslOptions) => - { - sslOptions.TargetHost = "fakedomain.faketld"; - }; + options.OnAuthenticateAsClient = (connection, sslOptions) => sslOptions.TargetHost = CertificateHelper.CertificateSubject; if (isDevelopment) { diff --git a/orleans/TransportLayerSecurity/TLS.Server/TLS.Server.csproj b/orleans/TransportLayerSecurity/TLS.Server/TLS.Server.csproj index f4626d1bfff..77bf2307d85 100644 --- a/orleans/TransportLayerSecurity/TLS.Server/TLS.Server.csproj +++ b/orleans/TransportLayerSecurity/TLS.Server/TLS.Server.csproj @@ -1,19 +1,20 @@ - + net10.0 Exe - net7.0 - enable enable + enable true + - - - - + + + + + - \ No newline at end of file + diff --git a/orleans/TransportLayerSecurity/run.cmd b/orleans/TransportLayerSecurity/run.cmd new file mode 100644 index 00000000000..adfa1fb25ec --- /dev/null +++ b/orleans/TransportLayerSecurity/run.cmd @@ -0,0 +1,10 @@ +@echo off +echo === TransportLayerSecurity Sample === +echo This sample demonstrates TLS communication between client and server. +echo. +echo Starting server... +start "TLS Server" cmd /k dotnet run --project "%~dp0TLS.Server\TLS.Server.csproj" +echo Waiting for server to start... +timeout /t 5 /nobreak >nul +echo Starting client... +dotnet run --project "%~dp0TLS.Client\TLS.Client.csproj" diff --git a/orleans/TransportLayerSecurity/run.sh b/orleans/TransportLayerSecurity/run.sh new file mode 100644 index 00000000000..6d1094760d1 --- /dev/null +++ b/orleans/TransportLayerSecurity/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +echo "=== TransportLayerSecurity Sample ===" +echo "This sample demonstrates TLS communication between client and server." +echo "" +echo "Starting server in background..." +dotnet run --project "$SCRIPT_DIR/TLS.Server/TLS.Server.csproj" & +SERVER_PID=$! +echo "Waiting for server to start..." +sleep 5 +echo "Starting client..." +dotnet run --project "$SCRIPT_DIR/TLS.Client/TLS.Client.csproj" +echo "Stopping server..." +kill $SERVER_PID 2>/dev/null diff --git a/orleans/VBHelloWorld/HelloWorld/HelloWorld.csproj b/orleans/VBHelloWorld/HelloWorld/HelloWorld.csproj index 266c44af465..227cb83186d 100644 --- a/orleans/VBHelloWorld/HelloWorld/HelloWorld.csproj +++ b/orleans/VBHelloWorld/HelloWorld/HelloWorld.csproj @@ -1,15 +1,20 @@ - net7.0 + net10.0 Exe + enable + enable + true + + - - - + + + - \ No newline at end of file + diff --git a/orleans/VBHelloWorld/HelloWorld/Program.cs b/orleans/VBHelloWorld/HelloWorld/Program.cs index f9014e6b2a2..94c54fd2d71 100644 --- a/orleans/VBHelloWorld/HelloWorld/Program.cs +++ b/orleans/VBHelloWorld/HelloWorld/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -10,10 +10,7 @@ [assembly: GenerateCodeForDeclaringAssembly(typeof(IHelloGrain))] using var host = new HostBuilder() - .UseOrleans(builder => - { - builder.UseLocalhostClustering(); - }) + .UseOrleans(builder => builder.UseLocalhostClustering()) .UseConsoleLifetime() .Build(); diff --git a/orleans/VBHelloWorld/Interfaces/Interfaces.vbproj b/orleans/VBHelloWorld/Interfaces/Interfaces.vbproj index 9824d90ac83..bdfbfffc062 100644 --- a/orleans/VBHelloWorld/Interfaces/Interfaces.vbproj +++ b/orleans/VBHelloWorld/Interfaces/Interfaces.vbproj @@ -1,9 +1,12 @@ - net7.0 + net10.0 + enable + enable + true - + - \ No newline at end of file + diff --git a/orleans/VBHelloWorld/README.md b/orleans/VBHelloWorld/README.md index fdb98a7bc30..01334fe6d09 100644 --- a/orleans/VBHelloWorld/README.md +++ b/orleans/VBHelloWorld/README.md @@ -29,7 +29,7 @@ With the above attribute in place, the code generator analyzes the Visual Basic ## Sample prerequisites -This sample is written in C# and targets .NET 7.0. It requires the [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) or later. +This sample is written in C# and Visual Basic and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/VBHelloWorld/VBGrains/VBGrains.vbproj b/orleans/VBHelloWorld/VBGrains/VBGrains.vbproj index 2659b5bca50..de06f4eedf5 100644 --- a/orleans/VBHelloWorld/VBGrains/VBGrains.vbproj +++ b/orleans/VBHelloWorld/VBGrains/VBGrains.vbproj @@ -1,11 +1,16 @@ - net7.0 + net10.0 + enable + enable + true + + - + - \ No newline at end of file + diff --git a/orleans/VBHelloWorld/run.cmd b/orleans/VBHelloWorld/run.cmd new file mode 100644 index 00000000000..ad57f599180 --- /dev/null +++ b/orleans/VBHelloWorld/run.cmd @@ -0,0 +1,3 @@ +@echo off +echo Building and running VBHelloWorld sample... +dotnet run --project "%~dp0HelloWorld\HelloWorld.csproj" diff --git a/orleans/VBHelloWorld/run.sh b/orleans/VBHelloWorld/run.sh new file mode 100644 index 00000000000..0ccf9af2f25 --- /dev/null +++ b/orleans/VBHelloWorld/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Building and running VBHelloWorld sample..." +dotnet run --project "$(dirname "$0")/HelloWorld/HelloWorld.csproj" diff --git a/orleans/Voting/App.razor b/orleans/Voting/App.razor index 982c7109b96..cdd1128cb40 100644 --- a/orleans/Voting/App.razor +++ b/orleans/Voting/App.razor @@ -15,7 +15,7 @@ @code { - [Parameter] public string ClientIP { get; set; } + [Parameter] public required string ClientIP { get; set; } protected override Task OnInitializedAsync() { PollService.Initialize(ClientIP); diff --git a/orleans/Voting/Data/PollService.cs b/orleans/Voting/Data/PollService.cs index 9a16b8f0f2f..f3ce0592453 100644 --- a/orleans/Voting/Data/PollService.cs +++ b/orleans/Voting/Data/PollService.cs @@ -5,25 +5,40 @@ namespace Voting.Data; public sealed partial class PollService { private readonly IGrainFactory _grainFactory; - private IUserAgentGrain _userAgentGrain; + private IUserAgentGrain? _userAgentGrain; public PollService(IGrainFactory grainFactory) => _grainFactory = grainFactory; public void Initialize(string clientIp) => _userAgentGrain = _grainFactory.GetGrain(clientIp); - public Task CreatePollAsync(string question, List options) => - _userAgentGrain.CreatePoll(new PollState + public Task CreatePollAsync(string question, List options) + { + if (_userAgentGrain is null) + throw new InvalidOperationException("PollService has not been initialized. Call Initialize first."); + + return _userAgentGrain.CreatePoll(new PollState { Question = question, Options = options.Select(o => (o, 0)).ToList() }); + } - public Task<(PollState Results, bool Voted)> GetPollResultsAsync(string pollId) => - _userAgentGrain.GetPollResults(pollId); + public Task<(PollState Results, bool Voted)> GetPollResultsAsync(string pollId) + { + if (_userAgentGrain is null) + throw new InvalidOperationException("PollService has not been initialized. Call Initialize first."); + + return _userAgentGrain.GetPollResults(pollId); + } - public Task AddVoteAsync(string pollId, int optionId) => - _userAgentGrain.AddVote(pollId, optionId); + public Task AddVoteAsync(string pollId, int optionId) + { + if (_userAgentGrain is null) + throw new InvalidOperationException("PollService has not been initialized. Call Initialize first."); + + return _userAgentGrain.AddVote(pollId, optionId); + } public async ValueTask WatchPoll(string pollId, IPollWatcher watcherObject) { diff --git a/orleans/Voting/Grains/IPollGrain.cs b/orleans/Voting/Grains/IPollGrain.cs index 99d5e25241b..674949f9bf5 100644 --- a/orleans/Voting/Grains/IPollGrain.cs +++ b/orleans/Voting/Grains/IPollGrain.cs @@ -12,6 +12,6 @@ public interface IPollGrain : IGrainWithStringKey [GenerateSerializer] public class PollState { - [Id(0)] public string Question { get; set; } - [Id(1)] public List<(string Option, int Votes)> Options { get; set; } + [Id(0)] public required string Question { get; set; } + [Id(1)] public required List<(string Option, int Votes)> Options { get; set; } } diff --git a/orleans/Voting/Helpers/ObserverManager.cs b/orleans/Voting/Helpers/ObserverManager.cs index 3d99373c2ef..28fb226c6d5 100644 --- a/orleans/Voting/Helpers/ObserverManager.cs +++ b/orleans/Voting/Helpers/ObserverManager.cs @@ -1,6 +1,6 @@ namespace Grains; -public class ObserverManager +public class ObserverManager where TObserver : notnull { private readonly Dictionary _observers = new(); private readonly TimeSpan _expiration; diff --git a/orleans/Voting/Helpers/ThrottlingException.cs b/orleans/Voting/Helpers/ThrottlingException.cs index 647ff2704bd..59a6bd9f390 100644 --- a/orleans/Voting/Helpers/ThrottlingException.cs +++ b/orleans/Voting/Helpers/ThrottlingException.cs @@ -1,11 +1,9 @@ -using System.Runtime.Serialization; - namespace VotingContract; [GenerateSerializer] public class ThrottlingException : Exception { + public ThrottlingException() { } public ThrottlingException(string message) : base(message) { } public ThrottlingException(string message, Exception innerException) : base(message, innerException) { } - protected ThrottlingException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/orleans/Voting/Pages/Error.cshtml.cs b/orleans/Voting/Pages/Error.cshtml.cs index 93e56c69cc6..d87440dde8d 100644 --- a/orleans/Voting/Pages/Error.cshtml.cs +++ b/orleans/Voting/Pages/Error.cshtml.cs @@ -8,7 +8,7 @@ namespace Voting.Pages; [IgnoreAntiforgeryToken] public class ErrorModel : PageModel { - public string RequestId { get; set; } + public string? RequestId { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); diff --git a/orleans/Voting/Pages/Poll.razor b/orleans/Voting/Pages/Poll.razor index a3bec707ad9..f7baabf1eab 100644 --- a/orleans/Voting/Pages/Poll.razor +++ b/orleans/Voting/Pages/Poll.razor @@ -18,10 +18,10 @@ else if (!_loaded) } else if (!_voted) { -

@_results.Question

+

@_results?.Question


- @foreach (var (index, option) in _results.Options.Select((value, index) => (index, value))) + @foreach (var (index, option) in _results?.Options?.Select((value, index) => (index, value)) ?? []) {
@@ -36,7 +36,7 @@ else if (!_voted) } else { -

@_results.Question

+

@_results?.Question


@foreach (var (option, votes, percentage) in CalculateResults()) @@ -58,15 +58,15 @@ else } @code { - [Parameter] public string PollId { get; set; } - private MyPollWatcher _watcher; - private IAsyncDisposable _subscription; + [Parameter] public required string PollId { get; set; } + private MyPollWatcher? _watcher; + private IAsyncDisposable? _subscription; private bool _loaded; private bool _voted; - private string _errorMessage; + private string _errorMessage = string.Empty; private Guid ownerKey = Guid.NewGuid(); - private PollState _results; + private PollState? _results; protected override async Task OnInitializedAsync() { diff --git a/orleans/Voting/Pages/PollEditor.razor b/orleans/Voting/Pages/PollEditor.razor index 190e9eacef4..47cd40559a1 100644 --- a/orleans/Voting/Pages/PollEditor.razor +++ b/orleans/Voting/Pages/PollEditor.razor @@ -18,7 +18,7 @@ @foreach (var (index, option) in options.Select((name, index) => (index, name))) {
- +
} @@ -31,8 +31,8 @@ private Guid ownerKey = Guid.NewGuid(); private List options = new (); - private string question; - private string newOption; + private string question = string.Empty; + private string newOption = string.Empty; protected override async Task OnInitializedAsync() { diff --git a/orleans/Voting/Pages/_Host.cshtml b/orleans/Voting/Pages/_Host.cshtml index c53e75fe11f..3a02a5c5248 100644 --- a/orleans/Voting/Pages/_Host.cshtml +++ b/orleans/Voting/Pages/_Host.cshtml @@ -6,4 +6,4 @@ var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString(); } - + diff --git a/orleans/Voting/Program.cs b/orleans/Voting/Program.cs index 4f4bacfc3b2..7e5dae752d7 100644 --- a/orleans/Voting/Program.cs +++ b/orleans/Voting/Program.cs @@ -1,27 +1,11 @@ +using Orleans.Dashboard; using Voting.Data; var builder = WebApplication.CreateBuilder(args); -builder.Host.UseOrleans((ctx, orleansBuilder) => -{ - if (ctx.HostingEnvironment.IsDevelopment()) - { - // During development time, we don't want to have to deal with - // storage emulators or other dependencies. Just "Hit F5" to run. - orleansBuilder - .UseLocalhostClustering() - .AddMemoryGrainStorage("votes"); - } - else - { - // In Kubernetes, we use environment variables and the pod manifest - //orleansBuilder.UseKubernetesHosting(); - - // Use Redis for clustering & persistence - //var redisAddress = $"{Environment.GetEnvironmentVariable("REDIS")}:6379"; - //orleansBuilder.UseRedisClustering(options => options.ConnectionString = redisAddress); - //orleansBuilder.AddRedisGrainStorage("votes", options => options.ConnectionString = redisAddress); - } -}); +builder.Host.UseOrleans((ctx, orleansBuilder) => orleansBuilder + .UseLocalhostClustering() + .AddMemoryGrainStorage("votes") + .AddDashboard()); // Add services to the container. builder.Services.AddRazorPages(); @@ -42,6 +26,9 @@ app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); + app.MapBlazorHub(); +app.MapRazorPages(); +app.MapOrleansDashboard("/dashboard"); app.MapFallbackToPage("/_Host"); app.Run(); diff --git a/orleans/Voting/README.md b/orleans/Voting/README.md index ea20ce42a22..f4735611308 100644 --- a/orleans/Voting/README.md +++ b/orleans/Voting/README.md @@ -22,7 +22,7 @@ The Web app sends HTTP requests which are handled by ASP.NET Core MVC controller ## Sample prerequisites -This sample is written in C# and targets .NET 7.0. It requires the [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) or later. +This sample is written in C# and targets .NET 10. It requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later. ## Building the sample diff --git a/orleans/Voting/Shared/NavMenu.razor b/orleans/Voting/Shared/NavMenu.razor index a201e99af25..6da717e1daf 100644 --- a/orleans/Voting/Shared/NavMenu.razor +++ b/orleans/Voting/Shared/NavMenu.razor @@ -25,7 +25,7 @@ @code { private bool collapseNavMenu = true; - private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; + private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; private void ToggleNavMenu() { diff --git a/orleans/Voting/Voting.csproj b/orleans/Voting/Voting.csproj index f00afd546fb..efe02441cd3 100644 --- a/orleans/Voting/Voting.csproj +++ b/orleans/Voting/Voting.csproj @@ -1,15 +1,15 @@ - - net7.0 + net10.0 enable + enable + true - - - + + + diff --git a/orleans/Voting/run.cmd b/orleans/Voting/run.cmd new file mode 100644 index 00000000000..9536012eaa7 --- /dev/null +++ b/orleans/Voting/run.cmd @@ -0,0 +1,5 @@ +@echo off +echo Building and running Voting sample... +echo Open https://localhost:7178 in your browser +echo Orleans Dashboard available at https://localhost:7178/dashboard/ +dotnet run --project "%~dp0Voting.csproj" diff --git a/orleans/Voting/run.sh b/orleans/Voting/run.sh new file mode 100644 index 00000000000..0c191695f25 --- /dev/null +++ b/orleans/Voting/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "Building and running Voting sample..." +echo "Open https://localhost:7178 in your browser" +echo "Orleans Dashboard available at https://localhost:7178/dashboard/" +dotnet run --project "$(dirname "$0")/Voting.csproj" From 9d2bf73b6803cb0e914e3369c9120b67195f6165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:44:30 +0000 Subject: [PATCH 08/11] Bump actions/setup-node from 6.1.0 to 6.2.0 in the dotnet group Bumps the dotnet group with 1 update: [actions/setup-node](https://github.com/actions/setup-node). Updates `actions/setup-node` from 6.1.0 to 6.2.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/395ad3262231945c25e8478fd5baf05154b1d79f...6044e13b5dc448c55e2357c09f80417699197238) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dotnet ... Signed-off-by: dependabot[bot] --- .github/workflows/markdownlint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 167c3496075..bdd76b3c25a 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #@v2 - name: Use Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #@v1 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #@v1 with: node-version: 12.x - name: Run Markdownlint From 91b99469a3247d11ca486adfeb053503d9da4929 Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Wed, 21 Jan 2026 21:16:53 +0000 Subject: [PATCH 09/11] Orleans JournaledGrain sample (#7029) * Orleans JournaledGrain sample * Add timestamp to projection type * Ensure projection has been materialized before returning list * Add support for navigating a todo lists history * Allow todo lists to have different names than id's - change registry grain to be state-based journaled * Introduce constant for todo list registry id * RefreshNow is not needed when OnActivateAsync is not overridden * Update to .NET 10, Orleans 10, Aspire 13.1.0 --------- Co-authored-by: Reuben Bond --- .../JournaledTodoList.AppHost.csproj | 24 +++ .../JournaledTodoList.AppHost/Program.cs | 27 ++++ .../Properties/launchSettings.json | 29 ++++ .../appsettings.Development.json | 8 + .../appsettings.json | 9 ++ .../Extensions.cs | 122 +++++++++++++++ .../JournaledTodoList.ServiceDefaults.csproj | 22 +++ .../Components/App.razor | 23 +++ .../Components/Layout/MainLayout.razor | 24 +++ .../Components/Layout/MainLayout.razor.css | 20 +++ .../Components/Layout/NavBar.razor | 17 +++ .../Components/Layout/NavBar.razor.cs | 28 ++++ .../Components/Pages/Error.razor | 36 +++++ .../Components/Pages/HomePage.razor | 24 +++ .../Components/Pages/HomePage.razor.cs | 40 +++++ .../Components/Pages/TodoListPage.razor | 102 +++++++++++++ .../Components/Pages/TodoListPage.razor.cs | 107 ++++++++++++++ .../Components/Routes.razor | 12 ++ .../Components/_Imports.razor | 12 ++ .../JournaledTodoList.WebApp/Constants.cs | 6 + .../Grains/Events/TodoItemAdded.cs | 8 + .../Grains/Events/TodoItemRemoved.cs | 7 + .../Grains/Events/TodoItemToggled.cs | 7 + .../Grains/Events/TodoItemUpdated.cs | 7 + .../Grains/Events/TodoListEvent.cs | 7 + .../Grains/Events/TodoListNameChanged.cs | 7 + .../Grains/ITodoListGrain.cs | 23 +++ .../Grains/ITodoListRegistryGrain.cs | 14 ++ .../Grains/ITodoListRegistryObserver.cs | 8 + .../Grains/TodoItem.cs | 4 + .../Grains/TodoList.cs | 9 ++ .../Grains/TodoListGrain.cs | 139 ++++++++++++++++++ .../Grains/TodoListReference.cs | 4 + .../Grains/TodoListRegistryGrain.cs | 83 +++++++++++ .../JournaledTodoList.WebApp.csproj | 22 +++ .../JournaledTodoList.WebApp/Program.cs | 39 +++++ .../Properties/launchSettings.json | 23 +++ .../Services/TodoListService.cs | 96 ++++++++++++ .../appsettings.Development.json | 8 + .../JournaledTodoList.WebApp/appsettings.json | 9 ++ .../JournaledTodoList.WebApp/wwwroot/app.css | 38 +++++ .../JournaledTodoList/JournaledTodoList.slnx | 5 + 42 files changed, 1259 insertions(+) create mode 100644 orleans/JournaledTodoList/JournaledTodoList.AppHost/JournaledTodoList.AppHost.csproj create mode 100644 orleans/JournaledTodoList/JournaledTodoList.AppHost/Program.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.AppHost/Properties/launchSettings.json create mode 100644 orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.Development.json create mode 100644 orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.json create mode 100644 orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/Extensions.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/JournaledTodoList.ServiceDefaults.csproj create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/App.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor.css create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/Error.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Routes.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/_Imports.razor create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Constants.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemAdded.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemRemoved.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemToggled.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemUpdated.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListEvent.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListNameChanged.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListGrain.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryGrain.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryObserver.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoItem.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoList.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListGrain.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListReference.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListRegistryGrain.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/JournaledTodoList.WebApp.csproj create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Program.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Properties/launchSettings.json create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/Services/TodoListService.cs create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.Development.json create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.json create mode 100644 orleans/JournaledTodoList/JournaledTodoList.WebApp/wwwroot/app.css create mode 100644 orleans/JournaledTodoList/JournaledTodoList.slnx diff --git a/orleans/JournaledTodoList/JournaledTodoList.AppHost/JournaledTodoList.AppHost.csproj b/orleans/JournaledTodoList/JournaledTodoList.AppHost/JournaledTodoList.AppHost.csproj new file mode 100644 index 00000000000..236e9f6ed20 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.AppHost/JournaledTodoList.AppHost.csproj @@ -0,0 +1,24 @@ + + + + + + Exe + net10.0 + enable + enable + true + 1df8a68d-9c06-4d9a-98ed-f735876b11f7 + + + + + + + + + + + + + diff --git a/orleans/JournaledTodoList/JournaledTodoList.AppHost/Program.cs b/orleans/JournaledTodoList/JournaledTodoList.AppHost/Program.cs new file mode 100644 index 00000000000..5f3e032d932 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.AppHost/Program.cs @@ -0,0 +1,27 @@ +var builder = DistributedApplication.CreateBuilder(args); + +// Add the resources which you will use for Orleans clustering and +// grain state storage. +var sessionStorage = builder.AddAzureStorage("sessionStorage") + .RunAsEmulator(); +var persistentStorage = builder.AddAzureStorage("persistentStorage") + .RunAsEmulator(config => config.WithLifetime(ContainerLifetime.Persistent)); + +var clusteringTable = sessionStorage.AddTables("clustering"); +var grainStorage = persistentStorage.AddBlobs("grain-state"); + +// Add the Orleans resource to the Aspire DistributedApplication +// builder, then configure it with Azure Table Storage for clustering +// and Azure Blob Storage for grain storage. +var orleans = builder.AddOrleans("default") + .WithClustering(clusteringTable) + .WithGrainStorage("Default", grainStorage); + +builder.AddProject("webapp") + .WithReference(orleans) + .WithReplicas(3) + .WithExternalHttpEndpoints() + .WaitFor(clusteringTable) + .WaitFor(grainStorage); + +builder.Build().Run(); diff --git a/orleans/JournaledTodoList/JournaledTodoList.AppHost/Properties/launchSettings.json b/orleans/JournaledTodoList/JournaledTodoList.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000000..71382477d85 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17222;http://localhost:15135", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21055", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22157" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15135", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19238", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20164" + } + } + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.Development.json b/orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.json b/orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.json new file mode 100644 index 00000000000..31c092aa450 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/Extensions.cs b/orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000000..14aa84b9b89 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/Extensions.cs @@ -0,0 +1,122 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation() + .AddMeter("Microsoft.Orleans"); + }) + .WithTracing(tracing => + { + tracing.AddSource("Microsoft.Orleans.Runtime"); + tracing.AddSource("Microsoft.Orleans.Application"); + + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/JournaledTodoList.ServiceDefaults.csproj b/orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/JournaledTodoList.ServiceDefaults.csproj new file mode 100644 index 00000000000..b71d3c77627 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.ServiceDefaults/JournaledTodoList.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/App.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/App.razor new file mode 100644 index 00000000000..b46dac45008 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/App.razor @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor new file mode 100644 index 00000000000..dada90be88b --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor @@ -0,0 +1,24 @@ +@inherits LayoutComponentBase + +
+
+ +
+ +
+ @Body +
+
diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor.css b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor.css new file mode 100644 index 00000000000..60cec92d5e5 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/MainLayout.razor.css @@ -0,0 +1,20 @@ +#blazor-error-ui { + color-scheme: light only; + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + box-sizing: border-box; + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor new file mode 100644 index 00000000000..14e4d93f3e9 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor @@ -0,0 +1,17 @@ + diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor.cs new file mode 100644 index 00000000000..5fd6c2ce8ae --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Layout/NavBar.razor.cs @@ -0,0 +1,28 @@ +using System.Collections.Immutable; +using JournaledTodoList.WebApp.Grains; +using JournaledTodoList.WebApp.Services; + +namespace JournaledTodoList.WebApp.Components.Layout; + +public partial class NavBar(TodoListService todoListService) : ITodoListRegistryObserver, IDisposable +{ + private IDisposable? subscription; + + private ImmutableArray TodoLists { get; set; } = []; + + protected override async Task OnInitializedAsync() + { + subscription = await todoListService.SubscribeAsync(this); + } + + public void Dispose() + { + subscription?.Dispose(); + } + + async Task ITodoListRegistryObserver.OnTodoListsChanged(ImmutableArray todoLists) => await InvokeAsync(() => + { + TodoLists = todoLists; + StateHasChanged(); + }); +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/Error.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/Error.razor new file mode 100644 index 00000000000..576cc2d2f4d --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor new file mode 100644 index 00000000000..05ba5a10410 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor @@ -0,0 +1,24 @@ +@page "/" +Todo Lists + +
+

My Todo Lists

+ +
+
+
+ + +
+
+
+ +
+ @foreach (var list in TodoLists) + { + + @list.Name + + } +
+
diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor.cs new file mode 100644 index 00000000000..f8eb7cb2027 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/HomePage.razor.cs @@ -0,0 +1,40 @@ +using System.Collections.Immutable; +using JournaledTodoList.WebApp.Grains; +using JournaledTodoList.WebApp.Services; + +namespace JournaledTodoList.WebApp.Components.Pages; + +public partial class HomePage(TodoListService todoListService) : ITodoListRegistryObserver, IDisposable +{ + private IDisposable? subscription; + private string newListName = ""; + + private ImmutableArray TodoLists { get; set; } = []; + + protected override async Task OnInitializedAsync() + { + subscription = await todoListService.SubscribeAsync(this); + } + + public void Dispose() + { + subscription?.Dispose(); + } + + async Task ITodoListRegistryObserver.OnTodoListsChanged(ImmutableArray todoLists) => await InvokeAsync(() => + { + TodoLists = todoLists; + StateHasChanged(); + }); + + private async Task CreateNewList(string listName) + { + if (string.IsNullOrWhiteSpace(listName) || TodoLists.Any(x => x.Name == listName)) + { + return; + } + + await todoListService.CreateTodoListAsync(listName); + newListName = ""; + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor new file mode 100644 index 00000000000..11657deffd0 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor @@ -0,0 +1,102 @@ +@page "/todolist/{ListId}" +Todo List - @ListId +
+
+
+

Todo List: @ListId

+
+
+

Event History

+
+
+ +
+
+ @if (IsViewingHistory) + { + + } + else + { +
+
+ + +
+
+ } +
+ +
+ +
+
+ +
+
+ @if (todoList is null) + { +
Loading...
+ } + else if (todoList.Items.IsEmpty) + { +
No items in this list yet. Add one above!
+ } + else + { +
+ @foreach (var item in todoList.Items) + { +
+
+ +
+ + +
+ } +
+ } +
+
+ @if (history.IsDefault) + { +
Loading history...
+ } + else if (history.IsEmpty) + { +
No history yet.
+ } + else + { +
+ @foreach (var evt in history.OrderByDescending(e => e.Timestamp)) + { + + } +
+ } +
+
+
diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor.cs new file mode 100644 index 00000000000..81ffdae56cf --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Pages/TodoListPage.razor.cs @@ -0,0 +1,107 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using JournaledTodoList.WebApp.Grains; +using JournaledTodoList.WebApp.Grains.Events; +using JournaledTodoList.WebApp.Services; +using Microsoft.AspNetCore.Components; + +namespace JournaledTodoList.WebApp.Components.Pages; + +public partial class TodoListPage(TodoListService todoService) +{ + private string newItemTitle = ""; + private TodoList? todoList; + private ImmutableArray history; + private DateTimeOffset? currentViewTimestamp; + + [MemberNotNullWhen(true, nameof(currentViewTimestamp))] + private bool IsViewingHistory => !history.IsDefaultOrEmpty && currentViewTimestamp < history[^1].Timestamp; + + [Parameter, EditorRequired] + public required string ListId { get; set; } + + protected override async Task OnParametersSetAsync() + { + if (todoList?.Name != ListId) + { + currentViewTimestamp = null; + todoList = null; + await LoadTodoList(); + } + } + + private async Task LoadTodoList() + { + todoList = await todoService.GetTodoListAsync(ListId); + history = await todoService.GetTodoListHistoryAsync(ListId); + } + + private async Task AddItem() + { + if (string.IsNullOrWhiteSpace(newItemTitle) || IsViewingHistory) + { + return; + } + + await todoService.AddTodoItemAsync(ListId, newItemTitle); + newItemTitle = ""; + await LoadTodoList(); + } + + private async Task UpdateItem(int itemId, string? title) + { + if (string.IsNullOrWhiteSpace(title) || IsViewingHistory) + { + return; + } + + await todoService.UpdateTodoItemAsync(ListId, itemId, title); + await LoadTodoList(); + } + + private async Task ToggleItem(int itemId) + { + if (IsViewingHistory) + { + return; + } + + await todoService.ToggleTodoItemAsync(ListId, itemId); + await LoadTodoList(); + } + + private async Task RemoveItem(int itemId) + { + if (IsViewingHistory) + { + return; + } + + await todoService.RemoveTodoItemAsync(ListId, itemId); + await LoadTodoList(); + } + + private async Task ViewAtTimestamp(DateTimeOffset timestamp) + { + if (timestamp == history[^1].Timestamp) + { + currentViewTimestamp = null; + } + else + { + currentViewTimestamp = timestamp; + todoList = await todoService.GetTodoListAtTimestampAsync(ListId, timestamp); + } + } + + private async Task ReturnToCurrentVersion() + { + currentViewTimestamp = null; + await LoadTodoList(); + } + + private bool IsCurrentHistoryItem(TodoListEvent item) + => currentViewTimestamp.HasValue + ? item.Timestamp == currentViewTimestamp + : history[^1] == item; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Routes.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Routes.razor new file mode 100644 index 00000000000..6d6b5d6b258 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/Routes.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/_Imports.razor b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/_Imports.razor new file mode 100644 index 00000000000..1fa622ca8b7 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Components/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using JournaledTodoList.WebApp +@using JournaledTodoList.WebApp.Components +@using JournaledTodoList.WebApp.Grains +@using JournaledTodoList.WebApp.Services diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Constants.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Constants.cs new file mode 100644 index 00000000000..fc242367cb9 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Constants.cs @@ -0,0 +1,6 @@ +public static class Constants +{ + public const string StateStorageProviderName = "StateStorage"; + + public const string TodoListRegistryId = "registry"; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemAdded.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemAdded.cs new file mode 100644 index 00000000000..62a70b369d4 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemAdded.cs @@ -0,0 +1,8 @@ +namespace JournaledTodoList.WebApp.Grains.Events; + +[GenerateSerializer, Immutable] +public record class TodoItemAdded(int ItemId, DateTimeOffset Timestamp, string Title) + : TodoListEvent(Timestamp) +{ + public override string GetDescription() => $"Added item {ItemId}: {Title}"; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemRemoved.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemRemoved.cs new file mode 100644 index 00000000000..04f1559812c --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemRemoved.cs @@ -0,0 +1,7 @@ +namespace JournaledTodoList.WebApp.Grains.Events; + +[GenerateSerializer, Immutable] +public record class TodoItemRemoved(int ItemId, DateTimeOffset Timestamp) : TodoListEvent(Timestamp) +{ + public override string GetDescription() => $"Removed item {ItemId}"; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemToggled.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemToggled.cs new file mode 100644 index 00000000000..b7a915048ee --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemToggled.cs @@ -0,0 +1,7 @@ +namespace JournaledTodoList.WebApp.Grains.Events; + +[GenerateSerializer, Immutable] +public record class TodoItemToggled(int ItemId, DateTimeOffset Timestamp) : TodoListEvent(Timestamp) +{ + public override string GetDescription() => $"Toggled completion status of item {ItemId}"; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemUpdated.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemUpdated.cs new file mode 100644 index 00000000000..da235161aa7 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoItemUpdated.cs @@ -0,0 +1,7 @@ +namespace JournaledTodoList.WebApp.Grains.Events; + +[GenerateSerializer, Immutable] +public record class TodoItemUpdated(int ItemId, DateTimeOffset Timestamp, string Title) : TodoListEvent(Timestamp) +{ + public override string GetDescription() => $"Updated item {ItemId}: {Title}"; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListEvent.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListEvent.cs new file mode 100644 index 00000000000..3407ac361ee --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListEvent.cs @@ -0,0 +1,7 @@ +namespace JournaledTodoList.WebApp.Grains.Events; + +[GenerateSerializer, Immutable] +public abstract record class TodoListEvent(DateTimeOffset Timestamp) +{ + public abstract string GetDescription(); +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListNameChanged.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListNameChanged.cs new file mode 100644 index 00000000000..fe90e337917 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/Events/TodoListNameChanged.cs @@ -0,0 +1,7 @@ +namespace JournaledTodoList.WebApp.Grains.Events; + +[GenerateSerializer, Immutable] +public record class TodoListNameChanged(string Name, DateTimeOffset Timestamp) : TodoListEvent(Timestamp) +{ + public override string GetDescription() => $"Todo list name changed to {Name}"; +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListGrain.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListGrain.cs new file mode 100644 index 00000000000..5be8886df9a --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListGrain.cs @@ -0,0 +1,23 @@ +using System.Collections.Immutable; +using JournaledTodoList.WebApp.Grains.Events; + +namespace JournaledTodoList.WebApp.Grains; + +public interface ITodoListGrain : IGrainWithStringKey +{ + Task GetTodoListAsync(); + + Task GetTodoListAtTimestampAsync(DateTimeOffset timestamp); + + Task AddTodoItemAsync(string title); + + Task UpdateTodoItemAsync(int id, string title); + + Task ToggleTodoItemAsync(int id); + + Task RemoveTodoItemAsync(int id); + + Task SetNameAsync(string listName); + + Task> GetHistoryAsync(); +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryGrain.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryGrain.cs new file mode 100644 index 00000000000..5b285e0d820 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryGrain.cs @@ -0,0 +1,14 @@ +using System.Collections.Immutable; + +namespace JournaledTodoList.WebApp.Grains; + +public interface ITodoListRegistryGrain : IGrainWithStringKey +{ + Task RegisterTodoListAsync(TodoListReference todoListReference); + + Task> GetAllTodoListsAsync(); + + Task Subscribe(ITodoListRegistryObserver observer); + + Task Unsubscribe(ITodoListRegistryObserver observer); +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryObserver.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryObserver.cs new file mode 100644 index 00000000000..cda0f551338 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/ITodoListRegistryObserver.cs @@ -0,0 +1,8 @@ +using System.Collections.Immutable; + +namespace JournaledTodoList.WebApp.Grains; + +public interface ITodoListRegistryObserver : IGrainObserver +{ + Task OnTodoListsChanged(ImmutableArray todoLists); +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoItem.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoItem.cs new file mode 100644 index 00000000000..7123804804a --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoItem.cs @@ -0,0 +1,4 @@ +namespace JournaledTodoList.WebApp.Grains; + +[GenerateSerializer, Immutable] +public record class TodoItem(int Id, string Title, bool IsCompleted); diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoList.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoList.cs new file mode 100644 index 00000000000..536fd119205 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoList.cs @@ -0,0 +1,9 @@ +using System.Collections.Immutable; + +namespace JournaledTodoList.WebApp.Grains; + +[GenerateSerializer, Immutable] +public record class TodoList( + string Name, + ImmutableArray Items, + DateTimeOffset Timestamp); diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListGrain.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListGrain.cs new file mode 100644 index 00000000000..922e6577291 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListGrain.cs @@ -0,0 +1,139 @@ +using System.Collections.Immutable; +using JournaledTodoList.WebApp.Grains.Events; +using Orleans.EventSourcing; + +namespace JournaledTodoList.WebApp.Grains; + +public sealed class TodoListGrain : JournaledGrain, ITodoListGrain +{ + public async Task> GetHistoryAsync() + { + var events = await RetrieveConfirmedEvents(0, Version); + return events.ToImmutableArray(); + } + + public async Task GetTodoListAtTimestampAsync(DateTimeOffset timestamp) + { + // Get all events up to the current version + var allEvents = await GetHistoryAsync(); + + // Create a fresh projection and apply the filtered events + var historicalProjection = new TodoListProjection(); + foreach (var evt in allEvents.Where(e => e.Timestamp <= timestamp)) + { + TransitionState(historicalProjection, evt); + } + + // Only return a TodoList if there were events that matched the timestamp. + return historicalProjection.Timestamp > DateTimeOffset.MinValue + ? new TodoList( + Name: this.GetPrimaryKeyString(), + Items: historicalProjection.Items.Values.OrderBy(x => x.Id).ToImmutableArray(), + Timestamp: historicalProjection.Timestamp) + : null; + } + + public async Task GetTodoListAsync() + { + var list = new TodoList( + Name: this.GetPrimaryKeyString(), + Items: State.Items.Values.OrderBy(x => x.Id).ToImmutableArray(), + Timestamp: State.Timestamp); + + return list; + } + + public async Task AddTodoItemAsync(string title) + { + var evt = new TodoItemAdded( + Version, + DateTimeOffset.UtcNow, + title); + + RaiseEvent(evt); + await ConfirmEvents(); + } + + public async Task UpdateTodoItemAsync(int id, string title) + { + var evt = new TodoItemUpdated(id, DateTimeOffset.UtcNow, title); + RaiseEvent(evt); + await ConfirmEvents(); + } + + public async Task ToggleTodoItemAsync(int id) + { + var evt = new TodoItemToggled(id, DateTimeOffset.UtcNow); + RaiseEvent(evt); + await ConfirmEvents(); + } + + public async Task RemoveTodoItemAsync(int id) + { + var evt = new TodoItemRemoved(id, DateTimeOffset.UtcNow); + RaiseEvent(evt); + await ConfirmEvents(); + } + + public async Task SetNameAsync(string listName) + { + var evt = new TodoListNameChanged(listName, DateTimeOffset.UtcNow); + RaiseEvent(evt); + await ConfirmEvents(); + + // Publish list with new name to todo list registry. + // The registry only cares about the id and name of the list, + // so this is the only place where we need to interact with the registry. + var registry = GrainFactory.GetGrain(Constants.TodoListRegistryId); + await registry.RegisterTodoListAsync(new TodoListReference(this.GetPrimaryKeyString(), listName)); + } + + /// + /// The state container for . + /// NOTE: this has to be a mutable object. + /// + public sealed class TodoListProjection + { + public Dictionary Items { get; set; } = []; + + public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.MinValue; + + public string Name { get; set; } = string.Empty; + + public void Apply(TodoItemAdded added) + { + Items.Add(added.ItemId, new TodoItem(added.ItemId, added.Title, false)); + Timestamp = added.Timestamp; + } + + public void Apply(TodoItemUpdated updated) + { + if (Items.TryGetValue(updated.ItemId, out var item)) + { + Items[updated.ItemId] = item with { Title = updated.Title }; + } + Timestamp = updated.Timestamp; + } + + public void Apply(TodoItemToggled toggled) + { + if (Items.TryGetValue(toggled.ItemId, out var item)) + { + Items[toggled.ItemId] = item with { IsCompleted = !item.IsCompleted }; + } + Timestamp = toggled.Timestamp; + } + + public void Apply(TodoItemRemoved removed) + { + Items.Remove(removed.ItemId); + Timestamp = removed.Timestamp; + } + + public void Apply(TodoListNameChanged nameChanged) + { + Name = nameChanged.Name; + Timestamp = nameChanged.Timestamp; + } + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListReference.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListReference.cs new file mode 100644 index 00000000000..6847523b53c --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListReference.cs @@ -0,0 +1,4 @@ +namespace JournaledTodoList.WebApp.Grains; + +[GenerateSerializer, Immutable] +public record class TodoListReference(string Id, string Name); diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListRegistryGrain.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListRegistryGrain.cs new file mode 100644 index 00000000000..36492a516a4 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Grains/TodoListRegistryGrain.cs @@ -0,0 +1,83 @@ +using System.Collections.Immutable; +using Orleans.EventSourcing; +using Orleans.Providers; +using Orleans.Utilities; + +namespace JournaledTodoList.WebApp.Grains; + +/// +/// A "state" based Journaled Grain. It does not save the events, just the state. +/// +[LogConsistencyProvider(ProviderName = Constants.StateStorageProviderName)] +public sealed class TodoListRegistryGrain(ILogger logger) + : JournaledGrain + , ITodoListRegistryGrain +{ + private readonly ObserverManager observers = new( + TimeSpan.FromMinutes(5), + logger); + + public async Task RegisterTodoListAsync(TodoListReference todoListReference) + { + if (State.TodoLists.Contains(todoListReference)) + { + return; + } + + RaiseEvent(todoListReference); + await ConfirmEvents(); + await NotifyObservers(); + } + + // Instead of having Apply methods in TodoListRegistry, we can override + // the TransitionState method and update the state here. + protected override void TransitionState(TodoListRegistry state, TodoListReference @event) + { + // If there is an existing item with the same Id, replace it with the new item, + // ensuring the order of the items are kept. + var existingList = state.TodoLists.FirstOrDefault(x => x.Id == @event.Id); + + if (existingList is not null) + { + state.TodoLists = state.TodoLists.Replace(existingList, @event); + } + else + { + state.TodoLists = state.TodoLists.Add(@event); + } + } + + public Task> GetAllTodoListsAsync() + { + return Task.FromResult(State.TodoLists); + } + + public Task Subscribe(ITodoListRegistryObserver observer) + { + observers.Subscribe(observer, observer); + observer.OnTodoListsChanged(State.TodoLists); + return Task.CompletedTask; + } + + public Task Unsubscribe(ITodoListRegistryObserver observer) + { + observers.Unsubscribe(observer); + return Task.CompletedTask; + } + + public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) + { + observers.Clear(); + return base.OnDeactivateAsync(reason, cancellationToken); + } + + private Task NotifyObservers() + => observers.Notify(observer => observer.OnTodoListsChanged(State.TodoLists)); + + [GenerateSerializer, Immutable] + public sealed class TodoListRegistry + { + [Id(0)] + public ImmutableArray TodoLists { get; set; } = []; + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/JournaledTodoList.WebApp.csproj b/orleans/JournaledTodoList/JournaledTodoList.WebApp/JournaledTodoList.WebApp.csproj new file mode 100644 index 00000000000..d25187977e3 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/JournaledTodoList.WebApp.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Program.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Program.cs new file mode 100644 index 00000000000..676c53593d4 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Program.cs @@ -0,0 +1,39 @@ +using JournaledTodoList.WebApp.Components; +using JournaledTodoList.WebApp.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); +builder.AddKeyedAzureTableServiceClient("clustering"); +builder.AddKeyedAzureBlobServiceClient("grain-state"); +builder.UseOrleans(siloBuilder => +{ + siloBuilder.AddLogStorageBasedLogConsistencyProviderAsDefault(); + siloBuilder.AddStateStorageBasedLogConsistencyProvider(name: Constants.StateStorageProviderName); +}); +builder.Services.AddScoped(); + +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseAntiforgery(); + +app.MapStaticAssets(); +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.Run(); diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Properties/launchSettings.json b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Properties/launchSettings.json new file mode 100644 index 00000000000..3403eb20fed --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5169", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7111;http://localhost:5169", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/Services/TodoListService.cs b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Services/TodoListService.cs new file mode 100644 index 00000000000..15914b3315d --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/Services/TodoListService.cs @@ -0,0 +1,96 @@ +using System.Collections.Immutable; +using System.Web; +using JournaledTodoList.WebApp.Grains; +using JournaledTodoList.WebApp.Grains.Events; + +namespace JournaledTodoList.WebApp.Services; + +public class TodoListService(IGrainFactory grainFactory) +{ + public async Task SubscribeAsync(ITodoListRegistryObserver observer) + { + var registryGrain = grainFactory.GetGrain(Constants.TodoListRegistryId); + var observerRef = grainFactory.CreateObjectReference(observer); + await registryGrain.Subscribe(observerRef); + return new Subscription(observerRef, registryGrain); + } + + public Task> GetTodoListReferencesAsync() + { + var registryGrain = grainFactory.GetGrain(Constants.TodoListRegistryId); + return registryGrain.GetAllTodoListsAsync(); + } + + public async Task CreateTodoListAsync(string listName) + { + var listId = NormalizeListName(listName); + var grain = grainFactory.GetGrain(listId); + await grain.SetNameAsync(listName); + + static string NormalizeListName(string name) + { + // Replace spaces and special characters to ensure valid URL + var normalized = name + .Trim() + .Replace(' ', '-') + .Replace('/', '-') + .Replace('\\', '-') + .ToLowerInvariant(); + + // Encode remaining bits for good measure! + return HttpUtility.UrlEncode(normalized); + } + } + + public Task GetTodoListAsync(string listId) + { + var grain = grainFactory.GetGrain(listId); + return grain.GetTodoListAsync(); + } + + public Task GetTodoListAtTimestampAsync(string listId, DateTimeOffset timestamp) + { + var grain = grainFactory.GetGrain(listId); + return grain.GetTodoListAtTimestampAsync(timestamp); + } + + public Task> GetTodoListHistoryAsync(string listId) + { + var grain = grainFactory.GetGrain(listId); + return grain.GetHistoryAsync(); + } + + public Task AddTodoItemAsync(string listId, string title) + { + var grain = grainFactory.GetGrain(listId); + return grain.AddTodoItemAsync(title); + } + + public Task UpdateTodoItemAsync(string listId, int itemId, string title) + { + var grain = grainFactory.GetGrain(listId); + return grain.UpdateTodoItemAsync(itemId, title); + } + + public Task ToggleTodoItemAsync(string listId, int itemId) + { + var grain = grainFactory.GetGrain(listId); + return grain.ToggleTodoItemAsync(itemId); + } + + public Task RemoveTodoItemAsync(string listId, int itemId) + { + var grain = grainFactory.GetGrain(listId); + return grain.RemoveTodoItemAsync(itemId); + } + + private sealed class Subscription( + ITodoListRegistryObserver observerRef, + ITodoListRegistryGrain registryGrain) : IDisposable + { + public void Dispose() + { + registryGrain.Unsubscribe(observerRef); + } + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.Development.json b/orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.json b/orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/orleans/JournaledTodoList/JournaledTodoList.WebApp/wwwroot/app.css b/orleans/JournaledTodoList/JournaledTodoList.WebApp/wwwroot/app.css new file mode 100644 index 00000000000..53883578d45 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.WebApp/wwwroot/app.css @@ -0,0 +1,38 @@ +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} + +.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { + color: var(--bs-secondary-color); + text-align: end; +} + +.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { + text-align: start; +} \ No newline at end of file diff --git a/orleans/JournaledTodoList/JournaledTodoList.slnx b/orleans/JournaledTodoList/JournaledTodoList.slnx new file mode 100644 index 00000000000..418e1b9df75 --- /dev/null +++ b/orleans/JournaledTodoList/JournaledTodoList.slnx @@ -0,0 +1,5 @@ + + + + + From a269a5a3c1440ae4faa450217a3d61eb56c670be Mon Sep 17 00:00:00 2001 From: Ivan Pehsterski Date: Mon, 26 Jan 2026 17:14:06 +0200 Subject: [PATCH 10/11] Conver old syntaxis to old one using StringBuilder (#7087) --- core/encoding/cyrillic-to-latin/cs/ConsoleModule.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/encoding/cyrillic-to-latin/cs/ConsoleModule.cs b/core/encoding/cyrillic-to-latin/cs/ConsoleModule.cs index da3fe685fd1..66864c65c49 100644 --- a/core/encoding/cyrillic-to-latin/cs/ConsoleModule.cs +++ b/core/encoding/cyrillic-to-latin/cs/ConsoleModule.cs @@ -86,8 +86,14 @@ static void Main() private static void ShowSyntax() { - Console.WriteLine("\nSyntax: CyrillicToRoman "); - Console.WriteLine(" where = source filename"); - Console.WriteLine(" = destination filename\n"); + StringBuilder sb = new StringBuilder(); + + sb.AppendLine(); + sb.AppendLine("Syntax: CyrillicToRoman "); + sb.AppendLine(" where = source filename"); + sb.AppendLine(" = destination filename"); + sb.AppendLine(); + + Console.WriteLine(sb.ToString()); } } From fd9090c0bb1d04cef27894242fc0bfca276cedd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:43:48 +0000 Subject: [PATCH 11/11] Bump actions/checkout from 6.0.1 to 6.0.2 in the dotnet group Bumps the dotnet group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v6.0.1...v6.0.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnet-code-metrics.yml | 2 +- .github/workflows/markdownlint.yml | 2 +- .github/workflows/publish-mono-samples.yml | 2 +- .github/workflows/snippets5000.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet-code-metrics.yml b/.github/workflows/dotnet-code-metrics.yml index 24032f17829..0437e3a5da7 100644 --- a/.github/workflows/dotnet-code-metrics.yml +++ b/.github/workflows/dotnet-code-metrics.yml @@ -22,7 +22,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - name: 'Print manual run reason' if: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index bdd76b3c25a..64f2a3508f2 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #@v2 + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 #@v2 - name: Use Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #@v1 with: diff --git a/.github/workflows/publish-mono-samples.yml b/.github/workflows/publish-mono-samples.yml index 5f923d8b2bb..e6205e204c5 100644 --- a/.github/workflows/publish-mono-samples.yml +++ b/.github/workflows/publish-mono-samples.yml @@ -23,7 +23,7 @@ jobs: build-mono: runs-on: macos-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #@v2 + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 #@v2 - name: Setup .NET SDK 6 if: ${{ env.DOTNET_DO_INSTALL == 'true' }} run: | diff --git a/.github/workflows/snippets5000.yml b/.github/workflows/snippets5000.yml index d96479ade58..4366a0cbe73 100644 --- a/.github/workflows/snippets5000.yml +++ b/.github/workflows/snippets5000.yml @@ -32,7 +32,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #@v4.2.2 + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 #@v4.2.2 # Get the latest preview SDK (or sdk not installed by the runner) - name: Setup .NET SDK