Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/osx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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'
Expand Down
7 changes: 7 additions & 0 deletions AzureSignalR.sln
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatSample.RazorPages", "sa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatSample.Cli", "samples\ChatSample.Cli\ChatSample.Cli.csproj", "{45EFE0AE-DE2F-EED2-55FC-9213D2AB9F31}"
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
Expand Down Expand Up @@ -203,6 +205,10 @@ Global
{45EFE0AE-DE2F-EED2-55FC-9213D2AB9F31}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45EFE0AE-DE2F-EED2-55FC-9213D2AB9F31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45EFE0AE-DE2F-EED2-55FC-9213D2AB9F31}.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
Expand Down Expand Up @@ -237,6 +243,7 @@ Global
{0F32E624-7AC8-4CA7-8ED9-E1A877442020} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
{D7A38BB7-6416-4E15-AD87-D525F203F549} = {C965ED06-6A17-4329-B3C6-811830F4F4ED}
{45EFE0AE-DE2F-EED2-55FC-9213D2AB9F31} = {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}
Expand Down
4 changes: 4 additions & 0 deletions build/dependencies.private.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@
<MicrosoftConfigurationUserSecretVersion>2.2.0</MicrosoftConfigurationUserSecretVersion>
<MicrosoftConfigurationEnvironmentVariablesVersion>2.2.0</MicrosoftConfigurationEnvironmentVariablesVersion>
<E2eTestUserSecretId>E2eTestUserSecret</E2eTestUserSecretId>

<AspireHostingAppHostPackageVersion>13.1.0</AspireHostingAppHostPackageVersion>
<AspireHostingAzureSignalRPackageVersion>13.1.0</AspireHostingAzureSignalRPackageVersion>

</PropertyGroup>
</Project>
64 changes: 50 additions & 14 deletions samples/ChatSample/ChatSample.CSharpClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,68 @@ sealed class Program
{
static async Task Main(string[] args)
{
var url = "http://localhost:5050";
var url = Environment.GetEnvironmentVariable("ServerEndpoint") ?? "http://localhost:5050";
var mode = Mode.Broadcast;

// Try to parse mode from environment variable
var modeEnv = Environment.GetEnvironmentVariable("MODE");
if (!string.IsNullOrEmpty(modeEnv))
{
Enum.TryParse<Mode>(modeEnv, true, out mode);
}

var proxy = await ConnectAsync(url + "/chat", Console.Out).ConfigureAwait(false);
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 - runs until process is terminated
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
cts.Cancel();
};

try
{
while (!cts.Token.IsCancellationRequested)
{
Console.WriteLine("Broadcasting...");
await proxy.InvokeAsync("BroadcastMessage", currentUser, $"Current time: {DateTime.Now}").ConfigureAwait(false);
await Task.Delay(5000, cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
case Mode.Broadcast:
await proxy.InvokeAsync("BroadcastMessage", currentUser, input).ConfigureAwait(false);
break;
case Mode.Echo:
await proxy.InvokeAsync("echo", input).ConfigureAwait(false);
break;
default:
break;
Console.WriteLine("Auto mode cancelled.");
}
}
else
{
var input = Console.ReadLine();
while (!string.IsNullOrEmpty(input))
{
switch (mode)
{
case Mode.Broadcast:
await proxy.InvokeAsync("BroadcastMessage", currentUser, input).ConfigureAwait(false);
break;
case Mode.Echo:
await proxy.InvokeAsync("echo", input).ConfigureAwait(false);
break;
default:
break;
}

input = Console.ReadLine();
input = Console.ReadLine();
}
}
}
private static async Task<HubConnection> ConnectAsync(string url, TextWriter output, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -105,6 +140,7 @@ private enum Mode
{
Broadcast,
Echo,
Auto
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"profiles": {
"ChatSample.CSharpClient": {
"commandName": "Project"
},
"auto": {
"commandName": "Project",
"environmentVariables": {
"MODE": "Auto"
},
"distributionName": ""
}
}
}
69 changes: 44 additions & 25 deletions samples/ChatSample/ChatSample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,48 @@ This sample demonstrates how to use Azure SignalR Service with ASP.NET Core Sign
- Git (for submodule dependencies)
- Docker (optional, for containerized deployment)

## Setup
## Running the Sample

1. Initialize the required submodules:
### Option 1: Run with .NET Aspire (Recommended)

[.NET Aspire](https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview#orchestration) is used to orchestrate the samples.

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](https://learn.microsoft.com/dotnet/aspire/fundamentals/setup-tooling?tabs=visual-studio#install-net-aspire) or [the .NET CLI workload](https://learn.microsoft.com/dotnet/aspire/fundamentals/setup-tooling?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.

Alternatively, you could add Azure related configurations in the appsettings.json file:
```json
{
"Azure": {
"SubscriptionId": "your subscription",
"Location": "your location"
}
}
```

```bash
git submodule update --init --recursive
```
Run the project and use Aspire dashboard to navigate to different samples.

2. Configure your Azure SignalR Service connection string:
### Option 2: Run without Aspire

- Update `appsettings.json` by replacing the empty connection string:
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 `Azure:SignalR:signalr1:ConnectionString`, or `ConnectionStrings:signalr1`:

```json
{
"Azure": {
"SignalR": {
"ConnectionString": "<your-connection-string>"
}
}
}
```
```bash
dotnet user-secrets set Azure:SignalR:signalr1:ConnectionString "<Your connection string>"
```

⚠️ **Important**: Make sure to set your connection string before building the Docker image or running the application.
Or:

## Running the Sample

### Option 1: Running Locally
```bash
dotnet user-secrets set ConnectionStrings:signalr1 "<Your connection string>"
```

1. Build and run the project:
Then build and run:

```bash
dotnet build
Expand All @@ -50,16 +63,22 @@ You can also specify a custom port:
dotnet run --urls="http://localhost:5050"
```

### Option 2: Running with Docker
### Option 3: Running with Docker

1. Initialize the required submodules:

```bash
git submodule update --init --recursive
```

1. Build the Docker image:
2. Build the Docker image:
```bash
docker build -t chat-app -f samples/ChatSample/ChatSample/Dockerfile .
```

2. Run the container:
3. Run the container:
```bash
docker run -d -p 5050:5050 chat-app
docker run -d -p 5050:5050 -e "ConnectionStrings__signalr1=<your-connection-string>" chat-app
```

Additional Docker commands:
Expand Down
29 changes: 12 additions & 17 deletions samples/ChatSample/ChatSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,19 @@ private enum AuthTypes
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSignalR()
.AddAzureSignalR(option =>
var builder = services.AddSignalR()
.AddNamedAzureSignalR("signalr1");
builder.Services.Configure<ServiceOptions>(option =>
{
option.GracefulShutdown.Mode = GracefulShutdownMode.WaitForClientsClose;
option.GracefulShutdown.Timeout = TimeSpan.FromSeconds(30);

option.GracefulShutdown.Add<ChatHub>(async (c) =>
{
TokenCredential credential = AuthType switch
{
AuthTypes.VisualStudio => new VisualStudioCodeCredential(),
AuthTypes.ApplicationWithCertificate => new ClientCertificateCredential(TenantId, AppClientId, "path-to-cert-file"),
AuthTypes.ApplicationWithClientSecret => new ClientSecretCredential(TenantId, AppClientId, "client-secret-value"),
AuthTypes.ApplicationWithFederatedIdentity => GetClientAssertionCredential(TenantId, AppClientId, MsiClientId),
AuthTypes.SystemAssignedManagedIdentity => new ManagedIdentityCredential(),
AuthTypes.UserAssignedManagedIdentity => new ManagedIdentityCredential(MsiClientId),
_ => throw new NotImplementedException(),
};

option.Endpoints = [
new ServiceEndpoint(new Uri(Endpoint), credential)
];
})
await c.Clients.All.SendAsync("exit");
});
});
builder
.AddMessagePackProtocol();
}

Expand Down
20 changes: 20 additions & 0 deletions samples/Samples.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -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);

// Configure Azure SignalR resource with Standard S1 SKU
// Note: The AssignProperty API for Azure resource customization is currently in preview
// and may change in future Aspire updates. This is used here to demonstrate SKU configuration.
#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<ChatSample>("chat").WithReference(signalr).WithExternalHttpEndpoints().WithHttpsEndpoint();

builder.AddProject<ChatSample_CSharpClient>("csharp-client-for-chat", "auto")
.WithEnvironment("ServerEndpoint", chatServer.GetEndpoint("https"));

builder.Build().Run();
29 changes: 29 additions & 0 deletions samples/Samples.AppHost/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
23 changes: 23 additions & 0 deletions samples/Samples.AppHost/Samples.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>1d977abc-8856-44ed-a28a-f31caa2e7174</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="$(AspireHostingAppHostPackageVersion)" />
<PackageReference Include="Aspire.Hosting.Azure.SignalR" Version="$(AspireHostingAzureSignalRPackageVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BlazorAppSample\BlazorAppSample.csproj" />
<ProjectReference Include="..\ChatSample\ChatSample.CSharpClient\ChatSample.CSharpClient.csproj" />
<ProjectReference Include="..\ChatSample\ChatSample\ChatSample.csproj" />
</ItemGroup>

</Project>
8 changes: 8 additions & 0 deletions samples/Samples.AppHost/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/Samples.AppHost/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
Loading