diff --git a/orleans/GPSTracker/GPSTracker.AppHost/GPSTracker.AppHost.csproj b/orleans/GPSTracker/GPSTracker.AppHost/GPSTracker.AppHost.csproj
new file mode 100644
index 00000000000..1bf637d69c9
--- /dev/null
+++ b/orleans/GPSTracker/GPSTracker.AppHost/GPSTracker.AppHost.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ true
+ afde51c8-abcf-4e8d-97a3-fd9301b96ffd
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/orleans/GPSTracker/GPSTracker.AppHost/Program.cs b/orleans/GPSTracker/GPSTracker.AppHost/Program.cs
new file mode 100644
index 00000000000..be426d35447
--- /dev/null
+++ b/orleans/GPSTracker/GPSTracker.AppHost/Program.cs
@@ -0,0 +1,19 @@
+var builder = DistributedApplication.CreateBuilder(args);
+
+// https://learn.microsoft.com/en-us/dotnet/aspire/frameworks/orleans?tabs=dotnet-cli
+var storage = builder.AddAzureStorage("storage")
+ .RunAsEmulator();
+var clusteringTable = storage.AddTables("clustering");
+var orleans = builder.AddOrleans("default")
+ .WithClustering(clusteringTable);
+
+var service = builder.AddProject("gpstracker-service")
+ .WithReference(orleans)
+ .WithReplicas(3);
+
+var deviceGateway = builder.AddProject("device-gateway")
+ .WithReference(orleans.AsClient())
+ .WithExternalHttpEndpoints()
+ .WaitFor(service);
+
+builder.Build().Run();
diff --git a/orleans/GPSTracker/GPSTracker.AppHost/Properties/launchSettings.json b/orleans/GPSTracker/GPSTracker.AppHost/Properties/launchSettings.json
new file mode 100644
index 00000000000..a384ae369e6
--- /dev/null
+++ b/orleans/GPSTracker/GPSTracker.AppHost/Properties/launchSettings.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:17244;http://localhost:15133",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21285",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22279"
+ }
+ },
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15133",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19294",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20112"
+ }
+ }
+ }
+}
diff --git a/orleans/GPSTracker/GPSTracker.AppHost/appsettings.Development.json b/orleans/GPSTracker/GPSTracker.AppHost/appsettings.Development.json
new file mode 100644
index 00000000000..0c208ae9181
--- /dev/null
+++ b/orleans/GPSTracker/GPSTracker.AppHost/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/orleans/GPSTracker/GPSTracker.AppHost/appsettings.json b/orleans/GPSTracker/GPSTracker.AppHost/appsettings.json
new file mode 100644
index 00000000000..31c092aa450
--- /dev/null
+++ b/orleans/GPSTracker/GPSTracker.AppHost/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj b/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj
index 5274db20142..bfce1d9e944 100644
--- a/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj
+++ b/orleans/GPSTracker/GPSTracker.Common/GPSTracker.Common.csproj
@@ -1,11 +1,11 @@
- net8.0
+ net9.0
enable
enable
-
+
\ No newline at end of file
diff --git a/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs b/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs
index 4a8fea79004..3ca38970383 100644
--- a/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs
+++ b/orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs
@@ -111,7 +111,7 @@ private static void UpdateDevicePosition(Model model, double delta)
public static double NextDouble(double min, double max) => Random.NextDouble() * (max - min) + min;
- private class Model
+ private sealed class Model
{
public Stopwatch TimeSinceLastUpdate { get; } = Stopwatch.StartNew();
public int DeviceId { get; set; }
diff --git a/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj b/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj
index f986c60fff6..b49cb511353 100644
--- a/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj
+++ b/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/GPSTracker.FakeDeviceGateway.csproj
@@ -1,11 +1,16 @@
- net8.0
+ net9.0
enable
enable
Exe
+
+
+
+
+
\ No newline at end of file
diff --git a/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/Program.cs b/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/Program.cs
index 29323f6ca91..f073dd6cf12 100644
--- a/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/Program.cs
+++ b/orleans/GPSTracker/GPSTracker.FakeDeviceGateway/Program.cs
@@ -2,11 +2,12 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using IHost host = Host.CreateDefaultBuilder(args)
- .UseOrleansClient((ctx, clientBuilder) => clientBuilder.UseLocalhostClustering())
- .UseConsoleLifetime()
- .Build();
+var builder = Host.CreateApplicationBuilder(args);
+builder.AddServiceDefaults();
+builder.AddKeyedAzureTableClient("clustering");
+builder.UseOrleansClient();
+using var host = builder.Build();
await host.StartAsync();
IHostApplicationLifetime lifetime = host.Services.GetRequiredService();
diff --git a/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj b/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj
index 619c89e22eb..7d29f0de57c 100644
--- a/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj
+++ b/orleans/GPSTracker/GPSTracker.Service/GPSTracker.Service.csproj
@@ -1,6 +1,6 @@
-
+
- net8.0
+ net9.0
enable
enable
Exe
@@ -8,13 +8,13 @@
+
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/orleans/GPSTracker/GPSTracker.Service/Grains/DeviceGrain.cs b/orleans/GPSTracker/GPSTracker.Service/Grains/DeviceGrain.cs
index f843c29e1df..79456d205cd 100644
--- a/orleans/GPSTracker/GPSTracker.Service/Grains/DeviceGrain.cs
+++ b/orleans/GPSTracker/GPSTracker.Service/Grains/DeviceGrain.cs
@@ -7,7 +7,7 @@ namespace GPSTracker.GrainImplementation;
[Reentrant]
public class DeviceGrain : Grain, IDeviceGrain
{
- private DeviceMessage _lastMessage = null!;
+ private DeviceMessage? _lastMessage;
private readonly IPushNotifierGrain _pushNotifier;
diff --git a/orleans/GPSTracker/GPSTracker.Service/Grains/HubListGrain.cs b/orleans/GPSTracker/GPSTracker.Service/Grains/HubListGrain.cs
index 472c97f15c0..56da17566c7 100644
--- a/orleans/GPSTracker/GPSTracker.Service/Grains/HubListGrain.cs
+++ b/orleans/GPSTracker/GPSTracker.Service/Grains/HubListGrain.cs
@@ -2,18 +2,12 @@
namespace GPSTracker.GrainImplementation;
-public class HubListGrain : Grain, IHubListGrain
+public class HubListGrain(IClusterMembershipService clusterMembershipService) : Grain, IHubListGrain
{
- private readonly IClusterMembershipService _clusterMembership;
- private readonly Dictionary _hubs = new();
+ private readonly Dictionary _hubs = [];
private MembershipVersion _cacheMembershipVersion;
private List<(SiloAddress Host, IRemoteLocationHub Hub)>? _cache;
- public HubListGrain(IClusterMembershipService clusterMembershipService)
- {
- _clusterMembership = clusterMembershipService;
- }
-
public ValueTask AddHub(SiloAddress host, IRemoteLocationHub hubReference)
{
// Invalidate the cache.
@@ -29,7 +23,7 @@ public ValueTask AddHub(SiloAddress host, IRemoteLocationHub hubReference)
private List<(SiloAddress Host, IRemoteLocationHub Hub)> GetCachedHubs()
{
// Returns a cached list of hubs if the cache is valid, otherwise builds a list of hubs.
- ClusterMembershipSnapshot clusterMembers = _clusterMembership.CurrentSnapshot;
+ ClusterMembershipSnapshot clusterMembers = clusterMembershipService.CurrentSnapshot;
if (_cache is { } && clusterMembers.Version == _cacheMembershipVersion)
{
return _cache;
diff --git a/orleans/GPSTracker/GPSTracker.Service/Grains/IHubListGrain.cs b/orleans/GPSTracker/GPSTracker.Service/Grains/IHubListGrain.cs
index 066b43489e3..fc1c7c013fa 100644
--- a/orleans/GPSTracker/GPSTracker.Service/Grains/IHubListGrain.cs
+++ b/orleans/GPSTracker/GPSTracker.Service/Grains/IHubListGrain.cs
@@ -1,4 +1,4 @@
-using Orleans.Runtime;
+using Orleans.Runtime;
namespace GPSTracker.GrainImplementation;
@@ -8,5 +8,6 @@ namespace GPSTracker.GrainImplementation;
public interface IHubListGrain : IGrainWithGuidKey
{
ValueTask AddHub(SiloAddress host, IRemoteLocationHub hubReference);
+
ValueTask> GetHubs();
}
diff --git a/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs b/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs
index d179caebc8f..b2c3dc21b5e 100644
--- a/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs
+++ b/orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs
@@ -7,44 +7,46 @@ namespace GPSTracker.GrainImplementation;
[Reentrant]
[StatelessWorker(maxLocalWorkers: 12)]
-public class PushNotifierGrain : Grain, IPushNotifierGrain
+public sealed class PushNotifierGrain(ILogger logger) : Grain, IPushNotifierGrain, IDisposable
{
private readonly Queue _messageQueue = new();
- private readonly ILogger _logger;
private List<(SiloAddress Host, IRemoteLocationHub Hub)> _hubs = new();
- public PushNotifierGrain(ILogger logger) => _logger = logger;
private Task _flushTask = Task.CompletedTask;
+ private IGrainTimer? _flushTimer;
+ private IGrainTimer? _refreshTimer;
public override async Task OnActivateAsync(CancellationToken cancellationToken)
{
// Set up a timer to regularly flush the message queue
- RegisterTimer(
- _ =>
- {
- Flush();
- return Task.CompletedTask;
- },
- null,
- TimeSpan.FromMilliseconds(15),
- TimeSpan.FromMilliseconds(15));
+ _flushTimer = this.RegisterGrainTimer(
+ ct => Flush(),
+ dueTime: TimeSpan.FromMilliseconds(15),
+ period: TimeSpan.FromMilliseconds(15));
// Set up a timer to regularly refresh the hubs, to respond to azure infrastructure changes
await RefreshHubs();
- RegisterTimer(
- asyncCallback: async _ => await RefreshHubs(),
- state: null,
+
+ _refreshTimer = this.RegisterGrainTimer(
+ async _ => await RefreshHubs(),
dueTime: TimeSpan.FromSeconds(60),
period: TimeSpan.FromSeconds(60));
await base.OnActivateAsync(cancellationToken);
}
+
public override async Task OnDeactivateAsync(DeactivationReason deactivationReason, CancellationToken cancellationToken)
{
await Flush();
await base.OnDeactivateAsync(deactivationReason, cancellationToken);
}
+ public void Dispose()
+ {
+ _flushTimer?.Dispose();
+ _refreshTimer?.Dispose();
+ }
+
private async ValueTask RefreshHubs()
{
// Discover the current infrastructure
@@ -77,16 +79,14 @@ async Task FlushInternal()
{
// Send all messages to all SignalR hubs
var messagesToSend = new List(Math.Min(_messageQueue.Count, MaxMessagesPerBatch));
- while (messagesToSend.Count < MaxMessagesPerBatch && _messageQueue.TryDequeue(out VelocityMessage? msg)) messagesToSend.Add(msg);
-
- var tasks = new List(_hubs.Count);
- var batch = new VelocityBatch(messagesToSend);
-
- foreach ((SiloAddress Host, IRemoteLocationHub Hub) hub in _hubs)
+ while (messagesToSend.Count < MaxMessagesPerBatch && _messageQueue.TryDequeue(out VelocityMessage? msg))
{
- tasks.Add(BroadcastUpdates(hub.Host, hub.Hub, batch, _logger));
+ messagesToSend.Add(msg);
}
+ var batch = new VelocityBatch(messagesToSend);
+ var tasks = _hubs.Select(hub => BroadcastUpdates(hub.Host, hub.Hub, batch, logger));
+
await Task.WhenAll(tasks);
}
}
diff --git a/orleans/GPSTracker/GPSTracker.Service/Grains/RemoteLocationHub.cs b/orleans/GPSTracker/GPSTracker.Service/Grains/RemoteLocationHub.cs
index a81ec195263..8f6a994e4fc 100644
--- a/orleans/GPSTracker/GPSTracker.Service/Grains/RemoteLocationHub.cs
+++ b/orleans/GPSTracker/GPSTracker.Service/Grains/RemoteLocationHub.cs
@@ -6,14 +6,10 @@ namespace GPSTracker;
///
/// Broadcasts location messages to clients which are connected to the local SignalR hub.
///
-internal sealed class RemoteLocationHub : IRemoteLocationHub
+internal sealed class RemoteLocationHub(IHubContext hub) : IRemoteLocationHub
{
- private readonly IHubContext _hub;
-
- public RemoteLocationHub(IHubContext hub) => _hub = hub;
-
// Send a message to every client which is connected to the hub
public ValueTask BroadcastUpdates(VelocityBatch messages) =>
- new(_hub.Clients.All.SendAsync(
+ new(hub.Clients.All.SendAsync(
"locationUpdates", messages, CancellationToken.None));
}
diff --git a/orleans/GPSTracker/GPSTracker.Service/HubListUpdater.cs b/orleans/GPSTracker/GPSTracker.Service/HubListUpdater.cs
index 6523bf84bb1..9efd2a9a18b 100644
--- a/orleans/GPSTracker/GPSTracker.Service/HubListUpdater.cs
+++ b/orleans/GPSTracker/GPSTracker.Service/HubListUpdater.cs
@@ -8,31 +8,19 @@ namespace GPSTracker;
///
/// Periodically updates the implementation with a reference to the local .
///
-[Reentrant]
-internal sealed class HubListUpdater : BackgroundService
+internal sealed class HubListUpdater(
+ IGrainFactory grainFactory,
+ ILogger logger,
+ ILocalSiloDetails localSiloDetails,
+ IHubContext hubContext) : BackgroundService
{
- private readonly IGrainFactory _grainFactory;
- private readonly ILogger _logger;
- private readonly ILocalSiloDetails _localSiloDetails;
- private readonly RemoteLocationHub _locationBroadcaster;
-
- public HubListUpdater(
- IGrainFactory grainFactory,
- ILogger logger,
- ILocalSiloDetails localSiloDetails,
- IHubContext hubContext)
- {
- _grainFactory = grainFactory;
- _logger = logger;
- _localSiloDetails = localSiloDetails;
- _locationBroadcaster = new RemoteLocationHub(hubContext);
- }
+ private readonly RemoteLocationHub _locationBroadcaster = new RemoteLocationHub(hubContext);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
- IHubListGrain hubListGrain = _grainFactory.GetGrain(Guid.Empty);
- SiloAddress localSiloAddress = _localSiloDetails.SiloAddress;
- IRemoteLocationHub selfReference = _grainFactory.CreateObjectReference(_locationBroadcaster);
+ IHubListGrain hubListGrain = grainFactory.GetGrain(Guid.Empty);
+ SiloAddress localSiloAddress = localSiloDetails.SiloAddress;
+ IRemoteLocationHub selfReference = grainFactory.CreateObjectReference(_locationBroadcaster);
// This runs in a loop because the HubListGrain does not use any form of persistence, so if the
// host which it is activated on stops, then it will lose any internal state.
@@ -45,7 +33,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
catch (Exception exception) when (!stoppingToken.IsCancellationRequested)
{
- _logger.LogError(exception, "Error polling location hub list");
+ logger.LogError(exception, "Error polling location hub list");
}
if (!stoppingToken.IsCancellationRequested)
diff --git a/orleans/GPSTracker/GPSTracker.Service/Program.cs b/orleans/GPSTracker/GPSTracker.Service/Program.cs
index d7ef77561bb..507940ee424 100644
--- a/orleans/GPSTracker/GPSTracker.Service/Program.cs
+++ b/orleans/GPSTracker/GPSTracker.Service/Program.cs
@@ -4,61 +4,25 @@
using OpenTelemetry.Trace;
using System.Net;
-WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddOpenTelemetry()
- .WithMetrics(metrics =>
- {
- metrics
- .AddPrometheusExporter()
- .AddMeter("Microsoft.Orleans");
- })
- .WithTracing(tracing =>
- {
- // Set a service name
- tracing.SetResourceBuilder(
- ResourceBuilder.CreateDefault()
- .AddService(serviceName: "GPSTracker", serviceVersion: "1.0"));
-
- tracing.AddSource("Microsoft.Orleans.Runtime");
- tracing.AddSource("Microsoft.Orleans.Application");
-
- tracing.AddZipkinExporter(zipkin =>
- {
- zipkin.Endpoint = new Uri("http://localhost:9411/api/v2/spans");
- });
- });
-
-builder.Host.UseOrleans((ctx, siloBuilder) => {
-
- // In order to support multiple hosts forming a cluster, they must listen on different ports.
- // Use the --InstanceId X option to launch subsequent hosts.
- int instanceId = ctx.Configuration.GetValue("InstanceId");
- siloBuilder.UseLocalhostClustering(
- siloPort: 11111 + instanceId,
- gatewayPort: 30000 + instanceId,
- primarySiloEndpoint: new IPEndPoint(IPAddress.Loopback, 11111));
-
- siloBuilder.AddActivityPropagation();
-});
-builder.WebHost.UseKestrel((ctx, kestrelOptions) =>
-{
- // To avoid port conflicts, each Web server must listen on a different port.
- int instanceId = ctx.Configuration.GetValue("InstanceId");
- kestrelOptions.ListenLocalhost(5001 + instanceId);
-});
+builder.AddServiceDefaults();
+builder.AddKeyedAzureTableClient("clustering");
+builder.UseOrleans();
builder.Services.AddHostedService();
builder.Services.AddSignalR().AddJsonProtocol();
-WebApplication app = builder.Build();
+var app = builder.Build();
+
+
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
+
app.UseStaticFiles();
-app.UseDefaultFiles();
app.UseRouting();
app.UseAuthorization();
app.MapHub("/locationHub");
-app.MapPrometheusScrapingEndpoint();
-app.Run();
+app.MapDefaultEndpoints();
+await app.RunAsync();
diff --git a/orleans/GPSTracker/GPSTracker.ServiceDefaults/Extensions.cs b/orleans/GPSTracker/GPSTracker.ServiceDefaults/Extensions.cs
new file mode 100644
index 00000000000..08eb64cb6d9
--- /dev/null
+++ b/orleans/GPSTracker/GPSTracker.ServiceDefaults/Extensions.cs
@@ -0,0 +1,123 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.ServiceDiscovery;
+using OpenTelemetry;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace Microsoft.Extensions.Hosting;
+
+// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
+// This project should be referenced by each service project in your solution.
+// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
+public static class Extensions
+{
+ public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.ConfigureOpenTelemetry();
+
+ builder.AddDefaultHealthChecks();
+
+ builder.Services.AddServiceDiscovery();
+
+ builder.Services.ConfigureHttpClientDefaults(http =>
+ {
+ // Turn on resilience by default
+ http.AddStandardResilienceHandler();
+
+ // Turn on service discovery by default
+ http.AddServiceDiscovery();
+ });
+
+ // Uncomment the following to restrict the allowed schemes for service discovery.
+ // builder.Services.Configure(options =>
+ // {
+ // options.AllowedSchemes = ["https"];
+ // });
+
+ return builder;
+ }
+
+ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.Logging.AddOpenTelemetry(logging =>
+ {
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+ });
+
+ builder.Services.AddOpenTelemetry()
+ .WithMetrics(metrics =>
+ {
+ metrics.AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddRuntimeInstrumentation()
+ .AddMeter("Microsoft.Orleans");
+ })
+ .WithTracing(tracing =>
+ {
+ // Uncomment to see Orleans internal traces. Very noisy.
+ // tracing.AddSource("Microsoft.Orleans.Runtime");
+ tracing.AddSource("Microsoft.Orleans.Application");
+ tracing.AddSource(builder.Environment.ApplicationName)
+ .AddAspNetCoreInstrumentation()
+ // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
+ //.AddGrpcClientInstrumentation()
+ .AddHttpClientInstrumentation();
+ });
+
+ builder.AddOpenTelemetryExporters();
+
+ return builder;
+ }
+
+ private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
+
+ if (useOtlpExporter)
+ {
+ builder.Services.AddOpenTelemetry().UseOtlpExporter();
+ }
+
+ // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
+ //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
+ //{
+ // builder.Services.AddOpenTelemetry()
+ // .UseAzureMonitor();
+ //}
+
+ return builder;
+ }
+
+ public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.Services.AddHealthChecks()
+ // Add a default liveness check to ensure app is responsive
+ .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
+
+ return builder;
+ }
+
+ public static WebApplication MapDefaultEndpoints(this WebApplication app)
+ {
+ // Adding health checks endpoints to applications in non-development environments has security implications.
+ // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
+ if (app.Environment.IsDevelopment())
+ {
+ // All health checks must pass for app to be considered ready to accept traffic after starting
+ app.MapHealthChecks("/health");
+
+ // Only health checks tagged with the "live" tag must pass for app to be considered alive
+ app.MapHealthChecks("/alive", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("live")
+ });
+ }
+
+ return app;
+ }
+}
diff --git a/orleans/GPSTracker/GPSTracker.ServiceDefaults/GPSTracker.ServiceDefaults.csproj b/orleans/GPSTracker/GPSTracker.ServiceDefaults/GPSTracker.ServiceDefaults.csproj
new file mode 100644
index 00000000000..f2dd6c96753
--- /dev/null
+++ b/orleans/GPSTracker/GPSTracker.ServiceDefaults/GPSTracker.ServiceDefaults.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/orleans/GPSTracker/GPSTracker.sln b/orleans/GPSTracker/GPSTracker.sln
index 49a9b734b6e..93f60698606 100644
--- a/orleans/GPSTracker/GPSTracker.sln
+++ b/orleans/GPSTracker/GPSTracker.sln
@@ -21,6 +21,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
screenshot.jpeg = screenshot.jpeg
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GPSTracker.AppHost", "GPSTracker.AppHost\GPSTracker.AppHost.csproj", "{63BE60F4-5F4A-43B9-AB32-E9075AB17437}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GPSTracker.ServiceDefaults", "GPSTracker.ServiceDefaults\GPSTracker.ServiceDefaults.csproj", "{6776EAA8-7997-4C09-8765-5E4DB0AA2BFE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +43,14 @@ Global
{B3ECE7C7-E076-4868-B6E1-9AFD16F8FA1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3ECE7C7-E076-4868-B6E1-9AFD16F8FA1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3ECE7C7-E076-4868-B6E1-9AFD16F8FA1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {63BE60F4-5F4A-43B9-AB32-E9075AB17437}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63BE60F4-5F4A-43B9-AB32-E9075AB17437}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63BE60F4-5F4A-43B9-AB32-E9075AB17437}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63BE60F4-5F4A-43B9-AB32-E9075AB17437}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6776EAA8-7997-4C09-8765-5E4DB0AA2BFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6776EAA8-7997-4C09-8765-5E4DB0AA2BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6776EAA8-7997-4C09-8765-5E4DB0AA2BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6776EAA8-7997-4C09-8765-5E4DB0AA2BFE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/orleans/GPSTracker/README.md b/orleans/GPSTracker/README.md
index c69d69465ae..02e589cc85d 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 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later.
## Building the sample
@@ -54,42 +54,10 @@ To download and run the sample, follow these steps:
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).
-Open three terminal windows. In the first terminal window, run the following at the command prompt:
+## Run the solution
-```dotnetcli
-dotnet run --project GPSTracker.Service
-```
-
-In the second terminal, launch another instance of the host, specifying that it's the second host by passing an `InstanceId` value as follows:
-
-```dotnetcli
-dotnet run --project GPSTracker.Service -- --InstanceId 1
-```
+Start the `GPSTracker.AppHost` project in Visual Studio or from the command line:
-Now open a web browser to `http://localhost:5001/index.html`. At this point, there will be no points moving around the map.
-
-In the third terminal window, run the following at the command prompt to begin simulating devices:
-
-```dotnetcli
-dotnet run --project GPSTracker.FakeDeviceGateway
```
-
-Dots will appear on the map in the Web browser and begin moving randomly around the area.
-
-## Orleans observability
-
-If you're interested in observability, this sample includes some optional logging, metrics, and distributed tracing.
-
-```docker
-docker run -p 9090:9090 -v ..\dotnet\samples\GPSTracker\prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
-```
-
-```docker
-docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 14250:14250 -p 9411:9411 jaegertracing/all-in-one:1.22
-```
-
-OR zipkin:
-
-```docker
-docker run -p 9411:9411 openzipkin/zipkin
+dotnet run --project GPSTracker.AppHost
```