Skip to content

Commit d18d9b3

Browse files
authored
Add ClientManager usage to management sample (#167)
SignalR client output sample: ``` User 'User' with connection id 'CKZhiNk__nyv3Vrz8SnK_Q1448c44c1' connected. User: gets message from service: 'aaa' ``` MessagePublisher output sample: ``` *********Usage********* send user <User Id> <Message> send users <User Id List (Seperated by ',')> <Message> send group <Group Name> <Message> send groups <Group List (Seperated by ',')> <Message> usergroup add <User Id> <Group Name> usergroup remove <User Id> <Group Name> broadcast <Message> close <Connection ID> <Reason> checkexist connection <Connection ID> checkexist user <User ID> checkexist group <Group Name> *********************** > checkexist connection CKZhiNk__nyv3Vrz8SnK_Q1448c44c1 connection 'CKZhiNk__nyv3Vrz8SnK_Q1448c44c1' exists. > checkexist connection CKZhiNk__nyv3Vrz8SnK_Q1448c44c2 connection 'CKZhiNk__nyv3Vrz8SnK_Q1448c44c2' does not exist. > checkexist user User user 'User' exists. > send user User abc send message 'abc' to 'User' ```
1 parent abf1c3d commit d18d9b3

File tree

6 files changed

+107
-34
lines changed

6 files changed

+107
-34
lines changed

samples/Management/MessagePublisher/MessagePublisher.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ public Task SendMessages(string command, string receiver, string message)
7575
}
7676
}
7777

78+
public Task CloseConnection(string connectionId, string reason)
79+
{
80+
return _hubContext.ClientManager.CloseConnectionAsync(connectionId, reason);
81+
}
82+
83+
public Task<bool> CheckExist(string type, string id)
84+
{
85+
return type switch
86+
{
87+
"connection" => _hubContext.ClientManager.ConnectionExistsAsync(id),
88+
"user" => _hubContext.ClientManager.UserExistsAsync(id),
89+
"group" => _hubContext.ClientManager.UserExistsAsync(id),
90+
_ => throw new NotSupportedException(),
91+
};
92+
}
93+
7894
public Task DisposeAsync() => _hubContext?.DisposeAsync();
7995
}
8096
}

samples/Management/MessagePublisher/Program.cs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010

1111
namespace Microsoft.Azure.SignalR.Samples.Management
1212
{
13-
class Program
13+
public class Program
1414
{
15-
static void Main(string[] args)
15+
public static void Main(string[] args)
1616
{
17-
var app = new CommandLineApplication();
18-
app.FullName = "Azure SignalR Management Sample: Message Publisher";
17+
var app = new CommandLineApplication
18+
{
19+
FullName = "Azure SignalR Management Sample: Message Publisher"
20+
};
1921
app.HelpOption("--help");
2022
app.Description = "Message publisher using Azure SignalR Service Management SDK.";
2123

@@ -28,7 +30,7 @@ static void Main(string[] args)
2830
.Build();
2931

3032

31-
app.OnExecute(async() =>
33+
app.OnExecute(async () =>
3234
{
3335
var connectionString = connectionStringOption.Value() ?? configuration["Azure:SignalR:ConnectionString"];
3436

@@ -82,8 +84,8 @@ private static async Task StartAsync(MessagePublisher publisher)
8284

8385
if (args.Length == 2 && args[0].Equals("broadcast"))
8486
{
85-
Console.WriteLine($"broadcast message '{args[1]}'");
8687
await publisher.SendMessages(args[0], null, args[1]);
88+
Console.WriteLine($"broadcast message '{args[1]}'");
8789
}
8890
else if (args.Length == 4 && args[0].Equals("send"))
8991
{
@@ -96,10 +98,27 @@ private static async Task StartAsync(MessagePublisher publisher)
9698
var preposition = args[1] == "add" ? "to" : "from";
9799
Console.WriteLine($"{args[1]} user '{args[2]}' {preposition} group '{args[3]}'");
98100
}
101+
else if (args.Length == 3 && args[0] == "close")
102+
{
103+
await publisher.CloseConnection(args[1], args[2]);
104+
Console.WriteLine($"closed connection '{args[1]}' because '{args[2]}'");
105+
//If you want client side see the reason, you need to turn on 'EnableDetailedErrors' option during client negotiation.
106+
}
107+
else if (args.Length == 3 && args[0] == "checkexist")
108+
{
109+
var exist = await publisher.CheckExist(args[1].ToLowerInvariant(), args[2]);
110+
Console.WriteLine(exist ? "exists" : "not exist");
111+
}
112+
else if (args.Length == 2 && args[0] == "close")
113+
{
114+
await publisher.CloseConnection(args[1], null);
115+
Console.WriteLine("closed");
116+
}
99117
else
100118
{
101119
Console.WriteLine($"Can't recognize command {argLine}");
102120
}
121+
Console.Write("> ");
103122
}
104123
}
105124
finally
@@ -110,16 +129,21 @@ private static async Task StartAsync(MessagePublisher publisher)
110129

111130
private static void ShowHelp()
112131
{
113-
Console.WriteLine(
114-
"*********Usage*********\n" +
115-
"send user <User Id> <Message>\n" +
116-
"send users <User Id List (Seperated by ',')> <Message>\n" +
117-
"send group <Group Name> <Message>\n" +
118-
"send groups <Group List (Seperated by ',')> <Message>\n" +
119-
"usergroup add <User Id> <Group Name>\n" +
120-
"usergroup remove <User Id> <Group Name>\n" +
121-
"broadcast <Message>\n" +
122-
"***********************");
132+
Console.Write(
133+
@"*********Usage*********
134+
send user <UserId> <Message>
135+
send users <User1>,<User2>,... <Message>
136+
send group <GroupName> <Message>
137+
send groups <Group1>,<Group2>,... <Message>
138+
usergroup add <User1>,<User2>,... <GroupName>
139+
usergroup remove <UserId> <GroupName>
140+
broadcast <Message>
141+
close <ConnectionID> <Reason>?
142+
checkexist connection <ConnectionID>
143+
checkexist user <UserID>
144+
checkexist group <GroupName>
145+
***********************
146+
> ");
123147
}
124148

125149
private static void MissOptions()

samples/Management/NegotiationServer/Controllers/NegotiateController.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,23 @@
55
using System.Threading.Tasks;
66
using Microsoft.AspNetCore.Mvc;
77
using Microsoft.Azure.SignalR.Management;
8+
using Microsoft.Extensions.Configuration;
89

910
namespace NegotiationServer.Controllers
1011
{
1112
[ApiController]
1213
public class NegotiateController : ControllerBase
1314
{
15+
private const string EnableDetailedErrors = "EnableDetailedErrors";
1416
private readonly ServiceHubContext _messageHubContext;
1517
private readonly ServiceHubContext _chatHubContext;
18+
private readonly bool _enableDetailedErrors;
1619

17-
public NegotiateController(IHubContextStore store)
20+
public NegotiateController(IHubContextStore store, IConfiguration configuration)
1821
{
1922
_messageHubContext = store.MessageHubContext;
2023
_chatHubContext = store.ChatHubContext;
24+
_enableDetailedErrors = configuration.GetValue(EnableDetailedErrors, false);
2125
}
2226

2327
[HttpPost("message/negotiate")]
@@ -40,7 +44,11 @@ private async Task<ActionResult> NegotiateBase(string user, ServiceHubContext se
4044
return BadRequest("User ID is null or empty.");
4145
}
4246

43-
var negotiateResponse = await serviceHubContext.NegotiateAsync(new() { UserId = user });
47+
var negotiateResponse = await serviceHubContext.NegotiateAsync(new()
48+
{
49+
UserId = user,
50+
EnableDetailedErrors = _enableDetailedErrors
51+
});
4452

4553
return new JsonResult(new Dictionary<string, string>()
4654
{

samples/Management/NegotiationServer/README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,19 @@ namespace NegotiationServer.Controllers
3535

3636
### Create instance of `ServiceHubContext`
3737

38-
`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.
38+
`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.
3939

40-
In `SignalRService` class, create the `ServiceHubContext`.
40+
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.
4141

4242
```C#
4343
public async Task StartAsync(CancellationToken cancellationToken)
4444
{
4545
using var serviceManager = new ServiceManagerBuilder()
4646
.WithConfiguration(_configuration)
47-
//or .WithOptions(o=>o.ConnectionString = _configuration["Azure:SignalR:ConnectionString"]
4847
.WithLoggerFactory(_loggerFactory)
4948
.BuildServiceManager();
50-
HubContext = await serviceManager.CreateHubContextAsync(Hub, cancellationToken);
49+
MessageHubContext = await serviceManager.CreateHubContextAsync(MessageHub, cancellationToken);
50+
ChatHubContext = await serviceManager.CreateHubContextAsync(ChatHub, cancellationToken);
5151
}
5252
```
5353

@@ -63,7 +63,6 @@ public Task StopAsync(CancellationToken cancellationToken) => HubContext?.Dispos
6363
In the `NegotiateController` class, provide the negotiation endpoint `/negotiate?user=<User ID>`.
6464

6565
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.
66-
6766
```C#
6867
[HttpPost("negotiate")]
6968
public async Task<ActionResult> Index(string user)
@@ -83,6 +82,16 @@ public async Task<ActionResult> Index(string user)
8382
}
8483
```
8584

85+
The sample above uses the default negotiation options. If you want to return detailed error messages to clients, you can set `EnableDetailedErrors` as follows:
86+
87+
```C#
88+
var negotiateResponse = await serviceHubContext.NegotiateAsync(new()
89+
{
90+
UserId = user,
91+
EnableDetailedErrors = true
92+
});
93+
```
94+
> `EnableDetailedErrors` defaults to false because these exception messages can contain sensitive information.
8695
## Full Sample
8796

8897
The full negotiation server sample can be found [here](.). The usage of this sample can be found [here](<https://github.com/aspnet/AzureSignalR-samples/tree/master/samples/Management#start-the-negotiation-server>).

samples/Management/README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ dotnet user-secrets set Azure:SignalR:ConnectionString "<Connection String>"
1717
dotnet run
1818
```
1919

20+
> Parameters of `dotnet run`
21+
>
22+
> --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.
23+
2024
### Start SignalR clients
2125

2226
```
@@ -44,13 +48,17 @@ dotnet run
4448
Once the message publisher get started, use the command to send message
4549

4650
```
47-
send user <User ID List (Separated by ',')> <Message>
48-
send users <User List> <Message>
49-
send group <Group Name> <Message>
50-
send groups <Group List (Separated by ',')> <Message>
51-
usergroup add <User ID> <Group Name>
52-
usergroup remove <User ID> <Group Name>
51+
send user <UserId> <Message>
52+
send users <User1,User2,...> <Message>
53+
send group <GroupName> <Message>
54+
send groups <Group1,Group2,...> <Message>
55+
usergroup add <User1,User2,...> <GroupName>
56+
usergroup remove <UserId> <GroupName>
5357
broadcast <Message>
58+
close <ConnectionID> <Reason>?
59+
checkexist connection <ConnectionID>
60+
checkexist user <UserID>
61+
checkexist group <GroupName>
5462
```
5563
For example, type `broadcast hello`, and press keyboard `enter` to publish messages.
5664

samples/Management/SignalRClient/Program.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7-
using System.Runtime.CompilerServices;
87
using System.Threading.Tasks;
98
using Microsoft.AspNetCore.SignalR.Client;
109
using Microsoft.Extensions.CommandLineUtils;
@@ -15,7 +14,7 @@ class Program
1514
{
1615
private const string MessageHubEndpoint = "http://localhost:5000/Message";
1716
private const string Target = "Target";
18-
private const string DefaultUser = "User";
17+
private const string DefaultUser = "TestUser";
1918

2019
static void Main(string[] args)
2120
{
@@ -37,7 +36,10 @@ static void Main(string[] args)
3736
await Task.WhenAll(from conn in connections
3837
select conn.StartAsync());
3938

40-
Console.WriteLine($"{connections.Count} Client(s) started...");
39+
foreach (var (connection, userId) in connections.Zip(userIds))
40+
{
41+
Console.WriteLine($"User '{userId}' with connection id '{connection.ConnectionId}' connected.");
42+
}
4143
Console.ReadLine();
4244

4345
await Task.WhenAll(from conn in connections
@@ -59,8 +61,14 @@ static HubConnection CreateHubConnection(string hubEndpoint, string userId)
5961

6062
connection.Closed += ex =>
6163
{
62-
Console.WriteLine(ex);
63-
return Task.FromResult(0);
64+
Console.Write($"The connection of '{userId}' is closed.");
65+
//If you expect non-null exception, you need to turn on 'EnableDetailedErrors' option during client negotiation.
66+
if (ex != null)
67+
{
68+
Console.Write($" Exception: {ex}");
69+
}
70+
Console.WriteLine();
71+
return Task.CompletedTask;
6472
};
6573

6674
return connection;

0 commit comments

Comments
 (0)