Skip to content

Commit

Permalink
Add strongly typed hub sample
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Y-Sindo committed Oct 21, 2021
1 parent d18d9b3 commit 00e3b30
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 41 deletions.
12 changes: 12 additions & 0 deletions samples/Management/MessagePublisher/IMessageClient.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
17 changes: 17 additions & 0 deletions samples/Management/MessagePublisher/IMessagePublisher.cs
Original file line number Diff line number Diff line change
@@ -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<bool> 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);
}
}
4 changes: 2 additions & 2 deletions samples/Management/MessagePublisher/MessagePublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.*" />
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.12.0-preview1-10003" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
Expand Down
14 changes: 12 additions & 2 deletions samples/Management/MessagePublisher/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <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
var stronglyTypedOption = app.Option("-s|--strongly-typed", "Use strongly typed hub.", CommandOptionType.NoValue);

var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddUserSecrets<Program>()
Expand Down Expand Up @@ -50,7 +52,15 @@ public static void Main(string[] args)
serviceTransportType = Enum.Parse<ServiceTransportType>(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);
Expand All @@ -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) =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IMessageClient> _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<IMessageClient>(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<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().AsTask();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<IMessageClient> _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<ActionResult> MessageHubNegotiate(string user)
[HttpPost("hub/negotiate")]
public async Task<ActionResult> 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<ActionResult> ChatHubNegotiate(string user)
{
return NegotiateBase(user, _chatHubContext);
var negotiateResponse = await _hubContext.NegotiateAsync(new()
{
UserId = user,
EnableDetailedErrors = _enableDetailedErrors
});

return new JsonResult(new Dictionary<string, string>()
{
{ "url", negotiateResponse.Url },
{ "accessToken", negotiateResponse.AccessToken }
});
}

private async Task<ActionResult> NegotiateBase(string user, ServiceHubContext serviceHubContext)
//The negotiation of strongly typed hub has little difference with untyped hub.
[HttpPost("stronglyTypedHub/negotiate")]
public async Task<ActionResult> 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
Expand Down
13 changes: 13 additions & 0 deletions samples/Management/NegotiationServer/IMessageClient.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

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

</Project>
30 changes: 12 additions & 18 deletions samples/Management/NegotiationServer/SignalRService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,19 +13,19 @@ namespace NegotiationServer
{
public interface IHubContextStore
{
public ServiceHubContext MessageHubContext { get; }
public ServiceHubContext ChatHubContext { get; }
public ServiceHubContext HubContext { get; }
public ServiceHubContext<IMessageClient> 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<IMessageClient> StronglyTypedHubContext { get; private set; }

public SignalRService(IConfiguration configuration, ILoggerFactory loggerFactory)
{
Expand All @@ -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<IMessageClient>(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;
}
}
}
15 changes: 13 additions & 2 deletions samples/Management/SignalRClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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<string>() { 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());
Expand Down

0 comments on commit 00e3b30

Please sign in to comment.