From 8812ba7888f683ac4f4567f7ca02b4b09a60e586 Mon Sep 17 00:00:00 2001
From: Egil Hansen <egil@assimilated.dk>
Date: Tue, 21 Jan 2025 20:30:42 +0000
Subject: [PATCH] Upgrade chat room orleans sample to dotnet 9 (#7014)

* Upgrade ChatRoom orleans sample to dotnet 9

* Set default name to Anonymous if not specified

* Remove unused using

* Ensure chatroom message history doesn't grow above 100

* Update orleans/ChatRoom/ChatRoom.Service/ChannelGrain.cs

---------

Co-authored-by: David Pine <david.pine@microsoft.com>
---
 .../ChatRoom.Client/ChatRoom.Client.csproj    | 13 ++++----
 orleans/ChatRoom/ChatRoom.Client/Program.cs   |  4 +--
 orleans/ChatRoom/ChatRoom.Common/ChatMsg.cs   |  6 ++--
 .../ChatRoom.Common/ChatRoom.Common.csproj    |  7 ++--
 .../ChatRoom/ChatRoom.Common/IChannelGrain.cs |  4 +--
 .../ChatRoom/ChatRoom.Service/ChannelGrain.cs | 32 +++++++++++--------
 .../ChatRoom.Service/ChatRoom.Service.csproj  |  6 ++--
 orleans/ChatRoom/ChatRoom.Service/Program.cs  | 10 +++---
 orleans/ChatRoom/ChatRoom.slnLaunch           | 15 +++++++++
 orleans/ChatRoom/README.md                    |  2 +-
 10 files changed, 60 insertions(+), 39 deletions(-)
 create mode 100644 orleans/ChatRoom/ChatRoom.slnLaunch

diff --git a/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj b/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj
index 37c9e76de9b..78e8b4af9c6 100644
--- a/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj
+++ b/orleans/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj
@@ -1,12 +1,11 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <OutputType>Exe</OutputType>
-    <ServerGarbageCollection>true</ServerGarbageCollection>
-  </PropertyGroup>
+   </PropertyGroup>
 
   <ItemGroup>
     <None Remove="logo.png" />
@@ -17,8 +16,10 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Spectre.Console" Version="0.48.0" />
-    <PackageReference Include="Spectre.Console.ImageSharp" Version="0.48.0" />
+    <PackageReference Include="Microsoft.Orleans.Client" Version="9.0.1" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
+    <PackageReference Include="Spectre.Console" Version="0.49.1" />
+    <PackageReference Include="Spectre.Console.ImageSharp" Version="0.49.1" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/orleans/ChatRoom/ChatRoom.Client/Program.cs b/orleans/ChatRoom/ChatRoom.Client/Program.cs
index 382525dbf10..b6c39d8b5bc 100644
--- a/orleans/ChatRoom/ChatRoom.Client/Program.cs
+++ b/orleans/ChatRoom/ChatRoom.Client/Program.cs
@@ -2,13 +2,13 @@
 using ChatRoom;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
-using Orleans.Runtime;
 using Spectre.Console;
 
 using var host = new HostBuilder()
     .UseOrleansClient(clientBuilder =>
     {
-        clientBuilder.UseLocalhostClustering()
+        clientBuilder
+            .UseLocalhostClustering()
             .AddMemoryStreams("chat");
     })
     .Build();
diff --git a/orleans/ChatRoom/ChatRoom.Common/ChatMsg.cs b/orleans/ChatRoom/ChatRoom.Common/ChatMsg.cs
index fabb9b09376..a064b9a04dd 100644
--- a/orleans/ChatRoom/ChatRoom.Common/ChatMsg.cs
+++ b/orleans/ChatRoom/ChatRoom.Common/ChatMsg.cs
@@ -1,12 +1,12 @@
-namespace ChatRoom;
+namespace ChatRoom;
 
-[GenerateSerializer]
+[GenerateSerializer, Immutable]
 public record class ChatMsg(
     string? Author,
     string Text)
 {
     [Id(0)]
-    public string Author { get; init; } = Author ?? "Alexey";
+    public string Author { get; init; } = Author ?? "Anonymous";
 
     [Id(1)]
     public DateTimeOffset Created { get; init; } = DateTimeOffset.Now;
diff --git a/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj b/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj
index b6cb0913e0c..67398f6cd68 100644
--- a/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj
+++ b/orleans/ChatRoom/ChatRoom.Common/ChatRoom.Common.csproj
@@ -1,14 +1,15 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
+    <RootNamespace>ChatRoomt</RootNamespace>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.Orleans.Sdk" Version="8.0.0" />
-    <PackageReference Include="Microsoft.Orleans.Streaming" Version="8.0.0" />
+    <PackageReference Include="Microsoft.Orleans.Sdk" Version="9.0.1" />
+    <PackageReference Include="Microsoft.Orleans.Streaming" Version="9.0.1" />
   </ItemGroup>
 
 </Project>
\ No newline at end of file
diff --git a/orleans/ChatRoom/ChatRoom.Common/IChannelGrain.cs b/orleans/ChatRoom/ChatRoom.Common/IChannelGrain.cs
index 52505de0038..de7e35d33cf 100644
--- a/orleans/ChatRoom/ChatRoom.Common/IChannelGrain.cs
+++ b/orleans/ChatRoom/ChatRoom.Common/IChannelGrain.cs
@@ -1,6 +1,4 @@
-using Orleans.Runtime;
-
-namespace ChatRoom;
+namespace ChatRoom;
 
 public interface IChannelGrain : IGrainWithStringKey
 {
diff --git a/orleans/ChatRoom/ChatRoom.Service/ChannelGrain.cs b/orleans/ChatRoom/ChatRoom.Service/ChannelGrain.cs
index 10250b7679f..35811b23e89 100644
--- a/orleans/ChatRoom/ChatRoom.Service/ChannelGrain.cs
+++ b/orleans/ChatRoom/ChatRoom.Service/ChannelGrain.cs
@@ -1,24 +1,23 @@
-using Orleans.Runtime;
-using Orleans.Streams;
+using Orleans.Streams;
 
 namespace ChatRoom;
 
-public class ChannelGrain : Grain, IChannelGrain
+public sealed class ChannelGrain : Grain, IChannelGrain
 {
-    private readonly List<ChatMsg> _messages = new(100);
-    private readonly List<string> _onlineMembers = new(10);
+    private readonly List<ChatMsg> _messages = [];
+    private readonly List<string> _onlineMembers = [];
 
-    private IAsyncStream<ChatMsg> _stream = null!;
+    // Initialized in OnActivateAsync that runs before
+    // other methods that uses _stream field can be invoked.
+    private IAsyncStream<ChatMsg> _stream = default!;
 
     public override Task OnActivateAsync(CancellationToken cancellationToken)
     {
         var streamProvider = this.GetStreamProvider("chat");
 
-        var streamId = StreamId.Create(
-            "ChatRoom", this.GetPrimaryKeyString());
+        var streamId = StreamId.Create("ChatRoom", this.GetPrimaryKeyString());
 
-        _stream = streamProvider.GetStream<ChatMsg>(
-            streamId);
+        _stream = streamProvider.GetStream<ChatMsg>(streamId);
 
         return base.OnActivateAsync(cancellationToken);
     }
@@ -29,8 +28,8 @@ public async Task<StreamId> Join(string nickname)
 
         await _stream.OnNextAsync(
             new ChatMsg(
-                "System",
-                $"{nickname} joins the chat '{this.GetPrimaryKeyString()}' ..."));
+                Author: "System",
+                Text: $"{nickname} joins the chat '{this.GetPrimaryKeyString()}' ..."));
 
         return _stream.StreamId;
     }
@@ -41,8 +40,8 @@ public async Task<StreamId> Leave(string nickname)
 
         await _stream.OnNextAsync(
             new ChatMsg(
-                "System",
-                $"{nickname} leaves the chat..."));
+                Author: "System",
+                Text: $"{nickname} leaves the chat..."));
 
         return _stream.StreamId;
     }
@@ -51,6 +50,11 @@ public async Task<bool> Message(ChatMsg msg)
     {
         _messages.Add(msg);
 
+        if (_messages.Count > 100)
+        {
+            _messages.RemoveAt(0);
+        }
+
         await _stream.OnNextAsync(msg);
 
         return true;
diff --git a/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj b/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj
index fd679dae667..758c234526a 100644
--- a/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj
+++ b/orleans/ChatRoom/ChatRoom.Service/ChatRoom.Service.csproj
@@ -1,7 +1,7 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <OutputType>Exe</OutputType>
@@ -9,7 +9,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.Orleans.Server" Version="8.0.0" />
+    <PackageReference Include="Microsoft.Orleans.Server" Version="9.0.1" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/orleans/ChatRoom/ChatRoom.Service/Program.cs b/orleans/ChatRoom/ChatRoom.Service/Program.cs
index a49de0be839..3f9a80f6cab 100644
--- a/orleans/ChatRoom/ChatRoom.Service/Program.cs
+++ b/orleans/ChatRoom/ChatRoom.Service/Program.cs
@@ -1,11 +1,13 @@
-using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
 
 await Host.CreateDefaultBuilder(args)
     .UseOrleans(siloBuilder =>
     {
-        siloBuilder
-            .UseLocalhostClustering()
+        siloBuilder.UseLocalhostClustering()
             .AddMemoryGrainStorage("PubSubStore")
-            .AddMemoryStreams("chat");
+            .AddMemoryStreams("chat")
+            .ConfigureLogging(logging => logging.AddConsole());
     })
     .RunConsoleAsync();
+
diff --git a/orleans/ChatRoom/ChatRoom.slnLaunch b/orleans/ChatRoom/ChatRoom.slnLaunch
new file mode 100644
index 00000000000..dcb30fbc541
--- /dev/null
+++ b/orleans/ChatRoom/ChatRoom.slnLaunch
@@ -0,0 +1,15 @@
+[
+  {
+    "Name": "Run client and service",
+    "Projects": [
+      {
+        "Path": "ChatRoom.Client\\ChatRoom.Client.csproj",
+        "Action": "Start"
+      },
+      {
+        "Path": "ChatRoom.Service\\ChatRoom.Service.csproj",
+        "Action": "Start"
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/orleans/ChatRoom/README.md b/orleans/ChatRoom/README.md
index 99496bc2886..e60f4aa5d66 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 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 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later.
 
 ## Building the sample