diff --git a/samples/Management/MessagePublisher/MessagePublisher.cs b/samples/Management/MessagePublisher/MessagePublisher.cs index a79a49ec..39d36a7b 100644 --- a/samples/Management/MessagePublisher/MessagePublisher.cs +++ b/samples/Management/MessagePublisher/MessagePublisher.cs @@ -75,6 +75,22 @@ public Task SendMessages(string command, string receiver, string message) } } + public Task CloseConnection(string connectionId, string reason) + { + return _hubContext.ClientManager.CloseConnectionAsync(connectionId, reason); + } + + public Task CheckExist(string type, string id) + { + return type switch + { + "connection" => _hubContext.ClientManager.ConnectionExistsAsync(id), + "user" => _hubContext.ClientManager.UserExistsAsync(id), + "group" => _hubContext.ClientManager.UserExistsAsync(id), + _ => throw new NotSupportedException(), + }; + } + public Task DisposeAsync() => _hubContext?.DisposeAsync(); } } \ No newline at end of file diff --git a/samples/Management/MessagePublisher/Program.cs b/samples/Management/MessagePublisher/Program.cs index f9e02338..9bd22692 100644 --- a/samples/Management/MessagePublisher/Program.cs +++ b/samples/Management/MessagePublisher/Program.cs @@ -10,12 +10,14 @@ namespace Microsoft.Azure.SignalR.Samples.Management { - class Program + public class Program { - static void Main(string[] args) + public static void Main(string[] args) { - var app = new CommandLineApplication(); - app.FullName = "Azure SignalR Management Sample: Message Publisher"; + var app = new CommandLineApplication + { + FullName = "Azure SignalR Management Sample: Message Publisher" + }; app.HelpOption("--help"); app.Description = "Message publisher using Azure SignalR Service Management SDK."; @@ -28,7 +30,7 @@ static void Main(string[] args) .Build(); - app.OnExecute(async() => + app.OnExecute(async () => { var connectionString = connectionStringOption.Value() ?? configuration["Azure:SignalR:ConnectionString"]; @@ -82,8 +84,8 @@ private static async Task StartAsync(MessagePublisher publisher) if (args.Length == 2 && args[0].Equals("broadcast")) { - Console.WriteLine($"broadcast message '{args[1]}'"); await publisher.SendMessages(args[0], null, args[1]); + Console.WriteLine($"broadcast message '{args[1]}'"); } else if (args.Length == 4 && args[0].Equals("send")) { @@ -96,10 +98,27 @@ private static async Task StartAsync(MessagePublisher publisher) var preposition = args[1] == "add" ? "to" : "from"; Console.WriteLine($"{args[1]} user '{args[2]}' {preposition} group '{args[3]}'"); } + else if (args.Length == 3 && args[0] == "close") + { + await publisher.CloseConnection(args[1], args[2]); + Console.WriteLine($"closed connection '{args[1]}' because '{args[2]}'"); + //If you want client side see the reason, you need to turn on 'EnableDetailedErrors' option during client negotiation. + } + else if (args.Length == 3 && args[0] == "checkexist") + { + var exist = await publisher.CheckExist(args[1].ToLowerInvariant(), args[2]); + Console.WriteLine(exist ? "exists" : "not exist"); + } + else if (args.Length == 2 && args[0] == "close") + { + await publisher.CloseConnection(args[1], null); + Console.WriteLine("closed"); + } else { Console.WriteLine($"Can't recognize command {argLine}"); } + Console.Write("> "); } } finally @@ -110,16 +129,21 @@ private static async Task StartAsync(MessagePublisher publisher) private static void ShowHelp() { - Console.WriteLine( - "*********Usage*********\n" + - "send user \n" + - "send users \n" + - "send group \n" + - "send groups \n" + - "usergroup add \n" + - "usergroup remove \n" + - "broadcast \n" + - "***********************"); + Console.Write( +@"*********Usage********* +send user +send users ,,... +send group +send groups ,,... +usergroup add ,,... +usergroup remove +broadcast +close ? +checkexist connection +checkexist user +checkexist group +*********************** +> "); } private static void MissOptions() diff --git a/samples/Management/NegotiationServer/Controllers/NegotiateController.cs b/samples/Management/NegotiationServer/Controllers/NegotiateController.cs index ccae4b88..a4a5e108 100644 --- a/samples/Management/NegotiationServer/Controllers/NegotiateController.cs +++ b/samples/Management/NegotiationServer/Controllers/NegotiateController.cs @@ -5,19 +5,23 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.SignalR.Management; +using Microsoft.Extensions.Configuration; namespace NegotiationServer.Controllers { [ApiController] public class NegotiateController : ControllerBase { + private const string EnableDetailedErrors = "EnableDetailedErrors"; private readonly ServiceHubContext _messageHubContext; private readonly ServiceHubContext _chatHubContext; + private readonly bool _enableDetailedErrors; - public NegotiateController(IHubContextStore store) + public NegotiateController(IHubContextStore store, IConfiguration configuration) { _messageHubContext = store.MessageHubContext; _chatHubContext = store.ChatHubContext; + _enableDetailedErrors = configuration.GetValue(EnableDetailedErrors, false); } [HttpPost("message/negotiate")] @@ -40,7 +44,11 @@ private async Task NegotiateBase(string user, ServiceHubContext se return BadRequest("User ID is null or empty."); } - var negotiateResponse = await serviceHubContext.NegotiateAsync(new() { UserId = user }); + var negotiateResponse = await serviceHubContext.NegotiateAsync(new() + { + UserId = user, + EnableDetailedErrors = _enableDetailedErrors + }); return new JsonResult(new Dictionary() { diff --git a/samples/Management/NegotiationServer/README.md b/samples/Management/NegotiationServer/README.md index 39a1141c..c36660c8 100644 --- a/samples/Management/NegotiationServer/README.md +++ b/samples/Management/NegotiationServer/README.md @@ -35,19 +35,19 @@ namespace NegotiationServer.Controllers ### Create instance of `ServiceHubContext` -`ServiceHubContext` provides methods to generate client endpoints and access tokens for SignalR clients to connect to Azure SignalR Service. Wrap `ServiceHubContext` into a [`IHostedService`](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-5.0) called `SignalRService` so that `ServiceHubContext` can be started and disposed when the web host started and stopped. +`ServiceHubContext` provides methods to generate client endpoints and access tokens for SignalR clients to connect to Azure SignalR Service. Wrap `ServiceHubContext` into a [`IHostedService`](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-5.0) called `SignalRService` so that `ServiceHubContext` can be started and disposed when the web host starts and stops. -In `SignalRService` class, create the `ServiceHubContext`. +In `SignalRService` class, create the `ServiceHubContext`. In the sample we have two hub, message hub and chat hub to demostrate how to set up multiple hubs. The chat hub is actually not used. ```C# public async Task StartAsync(CancellationToken cancellationToken) { using var serviceManager = new ServiceManagerBuilder() .WithConfiguration(_configuration) - //or .WithOptions(o=>o.ConnectionString = _configuration["Azure:SignalR:ConnectionString"] .WithLoggerFactory(_loggerFactory) .BuildServiceManager(); - HubContext = await serviceManager.CreateHubContextAsync(Hub, cancellationToken); + MessageHubContext = await serviceManager.CreateHubContextAsync(MessageHub, cancellationToken); + ChatHubContext = await serviceManager.CreateHubContextAsync(ChatHub, cancellationToken); } ``` @@ -63,7 +63,6 @@ public Task StopAsync(CancellationToken cancellationToken) => HubContext?.Dispos In the `NegotiateController` class, provide the negotiation endpoint `/negotiate?user=`. We use the `_hubContext` to generate a client endpoint and an access token and return to SignalR client following [Negotiation Protocol](https://github.com/aspnet/SignalR/blob/master/specs/TransportProtocols.md#post-endpoint-basenegotiate-request), which will redirect the SignalR client to the service. - ```C# [HttpPost("negotiate")] public async Task Index(string user) @@ -83,6 +82,16 @@ public async Task Index(string user) } ``` +The sample above uses the default negotiation options. If you want to return detailed error messages to clients, you can set `EnableDetailedErrors` as follows: + +```C# +var negotiateResponse = await serviceHubContext.NegotiateAsync(new() +{ + UserId = user, + EnableDetailedErrors = true +}); +``` +> `EnableDetailedErrors` defaults to false because these exception messages can contain sensitive information. ## Full Sample The full negotiation server sample can be found [here](.). The usage of this sample can be found [here](). \ No newline at end of file diff --git a/samples/Management/README.md b/samples/Management/README.md index 2885346e..6326699f 100644 --- a/samples/Management/README.md +++ b/samples/Management/README.md @@ -17,6 +17,10 @@ dotnet user-secrets set Azure:SignalR:ConnectionString "" dotnet run ``` +> Parameters of `dotnet run` +> +> --enableDetailedErrors: true to enable log detailed errors on client side, false to disable. The default value is false, as detailed errors might contain sensitive information. This is useful if you want client connection to get the exception on close. + ### Start SignalR clients ``` @@ -44,13 +48,17 @@ dotnet run Once the message publisher get started, use the command to send message ``` -send user -send users -send group -send groups -usergroup add -usergroup remove +send user +send users +send group +send groups +usergroup add +usergroup remove broadcast +close ? +checkexist connection +checkexist user +checkexist group ``` For example, type `broadcast hello`, and press keyboard `enter` to publish messages. diff --git a/samples/Management/SignalRClient/Program.cs b/samples/Management/SignalRClient/Program.cs index 8d29b943..060dcb22 100644 --- a/samples/Management/SignalRClient/Program.cs +++ b/samples/Management/SignalRClient/Program.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.CommandLineUtils; @@ -15,7 +14,7 @@ class Program { private const string MessageHubEndpoint = "http://localhost:5000/Message"; private const string Target = "Target"; - private const string DefaultUser = "User"; + private const string DefaultUser = "TestUser"; static void Main(string[] args) { @@ -37,7 +36,10 @@ static void Main(string[] args) await Task.WhenAll(from conn in connections select conn.StartAsync()); - Console.WriteLine($"{connections.Count} Client(s) started..."); + foreach (var (connection, userId) in connections.Zip(userIds)) + { + Console.WriteLine($"User '{userId}' with connection id '{connection.ConnectionId}' connected."); + } Console.ReadLine(); await Task.WhenAll(from conn in connections @@ -59,8 +61,14 @@ static HubConnection CreateHubConnection(string hubEndpoint, string userId) connection.Closed += ex => { - Console.WriteLine(ex); - return Task.FromResult(0); + Console.Write($"The connection of '{userId}' is closed."); + //If you expect non-null exception, you need to turn on 'EnableDetailedErrors' option during client negotiation. + if (ex != null) + { + Console.Write($" Exception: {ex}"); + } + Console.WriteLine(); + return Task.CompletedTask; }; return connection;