Skip to content
Merged
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
16 changes: 16 additions & 0 deletions samples/Management/MessagePublisher/MessagePublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> 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();
}
}
56 changes: 40 additions & 16 deletions samples/Management/MessagePublisher/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.";

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


app.OnExecute(async() =>
app.OnExecute(async () =>
{
var connectionString = connectionStringOption.Value() ?? configuration["Azure:SignalR:ConnectionString"];

Expand Down Expand Up @@ -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"))
{
Expand All @@ -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
Expand All @@ -110,16 +129,21 @@ private static async Task StartAsync(MessagePublisher publisher)

private static void ShowHelp()
{
Console.WriteLine(
"*********Usage*********\n" +
"send user <User Id> <Message>\n" +
"send users <User Id List (Seperated by ',')> <Message>\n" +
"send group <Group Name> <Message>\n" +
"send groups <Group List (Seperated by ',')> <Message>\n" +
"usergroup add <User Id> <Group Name>\n" +
"usergroup remove <User Id> <Group Name>\n" +
"broadcast <Message>\n" +
"***********************");
Console.Write(
@"*********Usage*********
send user <UserId> <Message>
send users <User1>,<User2>,... <Message>
send group <GroupName> <Message>
send groups <Group1>,<Group2>,... <Message>
usergroup add <User1>,<User2>,... <GroupName>
usergroup remove <UserId> <GroupName>
broadcast <Message>
close <ConnectionID> <Reason>?
checkexist connection <ConnectionID>
checkexist user <UserID>
checkexist group <GroupName>
***********************
> ");
}

private static void MissOptions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -40,7 +44,11 @@ private async Task<ActionResult> 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<string, string>()
{
Expand Down
19 changes: 14 additions & 5 deletions samples/Management/NegotiationServer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
```

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

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<ActionResult> Index(string user)
Expand All @@ -83,6 +82,16 @@ public async Task<ActionResult> 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about make it in format

EnableDetailedErrors defaults to false because these exception messages can contain sensitive information.

so that reader can easily get the note


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>).
20 changes: 14 additions & 6 deletions samples/Management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ dotnet user-secrets set Azure:SignalR:ConnectionString "<Connection String>"
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

```
Expand Down Expand Up @@ -44,13 +48,17 @@ dotnet run
Once the message publisher get started, use the command to send message

```
send user <User ID List (Separated by ',')> <Message>
send users <User List> <Message>
send group <Group Name> <Message>
send groups <Group List (Separated by ',')> <Message>
usergroup add <User ID> <Group Name>
usergroup remove <User ID> <Group Name>
send user <UserId> <Message>
send users <User1,User2,...> <Message>
send group <GroupName> <Message>
send groups <Group1,Group2,...> <Message>
usergroup add <User1,User2,...> <GroupName>
usergroup remove <UserId> <GroupName>
broadcast <Message>
close <ConnectionID> <Reason>?
checkexist connection <ConnectionID>
checkexist user <UserID>
checkexist group <GroupName>
```
For example, type `broadcast hello`, and press keyboard `enter` to publish messages.

Expand Down
18 changes: 13 additions & 5 deletions samples/Management/SignalRClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
{
Expand All @@ -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
Expand All @@ -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}");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about simply turn on it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EnableDetailedErrors is not recommended by SignalR, so I don't turn on it by default.

Copy link
Contributor

@wanlwanl wanlwanl Sep 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the If you expect non-null exception, you need to turn on is wierd here, how about making EnableDetailedErrors an option when you start negotiation server?

Console.WriteLine();
return Task.CompletedTask;
};

return connection;
Expand Down