diff --git a/samples/Management/MessagePublisher/IMessageClient.cs b/samples/Management/MessagePublisher/IMessageClient.cs new file mode 100644 index 00000000..b29df4a3 --- /dev/null +++ b/samples/Management/MessagePublisher/IMessageClient.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +namespace Microsoft.Azure.SignalR.Samples.Management +{ + public interface IMessageClient + { + Task Target(string message); + } +} \ No newline at end of file diff --git a/samples/Management/MessagePublisher/IMessagePublisher.cs b/samples/Management/MessagePublisher/IMessagePublisher.cs new file mode 100644 index 00000000..7702ac9a --- /dev/null +++ b/samples/Management/MessagePublisher/IMessagePublisher.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +namespace Microsoft.Azure.SignalR.Samples.Management +{ + public interface IMessagePublisher + { + Task CheckExist(string type, string id); + Task CloseConnection(string connectionId, string reason); + Task DisposeAsync(); + Task InitAsync(); + Task ManageUserGroup(string command, string userId, string groupName); + Task SendMessages(string command, string receiver, string message); + } +} \ No newline at end of file diff --git a/samples/Management/MessagePublisher/MessagePublisher.cs b/samples/Management/MessagePublisher/MessagePublisher.cs index 39d36a7b..e7d6cfa1 100644 --- a/samples/Management/MessagePublisher/MessagePublisher.cs +++ b/samples/Management/MessagePublisher/MessagePublisher.cs @@ -9,10 +9,10 @@ namespace Microsoft.Azure.SignalR.Samples.Management { - public class MessagePublisher + public class MessagePublisher : IMessagePublisher { private const string Target = "Target"; - private const string HubName = "Message"; + private const string HubName = "Hub"; private readonly string _connectionString; private readonly ServiceTransportType _serviceTransportType; private ServiceHubContext _hubContext; diff --git a/samples/Management/MessagePublisher/MessagePublisher.csproj b/samples/Management/MessagePublisher/MessagePublisher.csproj index 16ba1037..d98d818a 100644 --- a/samples/Management/MessagePublisher/MessagePublisher.csproj +++ b/samples/Management/MessagePublisher/MessagePublisher.csproj @@ -8,7 +8,7 @@ - + diff --git a/samples/Management/MessagePublisher/Program.cs b/samples/Management/MessagePublisher/Program.cs index 9bd22692..82fcade3 100644 --- a/samples/Management/MessagePublisher/Program.cs +++ b/samples/Management/MessagePublisher/Program.cs @@ -23,6 +23,8 @@ public static void Main(string[] args) var connectionStringOption = app.Option("-c|--connectionstring", "Set connection string.", CommandOptionType.SingleValue, true); var serviceTransportTypeOption = app.Option("-t|--transport", "Set service transport type. Options: |. 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 + var stronglyTypedOption = app.Option("-s|--strongly-typed", "Use strongly typed hub.", CommandOptionType.NoValue); + var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddUserSecrets() @@ -50,7 +52,15 @@ public static void Main(string[] args) serviceTransportType = Enum.Parse(serviceTransportTypeOption.Value(), true); } - var publisher = new MessagePublisher(connectionString, serviceTransportType); + IMessagePublisher publisher; + if (stronglyTypedOption.HasValue()) + { + publisher = new StronglyTypedMessagePublisher(connectionString, serviceTransportType); + } + else + { + publisher = new MessagePublisher(connectionString, serviceTransportType); + } await publisher.InitAsync(); await StartAsync(publisher); @@ -61,7 +71,7 @@ public static void Main(string[] args) app.Execute(args); } - private static async Task StartAsync(MessagePublisher publisher) + private static async Task StartAsync(IMessagePublisher publisher) { Console.CancelKeyPress += async (sender, e) => { diff --git a/samples/Management/MessagePublisher/StronglyTypedMessagePublisher.cs b/samples/Management/MessagePublisher/StronglyTypedMessagePublisher.cs new file mode 100644 index 00000000..35ec32d5 --- /dev/null +++ b/samples/Management/MessagePublisher/StronglyTypedMessagePublisher.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; +using Microsoft.Azure.SignalR.Management; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.SignalR.Samples.Management +{ + public class StronglyTypedMessagePublisher : IMessagePublisher + { + private const string HubName = "StronglyTypedHub"; + private readonly string _connectionString; + private readonly ServiceTransportType _serviceTransportType; + private ServiceHubContext _hubContext; + + public StronglyTypedMessagePublisher(string connectionString, ServiceTransportType serviceTransportType) + { + _connectionString = connectionString; + _serviceTransportType = serviceTransportType; + } + + public async Task InitAsync() + { + var serviceManager = new ServiceManagerBuilder().WithOptions(option => + { + option.ConnectionString = _connectionString; + option.ServiceTransportType = _serviceTransportType; + }) + //Uncomment the following line to get more logs + .WithLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())) + .BuildServiceManager(); + + _hubContext = await serviceManager.CreateHubContextAsync(HubName, default); + } + + + public Task ManageUserGroup(string command, string userId, string groupName) + { + switch (command) + { + case "add": + return _hubContext.UserGroups.AddToGroupAsync(userId, groupName); + case "remove": + return _hubContext.UserGroups.RemoveFromGroupAsync(userId, groupName); + default: + Console.WriteLine($"Can't recognize command {command}"); + return Task.CompletedTask; + } + } + + public Task SendMessages(string command, string receiver, string message) + { + switch (command) + { + case "broadcast": + return _hubContext.Clients.All.Target(message); + case "user": + var userId = receiver; + return _hubContext.Clients.User(userId).Target(message); + case "users": + var userIds = receiver.Split(','); + return _hubContext.Clients.Users(userIds).Target(message); + case "group": + var groupName = receiver; + return _hubContext.Clients.Group(groupName).Target(message); + case "groups": + var groupNames = receiver.Split(','); + return _hubContext.Clients.Groups(groupNames).Target(message); + default: + Console.WriteLine($"Can't recognize command {command}"); + return Task.CompletedTask; + } + } + + 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().AsTask(); + } +} \ No newline at end of file diff --git a/samples/Management/NegotiationServer/Controllers/NegotiateController.cs b/samples/Management/NegotiationServer/Controllers/NegotiateController.cs index a4a5e108..29f7ebf7 100644 --- a/samples/Management/NegotiationServer/Controllers/NegotiateController.cs +++ b/samples/Management/NegotiationServer/Controllers/NegotiateController.cs @@ -13,38 +13,48 @@ namespace NegotiationServer.Controllers public class NegotiateController : ControllerBase { private const string EnableDetailedErrors = "EnableDetailedErrors"; - private readonly ServiceHubContext _messageHubContext; - private readonly ServiceHubContext _chatHubContext; + private readonly ServiceHubContext _hubContext; + private readonly ServiceHubContext _stronglyTypedHubContext; private readonly bool _enableDetailedErrors; public NegotiateController(IHubContextStore store, IConfiguration configuration) { - _messageHubContext = store.MessageHubContext; - _chatHubContext = store.ChatHubContext; + _hubContext = store.HubContext; + _stronglyTypedHubContext = store.StronglyTypedHubContext; _enableDetailedErrors = configuration.GetValue(EnableDetailedErrors, false); } - [HttpPost("message/negotiate")] - public Task MessageHubNegotiate(string user) + [HttpPost("hub/negotiate")] + public async Task HubNegotiate(string user) { - return NegotiateBase(user, _messageHubContext); - } + if (string.IsNullOrEmpty(user)) + { + return BadRequest("User ID is null or empty."); + } - //This API is not used. Just demonstrate a way to have multiple hubs. - [HttpPost("chat/negotiate")] - public Task ChatHubNegotiate(string user) - { - return NegotiateBase(user, _chatHubContext); + var negotiateResponse = await _hubContext.NegotiateAsync(new() + { + UserId = user, + EnableDetailedErrors = _enableDetailedErrors + }); + + return new JsonResult(new Dictionary() + { + { "url", negotiateResponse.Url }, + { "accessToken", negotiateResponse.AccessToken } + }); } - private async Task NegotiateBase(string user, ServiceHubContext serviceHubContext) + //The negotiation of strongly typed hub has little difference with untyped hub. + [HttpPost("stronglyTypedHub/negotiate")] + public async Task StronglyTypedHubNegotiate(string user) { if (string.IsNullOrEmpty(user)) { return BadRequest("User ID is null or empty."); } - var negotiateResponse = await serviceHubContext.NegotiateAsync(new() + var negotiateResponse = await _stronglyTypedHubContext.NegotiateAsync(new() { UserId = user, EnableDetailedErrors = _enableDetailedErrors diff --git a/samples/Management/NegotiationServer/IMessageClient.cs b/samples/Management/NegotiationServer/IMessageClient.cs new file mode 100644 index 00000000..9c116dcd --- /dev/null +++ b/samples/Management/NegotiationServer/IMessageClient.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +namespace NegotiationServer +{ + // Copied from Message Publisher + public interface IMessageClient + { + Task Target(string message); + } +} \ No newline at end of file diff --git a/samples/Management/NegotiationServer/NegotiationServer.csproj b/samples/Management/NegotiationServer/NegotiationServer.csproj index daead9c3..dc34ddaa 100644 --- a/samples/Management/NegotiationServer/NegotiationServer.csproj +++ b/samples/Management/NegotiationServer/NegotiationServer.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/Management/NegotiationServer/SignalRService.cs b/samples/Management/NegotiationServer/SignalRService.cs index 0c86b1f6..e01cf2a0 100644 --- a/samples/Management/NegotiationServer/SignalRService.cs +++ b/samples/Management/NegotiationServer/SignalRService.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.SignalR.Management; @@ -12,19 +13,19 @@ namespace NegotiationServer { public interface IHubContextStore { - public ServiceHubContext MessageHubContext { get; } - public ServiceHubContext ChatHubContext { get; } + public ServiceHubContext HubContext { get; } + public ServiceHubContext StronglyTypedHubContext { get; } } public class SignalRService : IHostedService, IHubContextStore { - private const string ChatHub = "Chat"; - private const string MessageHub = "Message"; + private const string StronglyTypedHub = "StronglyTypedHub"; + private const string Hub = "Hub"; private readonly IConfiguration _configuration; private readonly ILoggerFactory _loggerFactory; - public ServiceHubContext MessageHubContext { get; private set; } - public ServiceHubContext ChatHubContext { get; private set; } + public ServiceHubContext HubContext { get; private set; } + public ServiceHubContext StronglyTypedHubContext { get; private set; } public SignalRService(IConfiguration configuration, ILoggerFactory loggerFactory) { @@ -39,22 +40,15 @@ async Task IHostedService.StartAsync(CancellationToken cancellationToken) //or .WithOptions(o=>o.ConnectionString = _configuration["Azure:SignalR:ConnectionString"] .WithLoggerFactory(_loggerFactory) .BuildServiceManager(); - MessageHubContext = await serviceManager.CreateHubContextAsync(MessageHub, cancellationToken); - ChatHubContext = await serviceManager.CreateHubContextAsync(ChatHub, cancellationToken); + HubContext = await serviceManager.CreateHubContextAsync(Hub, cancellationToken); + StronglyTypedHubContext = await serviceManager.CreateHubContextAsync(StronglyTypedHub, cancellationToken); } Task IHostedService.StopAsync(CancellationToken cancellationToken) { - return Task.WhenAll(Dispose(MessageHubContext), Dispose(ChatHubContext)); - } - - private static Task Dispose(ServiceHubContext hubContext) - { - if (hubContext == null) - { - return Task.CompletedTask; - } - return hubContext.DisposeAsync(); + HubContext.Dispose(); + StronglyTypedHubContext.Dispose(); + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/samples/Management/SignalRClient/Program.cs b/samples/Management/SignalRClient/Program.cs index 060dcb22..14e04db5 100644 --- a/samples/Management/SignalRClient/Program.cs +++ b/samples/Management/SignalRClient/Program.cs @@ -12,7 +12,8 @@ namespace SignalRClient { class Program { - private const string MessageHubEndpoint = "http://localhost:5000/Message"; + private const string HubEndpoint = "http://localhost:5000/Hub"; + private const string StronglyTypedHubEndpoint = "http://localhost:5000/StronglyTypedHub"; private const string Target = "Target"; private const string DefaultUser = "TestUser"; @@ -25,13 +26,23 @@ static void Main(string[] args) app.HelpOption("--help"); var userIdOption = app.Option("-u|--userIdList", "Set user ID list", CommandOptionType.MultipleValue, true); + var stronglyTypedOption = app.Option("-s|--strongly-typed", "Use strongly typed hub.", CommandOptionType.NoValue); app.OnExecute(async () => { var userIds = userIdOption.Values != null && userIdOption.Values.Count > 0 ? userIdOption.Values : new List() { DefaultUser }; + string hubEndpointToConnect; + if (stronglyTypedOption.HasValue()) + { + hubEndpointToConnect = StronglyTypedHubEndpoint; + } + else + { + hubEndpointToConnect = HubEndpoint; + } var connections = (from userId in userIds - select CreateHubConnection(MessageHubEndpoint, userId)).ToList(); + select CreateHubConnection(hubEndpointToConnect, userId)).ToList(); await Task.WhenAll(from conn in connections select conn.StartAsync());