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"