diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml
index 888c1ad83..e8d458f62 100644
--- a/.github/workflows/osx.yml
+++ b/.github/workflows/osx.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: macOS-latest
strategy:
matrix:
- dotnet-version: [ '8.0.x', '7.0.x', '6.0.x' ]
+ dotnet-version: [ '8.0.x' ]
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
@@ -41,6 +41,8 @@ jobs:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Display dotnet version
run: dotnet --version
+ - name: Install aspire workload
+ run: dotnet workload install aspire
- name: Build with dotnet
run: |
if [ "${{ matrix.dotnet-version }}" != "6.0.x" ]; then
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index 6038f58bb..53176cb0d 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- dotnet-version: [ '8.0.x', '7.0.x', '6.0.x' ]
+ dotnet-version: [ '8.0.x' ]
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
@@ -41,6 +41,8 @@ jobs:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Display dotnet version
run: dotnet --version
+ - name: Install aspire workload
+ run: dotnet workload install aspire
- name: Build with dotnet
run: "dotnet build AzureSignalR.sln /p:DisableNet461Tests=true"
if: steps.filter.outputs.src == 'true'
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index ff7a16719..d5b1e6d38 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: [windows-latest]
strategy:
matrix:
- dotnet-version: [ '8.0.x', '7.0.x', '6.0.x' ]
+ dotnet-version: [ '8.0.x' ]
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
@@ -41,6 +41,8 @@ jobs:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Display dotnet version
run: dotnet --version
+ - name: Install aspire workload
+ run: dotnet workload install aspire
- name: Build with dotnet
run: "dotnet build AzureSignalR.sln"
if: steps.filter.outputs.src == 'true'
diff --git a/AzureSignalR.sln b/AzureSignalR.sln
index 8d752e0da..679e19af8 100644
--- a/AzureSignalR.sln
+++ b/AzureSignalR.sln
@@ -89,6 +89,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatSample.Net70", "samples
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagementPublisher", "samples\ChatSample\ChatSample.ManagementPublisher\ManagementPublisher.csproj", "{0F32E624-7AC8-4CA7-8ED9-E1A877442020}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.AppHost", "samples\Samples.AppHost\Samples.AppHost.csproj", "{66AA925F-2FFC-4CBF-906F-5BEDC1010062}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -203,6 +205,10 @@ Global
{0F32E624-7AC8-4CA7-8ED9-E1A877442020}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F32E624-7AC8-4CA7-8ED9-E1A877442020}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F32E624-7AC8-4CA7-8ED9-E1A877442020}.Release|Any CPU.Build.0 = Release|Any CPU
+ {66AA925F-2FFC-4CBF-906F-5BEDC1010062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {66AA925F-2FFC-4CBF-906F-5BEDC1010062}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {66AA925F-2FFC-4CBF-906F-5BEDC1010062}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {66AA925F-2FFC-4CBF-906F-5BEDC1010062}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -237,6 +243,7 @@ Global
{594EC59A-7305-4A36-8BE6-4A928FBFD71B} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
{49634EE4-A0F4-4672-A8B3-B994CF81C9AB} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
{0F32E624-7AC8-4CA7-8ED9-E1A877442020} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
+ {66AA925F-2FFC-4CBF-906F-5BEDC1010062} = {C4BC9889-B49F-41B6-806B-F84941B2549B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
diff --git a/build/dependencies.private.props b/build/dependencies.private.props
index 2313d5a43..91d496ffe 100644
--- a/build/dependencies.private.props
+++ b/build/dependencies.private.props
@@ -23,5 +23,9 @@
2.2.0
2.2.0
E2eTestUserSecret
+
+ 8.1.0
+ 8.1.0
+
diff --git a/samples/ChatSample/ChatSample.CSharpClient/Program.cs b/samples/ChatSample/ChatSample.CSharpClient/Program.cs
index 0c23d79c8..7852ff08f 100644
--- a/samples/ChatSample/ChatSample.CSharpClient/Program.cs
+++ b/samples/ChatSample/ChatSample.CSharpClient/Program.cs
@@ -12,35 +12,49 @@ class Program
{
static async Task Main(string[] args)
{
- var url = "http://localhost:5050";
+ var url = Environment.GetEnvironmentVariable("ServerEndpoint") ?? "http://localhost:5050";
+ Enum.TryParse(Environment.GetEnvironmentVariable("MODE"), out var mode);
var proxy = await ConnectAsync(url + "/chat", Console.Out);
var currentUser = Guid.NewGuid().ToString("N");
- Mode mode = Mode.Broadcast;
if (args.Length > 0)
{
Enum.TryParse(args[0], true, out mode);
}
Console.WriteLine($"Logged in as user {currentUser}");
- var input = Console.ReadLine();
- while (!string.IsNullOrEmpty(input))
+ if (mode == Mode.Auto)
{
- switch (mode)
+ // auto mode
+ while (true)
{
- case Mode.Broadcast:
- await proxy.InvokeAsync("BroadcastMessage", currentUser, input);
- break;
- case Mode.Echo:
- await proxy.InvokeAsync("echo", input);
- break;
- default:
- break;
+ Console.WriteLine("Broadcasting...");
+ await proxy.InvokeAsync("BroadcastMessage", currentUser, $"Current time: {DateTime.Now}");
+ await Task.Delay(5000);
}
+ }
+ else
+ {
+ var input = Console.ReadLine();
+ while (!string.IsNullOrEmpty(input))
+ {
+ switch (mode)
+ {
+ case Mode.Broadcast:
+ await proxy.InvokeAsync("BroadcastMessage", currentUser, input);
+ break;
+ case Mode.Echo:
+ await proxy.InvokeAsync("echo", input);
+ break;
+ default:
+ break;
+ }
- input = Console.ReadLine();
+ input = Console.ReadLine();
+ }
}
}
+
private static async Task ConnectAsync(string url, TextWriter output, CancellationToken cancellationToken = default)
{
var connection = new HubConnectionBuilder()
@@ -101,6 +115,7 @@ private enum Mode
{
Broadcast,
Echo,
+ Auto
}
}
}
diff --git a/samples/ChatSample/ChatSample.CSharpClient/Properties/launchSettings.json b/samples/ChatSample/ChatSample.CSharpClient/Properties/launchSettings.json
new file mode 100644
index 000000000..3c9a31458
--- /dev/null
+++ b/samples/ChatSample/ChatSample.CSharpClient/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "profiles": {
+ "ChatSample.CSharpClient": {
+ "commandName": "Project"
+ },
+ "auto": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "MODE": "Auto"
+ },
+ "distributionName": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/ChatSample/ChatSample.Net60/Program.cs b/samples/ChatSample/ChatSample.Net60/Program.cs
index 3a6c08868..4463623c5 100644
--- a/samples/ChatSample/ChatSample.Net60/Program.cs
+++ b/samples/ChatSample/ChatSample.Net60/Program.cs
@@ -5,7 +5,8 @@
// Add services to the container.
builder.Services.AddRazorPages();
-builder.Services.AddSignalR().AddAzureSignalR();
+builder.Services.AddSignalR()
+ .AddNamedAzureSignalR("signalr1");
// uncomment for streaming outside the scope
// builder.Services.AddHostedService();
diff --git a/samples/ChatSample/ChatSample.Net60/README.md b/samples/ChatSample/ChatSample.Net60/README.md
new file mode 100644
index 000000000..5d0a85d07
--- /dev/null
+++ b/samples/ChatSample/ChatSample.Net60/README.md
@@ -0,0 +1,41 @@
+# Run the samples
+
+[.NET Aspire](https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview#orchestration) is used to orchestrate the samples.
+
+## Run with aspire ready in visual studio
+
+To work with .NET Aspire, you need the following installed locally:
+- [.NET 8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
+- .NET Aspire workload:
+ - Installed with the [Visual Studio installer](../fundamentals/setup-tooling.md?tabs=visual-studio#install-net-aspire) or [the .NET CLI workload](../fundamentals/setup-tooling.md?tabs=dotnet-cli#install-net-aspire).
+- An OCI compliant container runtime, such as:
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop) or [Podman](https://podman.io/).
+
+In Visual Studio, set **samples/Samples.AppHost** project as the Startup Project. Right click **Connected Services** and select **Azure Resource Provisioning Settings** and select your Azure subscription, region and resource group to use.
+
+![Azure Resource Provisiong Settings](../../images/add-azure-provisioning.png)
+
+Alternatively, you could add Azure related configurations in the appsettings.json file:
+ ```json
+ {
+ "Azure": {
+ "SubscriptionId": "your subscription",
+ "Location": "your location"
+ }
+ }
+ ```
+
+Run the project and use Aspire dashboard to navigate to different samples:
+![Aspire Dashboard](../../images/aspire-dashboard.png)
+
+## Run without aspire
+
+Aspire helps you to automatically provision a new Azure SignalR resource and set the connection strings for the sample to use automatically. You could still use the traditional way to set the connection strings by yourself and run the sample directly. Samples now use named connection string `AddNamedAzureSignalR("signalr1")`. Set your connection string to `signalr1:Azure:SignalR:ConnectionString`, or `ConnectionStrings:signalr1`:
+
+```
+dotnet user-secrets set Azure:SignalR:signalr1:ConnectionString ""
+```
+
+```
+dotnet user-secrets set ConnectionStrings:signalr1 ""
+```
\ No newline at end of file
diff --git a/samples/ChatSample/ChatSample.Net70/Program.cs b/samples/ChatSample/ChatSample.Net70/Program.cs
index ad111ca1f..2c4456f71 100644
--- a/samples/ChatSample/ChatSample.Net70/Program.cs
+++ b/samples/ChatSample/ChatSample.Net70/Program.cs
@@ -8,7 +8,7 @@
builder.Services.AddSignalR(o =>
{
o.MaximumParallelInvocationsPerClient = 2;
-}).AddAzureSignalR("");
+}).AddNamedAzureSignalR("signalr1");
var app = builder.Build();
diff --git a/samples/ChatSample/ChatSample.Net70/README.md b/samples/ChatSample/ChatSample.Net70/README.md
index 5d4ca9002..6cb8d5fcf 100644
--- a/samples/ChatSample/ChatSample.Net70/README.md
+++ b/samples/ChatSample/ChatSample.Net70/README.md
@@ -4,18 +4,46 @@ Requires NET7.0 Preview7 or later SDK/Runtime. Please installed from https://dot
### Usage
-Update `Program.cs` file of below lines to replace the placeholder `` with your Azure SignalR Service connection string.
+[.NET Aspire](https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview#orchestration) is used to orchestrate the samples.
-```cs
-builder.Services.AddSignalR(o =>
-{
- o.MaximumParallelInvocationsPerClient = 2;
-}).AddAzureSignalR("");
-```
+#### Run with aspire ready in visual studio
+
+To work with .NET Aspire, you need the following installed locally:
+- [.NET 8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
+- .NET Aspire workload:
+ - Installed with the [Visual Studio installer](../fundamentals/setup-tooling.md?tabs=visual-studio#install-net-aspire) or [the .NET CLI workload](../fundamentals/setup-tooling.md?tabs=dotnet-cli#install-net-aspire).
+- An OCI compliant container runtime, such as:
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop) or [Podman](https://podman.io/).
+
+In Visual Studio, set **samples/Samples.AppHost** project as the Startup Project. Right click **Connected Services** and select **Azure Resource Provisioning Settings** and select your Azure subscription, region and resource group to use.
+
+![Azure Resource Provisiong Settings](../../images/add-azure-provisioning.png)
+
+Alternatively, you could add Azure related configurations in the appsettings.json file:
+ ```json
+ {
+ "Azure": {
+ "SubscriptionId": "your subscription",
+ "Location": "your location"
+ }
+ }
+ ```
+
+Run the project and use Aspire dashboard to navigate to different samples:
+
+![Aspire Dashboard](../../images/aspire-dashboard.png)
-Run dotnet command to start the server.
+#### Run without aspire
-```bash
+Aspire helps you to automatically provision a new Azure SignalR resource and set the connection strings for the sample to use automatically. You could still use the traditional way to set the connection strings by yourself and run the sample directly. Samples now use named connection string `AddNamedAzureSignalR("signalr1")`. Set your connection string to `signalr1:Azure:SignalR:ConnectionString`, or `ConnectionStrings:signalr1`:
+
+```
+dotnet user-secrets set Azure:SignalR:signalr1:ConnectionString ""
+dotnet run
+```
+
+```
+dotnet user-secrets set ConnectionStrings:signalr1 ""
dotnet run
```
diff --git a/samples/ChatSample/ChatSample/README.md b/samples/ChatSample/ChatSample/README.md
new file mode 100644
index 000000000..5bb373bba
--- /dev/null
+++ b/samples/ChatSample/ChatSample/README.md
@@ -0,0 +1,42 @@
+# Run the samples
+
+[.NET Aspire](https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview#orchestration) is used to orchestrate the samples.
+
+## Run with aspire ready in visual studio
+
+To work with .NET Aspire, you need the following installed locally:
+- [.NET 8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
+- .NET Aspire workload:
+ - Installed with the [Visual Studio installer](../fundamentals/setup-tooling.md?tabs=visual-studio#install-net-aspire) or [the .NET CLI workload](../fundamentals/setup-tooling.md?tabs=dotnet-cli#install-net-aspire).
+- An OCI compliant container runtime, such as:
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop) or [Podman](https://podman.io/).
+
+In Visual Studio, set **samples/Samples.AppHost** project as the Startup Project. Right click **Connected Services** and select **Azure Resource Provisioning Settings** and select your Azure subscription, region and resource group to use.
+
+![Azure Resource Provisiong Settings](../../images/add-azure-provisioning.png)
+
+Alternatively, you could add Azure related configurations in the appsettings.json file:
+ ```json
+ {
+ "Azure": {
+ "SubscriptionId": "your subscription",
+ "Location": "your location"
+ }
+ }
+ ```
+
+Run the project and use Aspire dashboard to navigate to different samples:
+
+![Aspire Dashboard](../../images/aspire-dashboard.png)
+
+## Run without aspire
+
+Aspire helps you to automatically provision a new Azure SignalR resource and set the connection strings for the sample to use automatically. You could still use the traditional way to set the connection strings by yourself and run the sample directly. Samples now use named connection string `AddNamedAzureSignalR("signalr1")`. Set your connection string to `signalr1:Azure:SignalR:ConnectionString`, or `ConnectionStrings:signalr1`:
+
+```
+dotnet user-secrets set Azure:SignalR:signalr1:ConnectionString ""
+```
+
+```
+dotnet user-secrets set ConnectionStrings:signalr1 ""
+```
\ No newline at end of file
diff --git a/samples/ChatSample/ChatSample/Startup.cs b/samples/ChatSample/ChatSample/Startup.cs
index 9d62184db..7c55bb65f 100644
--- a/samples/ChatSample/ChatSample/Startup.cs
+++ b/samples/ChatSample/ChatSample/Startup.cs
@@ -15,17 +15,19 @@ public class Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
- services.AddSignalR()
- .AddAzureSignalR(option =>
- {
- option.GracefulShutdown.Mode = GracefulShutdownMode.WaitForClientsClose;
- option.GracefulShutdown.Timeout = TimeSpan.FromSeconds(30);
+ var builder = services.AddSignalR()
+ .AddNamedAzureSignalR("signalr1");
+ builder.Services.Configure(option =>
+ {
+ option.GracefulShutdown.Mode = GracefulShutdownMode.WaitForClientsClose;
+ option.GracefulShutdown.Timeout = TimeSpan.FromSeconds(30);
- option.GracefulShutdown.Add(async (c) =>
- {
- await c.Clients.All.SendAsync("exit");
- });
- })
+ option.GracefulShutdown.Add(async (c) =>
+ {
+ await c.Clients.All.SendAsync("exit");
+ });
+ });
+ builder
.AddMessagePackProtocol();
}
diff --git a/samples/Samples.AppHost/Program.cs b/samples/Samples.AppHost/Program.cs
new file mode 100644
index 000000000..206de84f6
--- /dev/null
+++ b/samples/Samples.AppHost/Program.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Projects;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+#pragma warning disable AZPROVISION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+var signalr = builder.AddAzureSignalR("signalr1", (_, _, k) => k.AssignProperty(i => i.Sku.Name, "'Standard_S1'"));
+#pragma warning restore AZPROVISION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+var chatServer = builder.AddProject("chat").WithReference(signalr).WithExternalHttpEndpoints().WithHttpsEndpoint();
+
+builder.AddProject("csharp-client-for-chat", "auto")
+ .WithEnvironment("ServerEndpoint", chatServer.GetEndpoint("https"));
+
+builder.AddProject("chat-net6").WithReference(signalr).WithExternalHttpEndpoints();
+builder.AddProject("chat-net7-client-invocation").WithReference(signalr).WithExternalHttpEndpoints();
+
+builder.Build().Run();
\ No newline at end of file
diff --git a/samples/Samples.AppHost/Properties/launchSettings.json b/samples/Samples.AppHost/Properties/launchSettings.json
new file mode 100644
index 000000000..6bff53917
--- /dev/null
+++ b/samples/Samples.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:17064;http://localhost:15259",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21254",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22039"
+ }
+ },
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15259",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19030",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20189"
+ }
+ }
+ }
+}
diff --git a/samples/Samples.AppHost/Samples.AppHost.csproj b/samples/Samples.AppHost/Samples.AppHost.csproj
new file mode 100644
index 000000000..0c7f15edf
--- /dev/null
+++ b/samples/Samples.AppHost/Samples.AppHost.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+ 1d977abc-8856-44ed-a28a-f31caa2e7174
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Samples.AppHost/appsettings.Development.json b/samples/Samples.AppHost/appsettings.Development.json
new file mode 100644
index 000000000..0c208ae91
--- /dev/null
+++ b/samples/Samples.AppHost/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/samples/Samples.AppHost/appsettings.json b/samples/Samples.AppHost/appsettings.json
new file mode 100644
index 000000000..31c092aa4
--- /dev/null
+++ b/samples/Samples.AppHost/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/samples/images/add-azure-provisioning.png b/samples/images/add-azure-provisioning.png
new file mode 100644
index 000000000..2a33c6e53
Binary files /dev/null and b/samples/images/add-azure-provisioning.png differ
diff --git a/samples/images/aspire-dashboard.png b/samples/images/aspire-dashboard.png
new file mode 100644
index 000000000..7d3b4d39d
Binary files /dev/null and b/samples/images/aspire-dashboard.png differ