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

Adding reload feature. #135

Open
wants to merge 2 commits 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UserSecretsId>ReloadingSample</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.5.0" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
</Project>
126 changes: 126 additions & 0 deletions samples/ServerlessReloading/MessagePublisher/MessagePubulisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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 System.Net.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Azure.SignalR.Management;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Text;
using System.Net.Http.Headers;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

namespace Microsoft.Azure.SignalR.Samples.Management
{
public class MessagePublisher
{
private const string Target = "Target";
private const string HubName = "ManagementSampleHub";
private readonly string _connectionString;
private readonly ServiceTransportType _serviceTransportType;
private IServiceHubContext _hubContext;
// reload connection string
private readonly string connectionString = "Endpoint=http://localhost;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Port=8081;Version=1.0;";


public MessagePublisher(string connectionString, ServiceTransportType serviceTransportType)
{
_connectionString = connectionString;
_serviceTransportType = serviceTransportType;
}

public async Task InitAsync()
{
var serviceManager = new ServiceManagerBuilder().WithOptions(option =>
{
option.ConnectionString = _connectionString;
option.ServiceTransportType = _serviceTransportType;
}).Build();

_hubContext = await serviceManager.CreateHubContextAsync(HubName, new LoggerFactory());
}

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)
{
var jMsg = new
{
msgType = "0",
content = message
};
var uMsg = JsonConvert.SerializeObject(jMsg);
switch (command)
{
case "broadcast":
return _hubContext.Clients.All.SendAsync(Target, uMsg);
case "user":
var userId = receiver;
return _hubContext.Clients.User(userId).SendAsync(Target, uMsg);
case "users":
var userIds = receiver.Split(',');
return _hubContext.Clients.Users(userIds).SendAsync(Target, uMsg);
case "group":
var groupName = receiver;
return _hubContext.Clients.Group(groupName).SendAsync(Target, uMsg);
case "groups":
var groupNames = receiver.Split(',');
return _hubContext.Clients.Groups(groupNames).SendAsync(Target, uMsg);
case "reload":
HttpClient restClient = new HttpClient();
var msg = new
{
Target = Target,
Arguments = new string[] { connectionString }
};

// generate token
string key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH";
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
{
KeyId = key.GetHashCode().ToString()
};
SigningCredentials credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

JwtSecurityTokenHandler JwtTokenHandler = new JwtSecurityTokenHandler();
var token = JwtTokenHandler.CreateJwtSecurityToken(
issuer: null,
audience: "http://localhost/api/v1/reload",
notBefore: DateTime.Now,
expires: DateTime.Now.AddHours(1),
issuedAt: DateTime.Now,
signingCredentials: credentials) ;
string tk = JwtTokenHandler.WriteToken(token);

restClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tk);
restClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string json = JsonConvert.SerializeObject(msg);
var data = new StringContent(json, Encoding.UTF8, "application/json");
// Call reload rest api on the old service to start reloading connection
string url = "http://localhost:8080/api/v1/reload";

return restClient.PostAsync(url, data);
default:
Console.WriteLine($"Can't recognize command {command}");
return Task.CompletedTask;
}
}

public Task DisposeAsync() => _hubContext?.DisposeAsync();
}
}
136 changes: 136 additions & 0 deletions samples/ServerlessReloading/MessagePublisher/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.Azure.SignalR.Management;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Azure.SignalR.Samples.Management
{
class Program
{
static void Main(string[] args)
{
var app = new CommandLineApplication();
app.FullName = "Azure SignalR Management Sample: Message Publisher";
app.HelpOption("--help");
app.Description = "Message publisher using Azure SignalR Service Management SDK.";

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 configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddUserSecrets<Program>()
.AddEnvironmentVariables()
.Build();


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

if (string.IsNullOrEmpty(connectionString))
{
MissOptions();
return -1;
}

ServiceTransportType serviceTransportType;
if (string.IsNullOrEmpty(serviceTransportTypeOption.Value()))
{
serviceTransportType = ServiceTransportType.Transient;
}
else
{
serviceTransportType = Enum.Parse<ServiceTransportType>(serviceTransportTypeOption.Value(), true);
}

var publisher = new MessagePublisher(connectionString, serviceTransportType);
await publisher.InitAsync();

await StartAsync(publisher);

return 0;
});

app.Execute(args);
}

private static async Task StartAsync(MessagePublisher publisher)
{
Console.CancelKeyPress += async (sender, e) =>
{
await publisher.DisposeAsync();
Environment.Exit(0);
};

ShowHelp();

try
{
while (true)
{
var argLine = Console.ReadLine();
if (argLine == null)
{
continue;
}
var args = argLine.Split(' ');

if (args.Length == 2 && args[0].Equals("broadcast"))
{
Console.WriteLine($"broadcast message '{args[1]}'");
await publisher.SendMessages(args[0], null, args[1]);
}
else if (args.Length == 4 && args[0].Equals("send"))
{
await publisher.SendMessages(args[1], args[2], args[3]);
Console.WriteLine($"{args[0]} message '{args[3]}' to '{args[2]}'");
}
else if (args.Length == 4 && args[0] == "usergroup")
{
await publisher.ManageUserGroup(args[1], args[2], args[3]);
var preposition = args[1] == "add" ? "to" : "from";
Console.WriteLine($"{args[1]} user '{args[2]}' {preposition} group '{args[3]}'");
}
else if (args.Length == 1 && args[0].Equals("reload"))
{
await publisher.SendMessages(args[0], null, null);
Console.WriteLine($"{args[0]}");
}
else
{
Console.WriteLine($"Can't recognize command {argLine}");
}
}
}
finally
{
await publisher.DisposeAsync();
}
}

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" +
"reload\n" +
"***********************");
}

private static void MissOptions()
{
Console.WriteLine("Miss required options: Connection string and Hub must be set");
}
}
}
90 changes: 90 additions & 0 deletions samples/ServerlessReloading/MessagePublisher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
Message Publisher
=========

This sample shows how to use [Microsoft.Azure.SignalR.Management](https://www.nuget.org/packages/Microsoft.Azure.SignalR.Management) to publish messages to SignalR clients that connect to Azure SignalR Service.

## Build from Scratch

### Add Management SDK to your project

```
dotnet add package Microsoft.Azure.SignalR.Management -v 1.0.0-*
```

### Create instance of `IServiceManager`

The `IServiceManager` is able to manage your Azure SignalR Service from your connection string.

```c#
var serviceManager = new ServiceManagerBuilder()
.WithOptions(option =>
{
option.ConnectionString = "<Your Connection String>";
})
.Build();
```

### Create instance of `IServiceHubContext`

The `IServiceHubContext` is used to publish messages to a specific hub.

```C#
var hubContext = await serviceManager.CreateHubContextAsync("<Your Hub Name>");
```

### Publish messages to a specific hub

Once you create the `hubContext`, you can use it to publish messages to a given hub.

```C#
// broadcast
hubContext.Clients.All.SendAsync("<Your SignalR Client Callback>", "<Arg1>", "<Arg2>", ...);

// send to a user
hubContext.Clients.User("<User ID>").SendAsync("<Your SignalR Client Callback>", "<Arg1>", "<Arg2>", ...);

// send to users
hubContext.Clients.Users(<User ID List>).SendAsync("<Your SignalR Client Callback>", "<Arg1>", "<Arg2>", ...);

// send to a group
hubContext.Clients.Group("<Group Name>").SendAsync("<Your SignalR Client Callback>", "<Arg1>", "<Arg2>", ...);

// send to groups
hubContext.Clients.Group(<Group Name List>).SendAsync("<Your SignalR Client Callback>", "<Arg1>", "<Arg2>", ...);

// add a user to a group
hubContext.UserGroups.AddToGroupAsync("<User ID>", "<Group Name>");

// remove a user from a group
hubContext.UserGroups.RemoveFromGroupAsync("<User ID>", "<Group Name>");

...
```

All features can be found [here](<https://github.com/Azure/azure-signalr/blob/dev/docs/management-sdk-guide.md#features>).

### Reload connection to another new service platform

```C#
// Use specialized Json Web Token with userId claim
restClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tk);

// Serialize reloading message for transimisstion
string json = JsonConvert.SerializeObject(msg);

// Call reload rest api on the old service to start reloading connection
string url = "http://localhost:8080/api/v1/reload";

// Post REST API to reload connections on a specific service instance
restClient.PostAsync(url, data);
```

### Dispose the instance of `IServiceHubContext`

```c#
await hubContext.DisposeAsync();
```

## Full Sample

The full message publisher sample can be found [here](.). The usage of this sample can be found [here](<https://github.com/aspnet/AzureSignalR-samples/tree/master/samples/\ServerlessReloading#start-message-publisher>).
Loading