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