Skip to content

Commit 758deac

Browse files
committed
Add strongly typed hub sample
* NegotiationServer * Change another hub into strongly typed hub * MessagePublisher * Extract an interface `IMessagePublisher` from the `MessagePublisher`, and implement a `StronglyTypedMessagePublisher`. * Add an option "-s|--strongly-typed-hub" to decide which `IMessagePublisher` to use. * SignalRClient * Add an option "-s|--strongly-typed-hub" to decide connect to which hub.
1 parent bc3e7e3 commit 758deac

File tree

11 files changed

+203
-41
lines changed

11 files changed

+203
-41
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Threading.Tasks;
5+
6+
namespace Microsoft.Azure.SignalR.Samples.Management
7+
{
8+
public interface IMessageClient
9+
{
10+
Task Target(string message);
11+
}
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Threading.Tasks;
5+
6+
namespace Microsoft.Azure.SignalR.Samples.Management
7+
{
8+
public interface IMessagePublisher
9+
{
10+
Task<bool> CheckExist(string type, string id);
11+
Task CloseConnection(string connectionId, string reason);
12+
Task DisposeAsync();
13+
Task InitAsync();
14+
Task ManageUserGroup(string command, string userId, string groupName);
15+
Task SendMessages(string command, string receiver, string message);
16+
}
17+
}

samples/Management/MessagePublisher/MessagePublisher.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99

1010
namespace Microsoft.Azure.SignalR.Samples.Management
1111
{
12-
public class MessagePublisher
12+
public class MessagePublisher : IMessagePublisher
1313
{
1414
private const string Target = "Target";
15-
private const string HubName = "Message";
15+
private const string HubName = "Hub";
1616
private readonly string _connectionString;
1717
private readonly ServiceTransportType _serviceTransportType;
1818
private ServiceHubContext _hubContext;

samples/Management/MessagePublisher/MessagePublisher.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.*" />
11+
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.12.0-preview1-10003" />
1212
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
1313
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.2.0" />
1414
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />

samples/Management/MessagePublisher/Program.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public static void Main(string[] args)
2323

2424
var connectionStringOption = app.Option("-c|--connectionstring", "Set connection string.", CommandOptionType.SingleValue, true);
2525
var serviceTransportTypeOption = app.Option("-t|--transport", "Set service transport type. Options: <transient>|<persistent>. Default value: transient. Transient: calls REST API for each message. Persistent: Establish a WebSockets connection and send all messages in the connection.", CommandOptionType.SingleValue, true); // todo: description
26+
var stronglyTypedOption = app.Option("-s|--strongly-typed", "Use strongly typed hub.", CommandOptionType.NoValue);
27+
2628
var configuration = new ConfigurationBuilder()
2729
.SetBasePath(Directory.GetCurrentDirectory())
2830
.AddUserSecrets<Program>()
@@ -50,7 +52,15 @@ public static void Main(string[] args)
5052
serviceTransportType = Enum.Parse<ServiceTransportType>(serviceTransportTypeOption.Value(), true);
5153
}
5254

53-
var publisher = new MessagePublisher(connectionString, serviceTransportType);
55+
IMessagePublisher publisher;
56+
if (stronglyTypedOption.HasValue())
57+
{
58+
publisher = new StronglyTypedMessagePublisher(connectionString, serviceTransportType);
59+
}
60+
else
61+
{
62+
publisher = new MessagePublisher(connectionString, serviceTransportType);
63+
}
5464
await publisher.InitAsync();
5565

5666
await StartAsync(publisher);
@@ -61,7 +71,7 @@ public static void Main(string[] args)
6171
app.Execute(args);
6272
}
6373

64-
private static async Task StartAsync(MessagePublisher publisher)
74+
private static async Task StartAsync(IMessagePublisher publisher)
6575
{
6676
Console.CancelKeyPress += async (sender, e) =>
6777
{
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.Azure.SignalR.Management;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace Microsoft.Azure.SignalR.Samples.Management
10+
{
11+
public class StronglyTypedMessagePublisher : IMessagePublisher
12+
{
13+
private const string HubName = "StronglyTypedHub";
14+
private readonly string _connectionString;
15+
private readonly ServiceTransportType _serviceTransportType;
16+
private ServiceHubContext<IMessageClient> _hubContext;
17+
18+
public StronglyTypedMessagePublisher(string connectionString, ServiceTransportType serviceTransportType)
19+
{
20+
_connectionString = connectionString;
21+
_serviceTransportType = serviceTransportType;
22+
}
23+
24+
public async Task InitAsync()
25+
{
26+
var serviceManager = new ServiceManagerBuilder().WithOptions(option =>
27+
{
28+
option.ConnectionString = _connectionString;
29+
option.ServiceTransportType = _serviceTransportType;
30+
})
31+
//Uncomment the following line to get more logs
32+
.WithLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()))
33+
.BuildServiceManager();
34+
35+
_hubContext = await serviceManager.CreateHubContextAsync<IMessageClient>(HubName, default);
36+
}
37+
38+
39+
public Task ManageUserGroup(string command, string userId, string groupName)
40+
{
41+
switch (command)
42+
{
43+
case "add":
44+
return _hubContext.UserGroups.AddToGroupAsync(userId, groupName);
45+
case "remove":
46+
return _hubContext.UserGroups.RemoveFromGroupAsync(userId, groupName);
47+
default:
48+
Console.WriteLine($"Can't recognize command {command}");
49+
return Task.CompletedTask;
50+
}
51+
}
52+
53+
public Task SendMessages(string command, string receiver, string message)
54+
{
55+
switch (command)
56+
{
57+
case "broadcast":
58+
return _hubContext.Clients.All.Target(message);
59+
case "user":
60+
var userId = receiver;
61+
return _hubContext.Clients.User(userId).Target(message);
62+
case "users":
63+
var userIds = receiver.Split(',');
64+
return _hubContext.Clients.Users(userIds).Target(message);
65+
case "group":
66+
var groupName = receiver;
67+
return _hubContext.Clients.Group(groupName).Target(message);
68+
case "groups":
69+
var groupNames = receiver.Split(',');
70+
return _hubContext.Clients.Groups(groupNames).Target(message);
71+
default:
72+
Console.WriteLine($"Can't recognize command {command}");
73+
return Task.CompletedTask;
74+
}
75+
}
76+
77+
public Task CloseConnection(string connectionId, string reason)
78+
{
79+
return _hubContext.ClientManager.CloseConnectionAsync(connectionId, reason);
80+
}
81+
82+
public Task<bool> CheckExist(string type, string id)
83+
{
84+
return type switch
85+
{
86+
"connection" => _hubContext.ClientManager.ConnectionExistsAsync(id),
87+
"user" => _hubContext.ClientManager.UserExistsAsync(id),
88+
"group" => _hubContext.ClientManager.UserExistsAsync(id),
89+
_ => throw new NotSupportedException(),
90+
};
91+
}
92+
93+
public Task DisposeAsync() => _hubContext?.DisposeAsync().AsTask();
94+
}
95+
}

samples/Management/NegotiationServer/Controllers/NegotiateController.cs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,48 @@ namespace NegotiationServer.Controllers
1313
public class NegotiateController : ControllerBase
1414
{
1515
private const string EnableDetailedErrors = "EnableDetailedErrors";
16-
private readonly ServiceHubContext _messageHubContext;
17-
private readonly ServiceHubContext _chatHubContext;
16+
private readonly ServiceHubContext _hubContext;
17+
private readonly ServiceHubContext<IMessageClient> _stronglyTypedHubContext;
1818
private readonly bool _enableDetailedErrors;
1919

2020
public NegotiateController(IHubContextStore store, IConfiguration configuration)
2121
{
22-
_messageHubContext = store.MessageHubContext;
23-
_chatHubContext = store.ChatHubContext;
22+
_hubContext = store.HubContext;
23+
_stronglyTypedHubContext = store.StronglyTypedHubContext;
2424
_enableDetailedErrors = configuration.GetValue(EnableDetailedErrors, false);
2525
}
2626

27-
[HttpPost("message/negotiate")]
28-
public Task<ActionResult> MessageHubNegotiate(string user)
27+
[HttpPost("hub/negotiate")]
28+
public async Task<ActionResult> HubNegotiate(string user)
2929
{
30-
return NegotiateBase(user, _messageHubContext);
31-
}
30+
if (string.IsNullOrEmpty(user))
31+
{
32+
return BadRequest("User ID is null or empty.");
33+
}
3234

33-
//This API is not used. Just demonstrate a way to have multiple hubs.
34-
[HttpPost("chat/negotiate")]
35-
public Task<ActionResult> ChatHubNegotiate(string user)
36-
{
37-
return NegotiateBase(user, _chatHubContext);
35+
var negotiateResponse = await _hubContext.NegotiateAsync(new()
36+
{
37+
UserId = user,
38+
EnableDetailedErrors = _enableDetailedErrors
39+
});
40+
41+
return new JsonResult(new Dictionary<string, string>()
42+
{
43+
{ "url", negotiateResponse.Url },
44+
{ "accessToken", negotiateResponse.AccessToken }
45+
});
3846
}
3947

40-
private async Task<ActionResult> NegotiateBase(string user, ServiceHubContext serviceHubContext)
48+
//The negotiation of strongly typed hub has little difference with untyped hub.
49+
[HttpPost("stronglyTypedHub/negotiate")]
50+
public async Task<ActionResult> StronglyTypedHubNegotiate(string user)
4151
{
4252
if (string.IsNullOrEmpty(user))
4353
{
4454
return BadRequest("User ID is null or empty.");
4555
}
4656

47-
var negotiateResponse = await serviceHubContext.NegotiateAsync(new()
57+
var negotiateResponse = await _stronglyTypedHubContext.NegotiateAsync(new()
4858
{
4959
UserId = user,
5060
EnableDetailedErrors = _enableDetailedErrors
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Threading.Tasks;
5+
6+
namespace NegotiationServer
7+
{
8+
// Copied from Message Publisher
9+
public interface IMessageClient
10+
{
11+
Task Target(string message);
12+
}
13+
}

samples/Management/NegotiationServer/NegotiationServer.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.*" />
10+
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.12.0-preview1-10003" />
1111
</ItemGroup>
1212

1313
</Project>
Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System;
45
using System.Threading;
56
using System.Threading.Tasks;
67
using Microsoft.Azure.SignalR.Management;
@@ -12,19 +13,19 @@ namespace NegotiationServer
1213
{
1314
public interface IHubContextStore
1415
{
15-
public ServiceHubContext MessageHubContext { get; }
16-
public ServiceHubContext ChatHubContext { get; }
16+
public ServiceHubContext HubContext { get; }
17+
public ServiceHubContext<IMessageClient> StronglyTypedHubContext { get; }
1718
}
1819

1920
public class SignalRService : IHostedService, IHubContextStore
2021
{
21-
private const string ChatHub = "Chat";
22-
private const string MessageHub = "Message";
22+
private const string StronglyTypedHub = "StronglyTypedHub";
23+
private const string Hub = "Hub";
2324
private readonly IConfiguration _configuration;
2425
private readonly ILoggerFactory _loggerFactory;
2526

26-
public ServiceHubContext MessageHubContext { get; private set; }
27-
public ServiceHubContext ChatHubContext { get; private set; }
27+
public ServiceHubContext HubContext { get; private set; }
28+
public ServiceHubContext<IMessageClient> StronglyTypedHubContext { get; private set; }
2829

2930
public SignalRService(IConfiguration configuration, ILoggerFactory loggerFactory)
3031
{
@@ -39,22 +40,15 @@ async Task IHostedService.StartAsync(CancellationToken cancellationToken)
3940
//or .WithOptions(o=>o.ConnectionString = _configuration["Azure:SignalR:ConnectionString"]
4041
.WithLoggerFactory(_loggerFactory)
4142
.BuildServiceManager();
42-
MessageHubContext = await serviceManager.CreateHubContextAsync(MessageHub, cancellationToken);
43-
ChatHubContext = await serviceManager.CreateHubContextAsync(ChatHub, cancellationToken);
43+
HubContext = await serviceManager.CreateHubContextAsync(Hub, cancellationToken);
44+
StronglyTypedHubContext = await serviceManager.CreateHubContextAsync<IMessageClient>(StronglyTypedHub, cancellationToken);
4445
}
4546

4647
Task IHostedService.StopAsync(CancellationToken cancellationToken)
4748
{
48-
return Task.WhenAll(Dispose(MessageHubContext), Dispose(ChatHubContext));
49-
}
50-
51-
private static Task Dispose(ServiceHubContext hubContext)
52-
{
53-
if (hubContext == null)
54-
{
55-
return Task.CompletedTask;
56-
}
57-
return hubContext.DisposeAsync();
49+
HubContext.Dispose();
50+
StronglyTypedHubContext.Dispose();
51+
return Task.CompletedTask;
5852
}
5953
}
6054
}

samples/Management/SignalRClient/Program.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace SignalRClient
1212
{
1313
class Program
1414
{
15-
private const string MessageHubEndpoint = "http://localhost:5000/Message";
15+
private const string HubEndpoint = "http://localhost:5000/Hub";
16+
private const string StronglyTypedHubEndpoint = "http://localhost:5000/StronglyTypedHub";
1617
private const string Target = "Target";
1718
private const string DefaultUser = "TestUser";
1819

@@ -25,13 +26,23 @@ static void Main(string[] args)
2526
app.HelpOption("--help");
2627

2728
var userIdOption = app.Option("-u|--userIdList", "Set user ID list", CommandOptionType.MultipleValue, true);
29+
var stronglyTypedOption = app.Option("-s|--strongly-typed", "Use strongly typed hub.", CommandOptionType.NoValue);
2830

2931
app.OnExecute(async () =>
3032
{
3133
var userIds = userIdOption.Values != null && userIdOption.Values.Count > 0 ? userIdOption.Values : new List<string>() { DefaultUser };
3234

35+
string hubEndpointToConnect;
36+
if (stronglyTypedOption.HasValue())
37+
{
38+
hubEndpointToConnect = StronglyTypedHubEndpoint;
39+
}
40+
else
41+
{
42+
hubEndpointToConnect = HubEndpoint;
43+
}
3344
var connections = (from userId in userIds
34-
select CreateHubConnection(MessageHubEndpoint, userId)).ToList();
45+
select CreateHubConnection(hubEndpointToConnect, userId)).ToList();
3546

3647
await Task.WhenAll(from conn in connections
3748
select conn.StartAsync());

0 commit comments

Comments
 (0)