Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add strongly typed hub sample (Don't merge before feature is released) #168

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

stronglyTypedOption

maybe add another program for strongly typed hub instead? frankly speaking, I don't see how appealing the strongly typed one is from the sample


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