diff --git a/orleans/Chirper/Chirper.Client/Chirper.Client.csproj b/orleans/Chirper/Chirper.Client/Chirper.Client.csproj
index 7a56fc8f4b3..abea8cad7fa 100644
--- a/orleans/Chirper/Chirper.Client/Chirper.Client.csproj
+++ b/orleans/Chirper/Chirper.Client/Chirper.Client.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net9.0
enable
enable
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/orleans/Chirper/Chirper.Client/ShellHostedService.cs b/orleans/Chirper/Chirper.Client/ShellHostedService.cs
index 5ae6626c2bd..880233a9891 100644
--- a/orleans/Chirper/Chirper.Client/ShellHostedService.cs
+++ b/orleans/Chirper/Chirper.Client/ShellHostedService.cs
@@ -7,21 +7,12 @@
namespace Chirper.Client;
-public sealed partial class ShellHostedService : BackgroundService
+public sealed partial class ShellHostedService(IClusterClient client, IHostApplicationLifetime applicationLifetime) : BackgroundService
{
- private readonly IClusterClient _client;
- private readonly IHostApplicationLifetime _applicationLifetime;
-
private ChirperConsoleViewer? _viewer;
private IChirperViewer? _viewerRef;
private IChirperAccount? _account;
- public ShellHostedService(IClusterClient client, IHostApplicationLifetime applicationLifetime)
- {
- _client = client;
- _applicationLifetime = applicationLifetime;
- }
-
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
ShowHelp(true);
@@ -35,7 +26,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
else if (command is null or "/quit")
{
- _applicationLifetime.StopApplication();
+ applicationLifetime.StopApplication();
return;
}
else if (command.StartsWith("/user "))
@@ -44,7 +35,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Unobserve();
var username = match.Groups["username"].Value;
- _account = _client.GetGrain(username);
+ _account = client.GetGrain(username);
AnsiConsole.MarkupLine("[bold grey][[[/][bold lime]✓[/][bold grey]]][/] The current user is now [navy]{0}[/]", username);
}
@@ -121,7 +112,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
if (_viewerRef is null)
{
_viewer = new ChirperConsoleViewer(_account.GetPrimaryKeyString());
- _viewerRef = _client.CreateObjectReference(_viewer);
+ _viewerRef = client.CreateObjectReference(_viewer);
}
await _account.SubscribeAsync(_viewerRef);
diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj b/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj
index 1e2bd3c7486..b04dad1cea5 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
+ net9.0
enable
enable
-
+
diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs b/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs
index a8fe5b930bc..2475fcf48f9 100644
--- a/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs
+++ b/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs
@@ -1,7 +1,7 @@
-namespace System.Collections.Generic;
+namespace System.Collections.Generic;
///
-/// Helper extensions for enumerables.
+/// Helper extensions for .
///
public static class EnumerableExtensions
{
diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs b/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs
index 4d8f79745b8..344dd95a165 100644
--- a/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs
+++ b/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs
@@ -1,9 +1,9 @@
-namespace Chirper.Grains.Models;
+namespace Chirper.Grains.Models;
///
/// Data object representing one Chirp message entry
///
-[GenerateSerializer]
+[GenerateSerializer, Immutable]
public record class ChirperMessage(
///
/// The message content for this chirp message entry.
diff --git a/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj b/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj
index ea74d6737f7..a3f6b16ea00 100644
--- a/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj
+++ b/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj
@@ -1,13 +1,13 @@
- net8.0
+ net9.0
enable
enable
-
+
diff --git a/orleans/Chirper/Chirper.Grains/ChirperAccount.cs b/orleans/Chirper/Chirper.Grains/ChirperAccount.cs
index 5d72315b932..c869f134662 100644
--- a/orleans/Chirper/Chirper.Grains/ChirperAccount.cs
+++ b/orleans/Chirper/Chirper.Grains/ChirperAccount.cs
@@ -1,4 +1,4 @@
-using System.Collections.Immutable;
+using System.Collections.Immutable;
using Chirper.Grains.Models;
using Microsoft.Extensions.Logging;
using Orleans.Concurrency;
@@ -7,8 +7,12 @@
namespace Chirper.Grains;
[Reentrant]
-public sealed class ChirperAccount : Grain, IChirperAccount
+public sealed class ChirperAccount(
+ [PersistentState(stateName: "account", storageName: "AccountState")] IPersistentState state,
+ ILogger logger) : Grain, IChirperAccount
{
+ private static string GrainType => nameof(ChirperAccount);
+
///
/// Size for the recently received message cache.
///
@@ -29,28 +33,17 @@ public sealed class ChirperAccount : Grain, IChirperAccount
/// This list is not part of state and will not survive grain deactivation.
///
private readonly HashSet _viewers = new();
- private readonly ILogger _logger;
- private readonly IPersistentState _state;
///
/// Allows state writing to happen in the background.
///
private Task? _outstandingWriteStateOperation;
- public ChirperAccount(
- [PersistentState(stateName: "account", storageName: "AccountState")] IPersistentState state,
- ILogger logger)
- {
- _state = state;
- _logger = logger;
- }
-
- private static string GrainType => nameof(ChirperAccount);
private string GrainKey => this.GetPrimaryKeyString();
public override Task OnActivateAsync(CancellationToken _)
{
- _logger.LogInformation("{GrainType} {GrainKey} activated.", GrainType, GrainKey);
+ logger.LogInformation("{GrainType} {GrainKey} activated.", GrainType, GrainKey);
return Task.CompletedTask;
}
@@ -59,41 +52,41 @@ public async ValueTask PublishMessageAsync(string message)
{
var chirp = CreateNewChirpMessage(message);
- _logger.LogInformation("{GrainType} {GrainKey} publishing new chirp message '{Chirp}'.",
+ logger.LogInformation("{GrainType} {GrainKey} publishing new chirp message '{Chirp}'.",
GrainType, GrainKey, chirp);
- _state.State.MyPublishedMessages.Enqueue(chirp);
+ state.State.MyPublishedMessages.Enqueue(chirp);
- while (_state.State.MyPublishedMessages.Count > PublishedMessagesCacheSize)
+ while (state.State.MyPublishedMessages.Count > PublishedMessagesCacheSize)
{
- _state.State.MyPublishedMessages.Dequeue();
+ state.State.MyPublishedMessages.Dequeue();
}
await WriteStateAsync();
// notify viewers of new message
- _logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {ViewerCount} viewers.",
+ logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {ViewerCount} viewers.",
GrainType, GrainKey, _viewers.Count);
_viewers.ForEach(_ => _.NewChirp(chirp));
// notify followers of a new message
- _logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {FollowerCount} followers.",
- GrainType, GrainKey, _state.State.Followers.Count);
+ logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {FollowerCount} followers.",
+ GrainType, GrainKey, state.State.Followers.Count);
- await Task.WhenAll(_state.State.Followers.Values.Select(_ => _.NewChirpAsync(chirp)).ToArray());
+ await Task.WhenAll(state.State.Followers.Values.Select(_ => _.NewChirpAsync(chirp)).ToArray());
}
public ValueTask> GetReceivedMessagesAsync(int number, int start)
{
if (start < 0) start = 0;
- if (start + number > _state.State.RecentReceivedMessages.Count)
+ if (start + number > state.State.RecentReceivedMessages.Count)
{
- number = _state.State.RecentReceivedMessages.Count - start;
+ number = state.State.RecentReceivedMessages.Count - start;
}
return ValueTask.FromResult(
- _state.State.RecentReceivedMessages
+ state.State.RecentReceivedMessages
.Skip(start)
.Take(number)
.ToImmutableList());
@@ -101,7 +94,7 @@ public ValueTask> GetReceivedMessagesAsync(int num
public async ValueTask FollowUserIdAsync(string username)
{
- _logger.LogInformation(
+ logger.LogInformation(
"{GrainType} {UserName} > FollowUserName({TargetUserName}).",
GrainType,
GrainKey,
@@ -111,7 +104,7 @@ public async ValueTask FollowUserIdAsync(string username)
await userToFollow.AddFollowerAsync(GrainKey, this.AsReference());
- _state.State.Subscriptions[username] = userToFollow;
+ state.State.Subscriptions[username] = userToFollow;
await WriteStateAsync();
@@ -121,7 +114,7 @@ public async ValueTask FollowUserIdAsync(string username)
public async ValueTask UnfollowUserIdAsync(string username)
{
- _logger.LogInformation(
+ logger.LogInformation(
"{GrainType} {GrainKey} > UnfollowUserName({TargetUserName}).",
GrainType,
GrainKey,
@@ -132,7 +125,7 @@ await GrainFactory.GetGrain(username)
.RemoveFollowerAsync(GrainKey);
// remove this publisher from the subscriptions list
- _state.State.Subscriptions.Remove(username);
+ state.State.Subscriptions.Remove(username);
// save now
await WriteStateAsync();
@@ -142,10 +135,10 @@ await GrainFactory.GetGrain(username)
}
public ValueTask> GetFollowingListAsync() =>
- ValueTask.FromResult(_state.State.Subscriptions.Keys.ToImmutableList());
+ ValueTask.FromResult(state.State.Subscriptions.Keys.ToImmutableList());
public ValueTask> GetFollowersListAsync() =>
- ValueTask.FromResult(_state.State.Followers.Keys.ToImmutableList());
+ ValueTask.FromResult(state.State.Followers.Keys.ToImmutableList());
public ValueTask SubscribeAsync(IChirperViewer viewer)
{
@@ -162,12 +155,12 @@ public ValueTask UnsubscribeAsync(IChirperViewer viewer)
public ValueTask> GetPublishedMessagesAsync(int number, int start)
{
if (start < 0) start = 0;
- if (start + number > _state.State.MyPublishedMessages.Count)
+ if (start + number > state.State.MyPublishedMessages.Count)
{
- number = _state.State.MyPublishedMessages.Count - start;
+ number = state.State.MyPublishedMessages.Count - start;
}
return ValueTask.FromResult(
- _state.State.MyPublishedMessages
+ state.State.MyPublishedMessages
.Skip(start)
.Take(number)
.ToImmutableList());
@@ -175,37 +168,37 @@ public ValueTask> GetPublishedMessagesAsync(int nu
public async ValueTask AddFollowerAsync(string username, IChirperSubscriber follower)
{
- _state.State.Followers[username] = follower;
+ state.State.Followers[username] = follower;
await WriteStateAsync();
_viewers.ForEach(cv => cv.NewFollower(username));
}
public ValueTask RemoveFollowerAsync(string username)
{
- _state.State.Followers.Remove(username);
+ state.State.Followers.Remove(username);
return WriteStateAsync();
}
public async Task NewChirpAsync(ChirperMessage chirp)
{
- _logger.LogInformation(
+ logger.LogInformation(
"{GrainType} {GrainKey} received chirp message = {Chirp}",
GrainType,
GrainKey,
chirp);
- _state.State.RecentReceivedMessages.Enqueue(chirp);
+ state.State.RecentReceivedMessages.Enqueue(chirp);
// only relevant when not using fixed queue
- while (_state.State.RecentReceivedMessages.Count > ReceivedMessagesCacheSize) // to keep not more than the max number of messages
+ while (state.State.RecentReceivedMessages.Count > ReceivedMessagesCacheSize) // to keep not more than the max number of messages
{
- _state.State.RecentReceivedMessages.Dequeue();
+ state.State.RecentReceivedMessages.Dequeue();
}
await WriteStateAsync();
// notify any viewers that a new chirp has been received
- _logger.LogInformation(
+ logger.LogInformation(
"{GrainType} {GrainKey} sending received chirp message to {ViewerCount} viewers",
GrainType,
GrainKey,
@@ -246,7 +239,7 @@ private async ValueTask WriteStateAsync()
if (_outstandingWriteStateOperation is null)
{
// If after the initial write is completed, no other request initiated a new write operation, do it now.
- currentWriteStateOperation = _state.WriteStateAsync();
+ currentWriteStateOperation = state.WriteStateAsync();
_outstandingWriteStateOperation = currentWriteStateOperation;
}
else
diff --git a/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs b/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs
index a7fa6859b8a..6ec9694dd27 100644
--- a/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs
+++ b/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs
@@ -1,4 +1,4 @@
-using Chirper.Grains.Models;
+using Chirper.Grains.Models;
namespace Chirper.Grains;
diff --git a/orleans/Chirper/Chirper.Server/Chirper.Server.csproj b/orleans/Chirper/Chirper.Server/Chirper.Server.csproj
index ed23905ba9d..a4481270818 100644
--- a/orleans/Chirper/Chirper.Server/Chirper.Server.csproj
+++ b/orleans/Chirper/Chirper.Server/Chirper.Server.csproj
@@ -2,14 +2,14 @@
Exe
- net8.0
+ net9.0
enable
enable
-
-
+
+
diff --git a/orleans/Chirper/Chirper.sln b/orleans/Chirper/Chirper.sln
index 4d921fc05f4..ff35074f486 100644
--- a/orleans/Chirper/Chirper.sln
+++ b/orleans/Chirper/Chirper.sln
@@ -9,6 +9,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chirper.Server", "Chirper.Server\Chirper.Server.csproj", "{2C967379-D025-4EBB-96C0-1A4C6CBE4B4B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chirper.Client", "Chirper.Client\Chirper.Client.csproj", "{3BE53D35-938D-47D8-B135-501786B145DC}"
+ ProjectSection(ProjectDependencies) = postProject
+ {2C967379-D025-4EBB-96C0-1A4C6CBE4B4B} = {2C967379-D025-4EBB-96C0-1A4C6CBE4B4B}
+ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1223A865-0D72-4BF7-8FCD-016A48F3EC68}"
ProjectSection(SolutionItems) = preProject
diff --git a/orleans/Chirper/Chirper.slnLaunch b/orleans/Chirper/Chirper.slnLaunch
new file mode 100644
index 00000000000..1c5cd154cd3
--- /dev/null
+++ b/orleans/Chirper/Chirper.slnLaunch
@@ -0,0 +1,15 @@
+[
+ {
+ "Name": "Server + Client",
+ "Projects": [
+ {
+ "Path": "Chirper.Client\\Chirper.Client.csproj",
+ "Action": "Start"
+ },
+ {
+ "Path": "Chirper.Server\\Chirper.Server.csproj",
+ "Action": "Start"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/orleans/Chirper/README.md b/orleans/Chirper/README.md
index 82ed63e882f..0c0e892d838 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 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later.
## Building the sample