Azure SignalR serverless with ASP.Net Core - c#

I need to connect ASP.Net Core with SignalR in serverless mode. I couldn't find any good example of how to do it (only for Default mode) and if it's even possible?
We do have Azure functions, that work fine with serverless SignalR. But now we need to connect to another web server, that is running ASP.Net Core. How can we do it? So that Azure function and ASP.Net Core server share the same SignalR Service?

You can use the Azure SignalR Service Management SDK to interact with a serverless Azure SignalR Service from a non-Functions app. Their samples have some examples of how to create a negotiate service and how to send messages. They use an IHostedService to manage the hub contexts, but I bet that could be simplified.
Here's how one would send some messages to clients:
var serviceManager = new ServiceManagerBuilder().WithOptions(option =>
{
option.ConnectionString = _connectionString;
option.ServiceTransportType = _serviceTransportType;
})
.BuildServiceManager();
var hubContext = await serviceManager.CreateHubContextAsync("<Your Hub Name>");
hubContext.Clients.All.SendAsync("<Your SignalR Client Callback>", "<Arg1>", "<Arg2>", ...);

Here is an example of connecting to SignalR (Replace 'localhost' with your Azure SignalR address)
class Program
{
static async Task Main(string[] args)
{
await Task.Delay(3000);
var sender1 = SenderClient1("john");
var sender2 = SenderClient1("papa");
var sender3 = SenderClient1("marry");
await Task.WhenAll(sender1, sender2, sender3);
Console.ReadKey();
}
static async Task SenderClient1(string nomeUsuario)
{
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:5005/sockethub", options =>
{
options.Headers["Application"] = "API Sender";
})
.WithAutomaticReconnect()
.Build();
await connection.StartAsync();
await connection.SendAsync("UpdateClient", nomeUsuario);
Console.WriteLine("Connection started.");
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
while (true)
{
Thread.Sleep(400);
await connection.SendAsync($"SendNotification", $"{nomeUsuario} - {DateTime.Now:G}");
Console.WriteLine($"Send Message: {nomeUsuario} - {DateTime.Now:G}");
}
}
}
More details and an example of the complete solution can be found here: https://github.com/hgmauri/signalr-socket-dotnet5

Related

How can I connect a WPF application to the Azure SignalR Service

I am working on a WPF application that I'd love to connect to the Azure SignalR Service that I created an instance of. There isn't anything that I can find that directly addresses this scenario. I can setup a BlazorServer and connect that way. But, i'd rather not host the server and use Azure.
Any idea of how that might look?
For example, I attempt this and it does not work:
HubConnection? connection;
connection = new HubConnectionBuilder()
.WithUrl("https://scisachat.service.signalr.net/client/?hub=draftchat")
.WithAutomaticReconnect()
.Build();
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
this.Dispatcher.Invoke(() =>
{
var newMessage = $"{user}: {message}";
Messages.Items.Add(newMessage);
Messages.Items.Add($"Connected on {DateTime.Now}");
});
});
try
{
await connection.StartAsync();
Messages.Items.Add("Connection Started");
openConnection.IsEnabled = false;
sendMessage.IsEnabled = true;
}
catch (Exception ex)
{
Messages.Items.Add(ex.Message);
}
I tried to connect to the Azure SignalR service as opposed to my chat hub on a localhost. It fails spectacularly.

How to connect FlexemServer through Flexemclient by signalR client

As I'm new to signalR client and flexem server. I'm connecting flexemserver through flexem client with package ("FBoxClientDriver" and "FBoxClientDriver.Contract").
I'm referring flexem client with this url "https://docs.flexem.net/fbox/zh-cn/explain/index.html"
Here is my code for signalR client
public class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine("Hello, World!");
var connection = new HubConnectionBuilder()
.WithUrl("******************************")
.Build();
await connection.StartAsync();
await connection.InvokeAsync("Start");
await connection.InvokeAsync("StartAllDMonData");
await connection.InvokeAsync("GetBoxGroups");
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
}
}
Using SignalR client package
Microsoft.AspNetCore.SignalR.Client
I'm unable to connect to Flexem server and get the data. Connection state showing as connecting while debugging and However I'm unable to connect to flexem client through flexem server. Am I doing something wrong?

SignalR client not receiving messages in integration tests

I am trying to test my SignalR connections in my integration tests.
The client looks like this:
var connection = new HubConnectionBuilder()
.WithUrl(
$"{client.BaseAddress}meeting-notifications",
o =>
{
o.HttpMessageHandlerFactory = _ => Server?.CreateHandler();
})
.Build();
connection.On<BoardDto>("BoardStateChanged", board => { Do Something... });
await connection.StartAsync();
I am calling the method in my ASP-NET Core backend like so:
public async Task BroadcastBoardStateAsync(int boardId, BoardDto board)
{
await _notificationHub.Clients.All.BoardStateChanged(board);
}
The client is able to call a method on the server but not the other way around.
Does anyone know what I am missing here?
Edit: I debugged the server call and the _notificationHub contains the connection-id of the client.
Turns out SignalR v3.x does json serialization via System.Text.Json which had some problem with my POCO's.
To fix this, I had to explicitly tell SignalR to use NewtonsoftJson for serialization via this method call:
var connection = new HubConnectionBuilder()
.WithUrl(
$"{client.BaseAddress}meeting-notifications",
o =>
{
o.HttpMessageHandlerFactory = _ => Server?.CreateHandler();
})
----> .AddNewtonsoftJsonProtocol()
.Build();

Connecting to SignalR Server from Client

I have a web server acting as SignalR server today, where the connections from JS are coming in to correct Hub and are handled correctly.
Example of the Register and start JS side
hub = $.connection.webRTCHub;
$.connection.hub.qs = "type=pusher";
$.connection.hub.start().done(function () {
connectionId = $.connection.hub.id;
log("Connected with id ", $.connection.hub.id);
});
When trying to connect to this SignalR server with the C# SignalR Client Nuget-package, I get connected, I get a connection ID, but I do not think I get connected to correct hub because non of the logging is triggered, nor the correct responses are sent to rest of clients.
I am using the trace log for SignalR and it is showing connections, and showing that the ID is connecting. Below is the connection code from the C# client
connection = new HubConnection("http://localhost/signalr/hubs/webRTCHub");
await connection.Start();
MessageBox.Show(connection.ConnectionId);
I have also tried
connection = new HubConnection("http://localhost/signalr/webRTCHub");
and
connection = new HubConnection("http://localhost/");
Can someone point me into the right direction where to start?
I cant see it here, but you need to create a HubProxy for the Hub you want to connect to.
I assume your hub is "webRTCHub".
using(var connection = new HubConnection("http://localhost/"))
{
var hubProxy = _connection.CreateHubProxy("webRTCHub");
hubProxy.On("yourevent", () =>
{
_logger.Debug("Event recieved");
});
await _connection.Start();
}
Make sure you're registering your hub's route in app start, for example in case your using .NET core:
app.UseSignalR(routes =>
{
routes.MapHub<webRTCHubHub>("/signalr/hubs/webRTCHub");
});
While the class webRTCHub should look something like this:
public class webRTCHub : Hub
{
public async Task SendNotification(string userId, string message)
{
await Clients.User(userId).SendAsync("ReceiveNotification", "You have a new message: " + message);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
}
}
For the js side:
"use strict";
var connection;
connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost/signalr/hubs/webRTCHub')
.build();
connection.on('ReceiveNotification', (message) => {
// show the message maybe
})
connection.start().catch(function (err) {
return console.error(err.toString())
});
connection.on('finished',(update)=>{
connection.stop();
});
To send back a message from the client to the server you should create a method as well in the class and call that from the script
Update: Packages and Services
for ASP.NET:
NuGet Packages:
Microsoft.AspNet.SignalR
Mapping Route in Application_Start
RouteTable.Routes.MapHubs("/signalr/hubs/webRTCHub", new webRTCHub());
for .NET Core:
Make sure to install the following package and add SignalR in ConfigureServices
Microsoft.AspNetCore.SignalR
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSignalR();
// ...
}
I guess you have not created any custom routes to handle signalr requests. You should initialize the HubConnection object without any url which will initialize the url of the connection object to "/signalr" as a default value.
connection = new HubConnection("");
or just
connection = new HubConnection();
Since you are using .NET FW and not .NET Core, you should configure the hub on the server like:
On your startup:
public void Configuration(IAppBuilder app)
{
//Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR(hubConfiguration);
});
}
The package you use:
Microsoft.AspNet.SignalR;
Microsoft.Owin;
Then on client side is the same for FW and Core, just point to your hub.

Azure Bot project with client and server in same web app

I've made an Azure bot application using the BotFramework v4 and used the WebChat control as an interface. I noticed that the bot server's dotnetcore app had a wwwroot folder with a placeholder HTML page in it, so thought it might be expedient to host the webchat client there. But now seems counter-intuitive that my webchat client is using DirectLine to send activities back to the same back-end that served it.
I had chosen the webchat client because I need to customise the appearance of the client. I also need the MVC app that serves the bot client to include Azure Active Directory B2C authentication (which it does). Users should see the webchat client before and after authentication but the bot back-end (handling the activities) needs to know whether the user is logged in and modify its behaviour accordingly (and I am struggling to achieve that part with DirectLine).
So my first question (ever on StackOverflow) is: With the Bot back-end and the webchat client front-end being hosted in the same, single Azure web app, is it necessary to use DirectLine, or is there a simpler way of doing this?
Relevant code in my Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
app.UseStaticFiles(); // to allow serving up the JS, CSS, etc., files.
app.UseBotFramework(); // to add middleware to route webchat activity to the bot back-end code
app.UseSession(); // to enable session state
app.UseAuthentication(); // to enable authentication (in this case AAD B2C)
app.UseMvcWithDefaultRoute(); // to add MVC middleware with default route
}
Also in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// standard code to add HttpContextAssessor, BotServices, BotConfigs and memory storage singletons ommitted for brevity ...
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options))
.AddCookie();
services.AddMvc();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromHours(1);
options.CookieHttpOnly = true;
});
// Create and add conversation state.
var conversationState = new ConversationState(dataStore);
services.AddSingleton(conversationState);
var userState = new UserState(dataStore);
services.AddSingleton(userState);
services.AddBot<MyBot>(options =>
{
options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);
options.ChannelProvider = new ConfigurationChannelProvider(Configuration);
// Catches any errors that occur during a conversation turn and logs them to currently
// configured ILogger.
ILogger logger = _loggerFactory.CreateLogger<RucheBot>();
options.OnTurnError = async (context, exception) =>
{
logger.LogError($"Exception caught : {exception}");
await context.SendActivityAsync("Sorry, it looks like something went wrong.");
};
});
}
My controller's Index method:
public async Task<ActionResult> Index()
{
string userId;
if (User.Identity.IsAuthenticated)
{
string aadb2cUserId = User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
Users.EnsureAccountExists(aadb2cUserId); // ensure account with given AAD identifier is know locally (by creating it if not)
userId = $"ia_{aadb2cUserId}";
}
else
{
userId = $"na_{Guid.NewGuid()}";
}
HttpClient client = new HttpClient();
string directLineUrl = $"https://directline.botframework.com/v3/directline/tokens/generate";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, directLineUrl);
// TODO: put this in the config somewhere
var secret = "<the secret code from my bot's DirectLine channel config in the Azure portal>";
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secret);
string jsonUser = JsonConvert.SerializeObject(new { User = new { Id = userId } });
request.Content = new StringContent(jsonUser, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
string token = string.Empty;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
}
var config = new ChatConfig()
{
Token = token,
UserId = userId,
};
return View(config);
}
And finally the code in the associated view:
<div id="webchat"></div>
<script type="text/javascript">
...
/// Called asynchronously during the page load
function renderWebChat( withSound )
{
var webchatOptions =
{
directLine: window.WebChat.createDirectLine( { secret: '#Model.Token'} ),
userID: '#Model.UserId'
};
if ( withSound )
{
webchatOptions.webSpeechPonyfillFactory = window.WebChat.createBrowserWebSpeechPonyfillFactory();
}
window.WebChat.renderWebChat( webchatOptions, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
}
</script>
I'm going to disagree with Nicolas R a little bit. When it comes to directly accessing your bot, you might like to have a look at this: https://www.npmjs.com/package/offline-directline
There's also the option of hosting a bot in the browser, which I think may facilitate the sort of direct communication you're looking for.
Long question, but the answer will be a lot shorter!
So my first question (ever on StackOverflow) is: With the Bot back-end
and the webchat client front-end being hosted in the same, single
Azure web app, is it necessary to use DirectLine, or is there a
simpler way of doing this?
Yes, it is necessary. In fact, all the channels types are using the Bot Connector to communicate with your backend (your bot code), there is no direct access possible. There are a lot of reasons for that, one is for example the billing!

Categories