Call SignalR Client method from normal C# class - c#

I am trying to add SignalR in my MVC project. I need to call a SignalR client method from my class library. I did below code
public class CommomHubManager : ICommomHubManager
{
readonly IHubContext Context;
public CommomHubManager()
{
Context = Helpers.Providers.HubContextProvider<Hubs.Notifications>.HubContext;
}
public Task AddUserToGroup(string groupName)
{
return Task.Factory.StartNew(() => {
Context.Clients.All.addUserToGroup(groupName);
});
}
}
Its not working, but when i try to call an another Hub class method from WebApi its working just fine. I want to know that is it possible to call SignalR Client method from normal C# class?

How to use SignalR hub instance outside of the hubpipleline
var context = GlobalHost.ConnectionManager.GetHubContext<CommomHubManager>();
context.Clients.All.Send("Admin", "stop the chat");
You can find out more in the SignalR documentation.

Related

SignalR Core, not getting response from server when client is connected

I am working on a SignalR Clinet-Server connection. My server is WebApi Core 2.1 and my client is WPF .NET Framework 4.7.2.
On the client side I have a singleton hub service with one Instance to recive messages from server:
using System.Collections.ObjectModel;
using Microsoft.AspNetCore.SignalR.Client;
public class HubService
{
//singleton
public static HubService Instance { get; } = new HubService();
public ObservableCollection<string> Notifications { get; set; }
public async void Initialize()
{
this.Notifications = new ObservableCollection<string>();
var hubConnection = new HubConnectionBuilder()
.WithUrl(UrlBuilder.BuildEndpoint("Notifications"))
.Build();
hubConnection.On<string>("ReciveServerUpdate", update =>
{
//todo
});
await hubConnection.StartAsync();
}
}
i initialize it as singleton:
public MainWindowViewModel()
{
HubService.Instance.Initialize();
}
While I'm debugging, on MainWindowViewModel im hitting that HubService.
On Server side its look like this.
Hub:
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
public class NotificationsHub : Hub
{
public async Task GetUpdateForServer(string call)
{
await this.Clients.Caller.SendAsync("ReciveServerUpdate", call);
}
}
Im trigering send message in this way in my controller's methods:
[HttpPost]
public async Task<IActionResult> PostTask([FromBody] Task task)
{
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
this.taskService.Add(task);
//here im calling sending message. When im debugging
//i see one connection from my WPF with unique ConnectionId
await this.notificationsHub.Clients.All.SendAsync("ReciveServerUpdate", "New Task in database!");
return this.Ok(task);
}
As I wrote before, while I'm debugging my WebApi, in Clients I have exactly one connection from my WPF. When I turn off WPF, connection count = 0 so connections works perfectly.
But when I call SendAsync(), I'm not reciving any information in WPF in hubConnection.On. Funny thing, yesterday it works perfectly.
So, is my thinking about making HubService as static singleton is right? If its, why i cant recive messages from WebApi by SignalR when my WPF is connected to it?
I asked something similiar yesterday but i found a solution for it. Yesterday, my methods works, i could hit hubConnection.On when i get any message from WebApi. My question from yestarday.
EDIT
Injection of HUb to controller:
private readonly ITaskService taskService;
private readonly IHubContext<NotificationsHub> notificationsHub;
public TaskController(ITaskService taskService, IHubContext<NotificationsHub> notificationsHub)
{
this.taskService = taskService;
this.notificationsHub = notificationsHub;
}
And Startup.cs only SignalR things (i deleted other things not related to signal):
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSignalR(routes => routes.MapHub<NotificationsHub>("/Notifications"));
}
EDIT2
Here is connection that i can get it, when my client WPF will register his connection:
I tried your code with all kinds of clients (wpf/console/even with a browser), it always works fine for me. The hubConnection.On<string>("ReciveServerUpdate", update => {//todo}); always be invoked when I send a request to PostTask.
I'm not sure why (sometimes) it doesn't work for you somehow . However, when SignalR client has connected to server but gets no message from server, there're possible two reasons:
Your PostTask([FromBody] Task task) action method is not executed. Let's say this is an ApiController method, if the browser posts a request with a Content-Type of application/www-x-form-urlencoded by accident, the invocation of Clients.All.SendAsync(..., ...); won't be executed at all.
The handler of SigalR client (hubConnection.On<>(method,handler)) must has exactly the same argument list as the invocation in order to receive messages. We must be very careful when dealing with this.
Finally, it's better to add a reference to Microsoft.Extensions.Logging.Console
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.*" />
so that we could enable logging to troubleshoot :
var hubConnection = new HubConnectionBuilder()
.WithUrl(UrlBuilder.BuildEndpoint("Notifications"))
.ConfigureLogging(logging =>{
logging.AddConsole(); // enable logging
})
.Build();

Communication between Topshelf service (acting as TCP server) and selfhosted OWIN WebAPI

I have a Topshelf windows service that acts as a TCP server. Inside this service, I also have a self-hosted (OWIN) WebAPI.
My goal is to somehow allow the WebAPI to communicate with the TCP server that's contained and running in the same service. Naturally I could simply use something like a "trigger" file or a shared DB that could be polled frequently, though I'd like to know of any more optimal/native ways to achieve this.
To get a better idea of the project, think of a single page application consuming my API and making certain calls with arbitrary string parameters. This data should then be passed to clients (C++ console apps using winsock) that are connected to the running TCP server.
The following Container is instantiated and passed to the Topshelf HostConfigurator
class ContainerService
{
private APIService _apiService;
private EngineService _engineService;
protected IDisposable WebAppHolder { get; set; }
public bool Start(HostControl hostControl)
{
var host = hostControl;
_apiService = new APIService();
_engineService = new EngineService();
// Initialize API service
if (WebAppHolder == null)
{
WebAppHolder = _apiService.Initialize();
}
// Initialize Engine service
_engineService.Initialize();
return true;
}
public bool Stop(HostControl hostControl)
{
// Stop API service
if (WebAppHolder != null)
{
WebAppHolder.Dispose();
WebAppHolder = null;
}
// Stop Engine service
_engineService.Stop();
return true;
}
}
Standard Topshelf stuff in program entry point (as mentioned above):
HostFactory.Run(hostConfigurator =>
{
hostConfigurator.Service<ContainerService>(containerService =>
{
containerService.WhenStarted((service, control) => service.Start(control));
containerService.WhenStopped((service, control) => service.Stop(control));
});
hostConfigurator.RunAsLocalSystem();
hostConfigurator.SetServiceName("Educe Service Host");
hostConfigurator.SetDisplayName("Communication Service");
hostConfigurator.SetDescription("Responsible for API and Engine services");
});
TCP Server:
public void Initialize()
{
_serverListener = new TcpListener(new IPEndPoint(hostAddress, (int)port));
_serverListener.Start();
_threadDoBeginAcceptTcpClient = new Thread(() => DoBeginAcceptTcpClient(_serverListener));
_threadDoBeginAcceptTcpClient.Start();
}
...
public void DoBeginAcceptTcpClient(TcpListener listener)
{
while(!_breakThread)
{
// Set the event to nonsignaled state.
TcpClientConnected.Reset();
// Start to listen for connections from a client.
Console.WriteLine("Waiting for a connection...");
// Accept the connection.
listener.BeginAcceptTcpClient(DoAcceptTcpClientCallback, listener);
// Wait until a connection is made and processed before continuing.
TcpClientConnected.WaitOne();
}
}
// Process the client connection.
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
// Get the listener that handles the client request.
TcpListener listener = (TcpListener)ar.AsyncState;
// End the operation and display the received data on the console.
Console.WriteLine("Client connection completed");
Clients.Add(listener.EndAcceptTcpClient(ar));
// Signal the calling thread to continue.
TcpClientConnected.Set();
}
WebAPI Controller:
public class ValuesController : ApiController
{
// GET api/values/5
public string Get(int id)
{
return $"Foo: {id}";
}
}
As mentioned earlier, what I seek is "communication" between the WebAPI and the windows service. How can I pass the "id" parameter from the WebAPI call to the _engineService object in my windows service? Perhaps something similar to WPF's MVVM Light Messenger? The idea is that it would then be parsed and sent to the appropriate TcpClient that is stored in the Clients List.
Any advice on how to achieve this will be appreciated. Please feel free to ask for clarification/more code.
Did you find any answer to your issue yet ?
I don't quite understand what you try to achieve looking for a communication between the two of them ? Do you want to somehow rely on TCP/IP to relay this id or in-memory ?
Potentially, you could consider a Mediator pattern and use this kind of library that seems quite useful in the case I understood : https://github.com/jbogard/MediatR
In a simpler approach, I would rely on events to achieve what you are trying to do, which is having a reactive communication from the HTTP request to the c++ users.
Did I understand you needs ? I am quite curious about the solution
I'm assuming you are trying to take an HTTP GET request's ID parameter and send it to TCP clients who are connected to the EngineService. If your EngineService is initialized before your ApiService, I think this is a question of how to get a handle to the one-and-only EngineService instance from within an ApiService's controller instances.
If I'm following you, you could make the EngineService a public static property of your ContainerService and reference it as ContainerService.EngineService from the controller (or anywhere in the app for that matter) or better register your EngineService as a singleton in a DI container an inject it into the ApiService.
Solution (calls to WebAPI trigger EngineService)
I now use RabbitMQ/EasyNetQ to achieve communication between the WebApi and the EngineService object containing my TCP clients.
I have incidentally split them into two separate Projects/Topshelf services now.
The following is the new "communication" component and it is instantiated in the EngineService constructor.
public class Communication
{
private readonly Logger _logger;
private readonly IBus _bus;
public delegate void ReceivedEventHandler(string data);
public event ReceivedEventHandler Received;
protected virtual void OnReceive(string data)
{
Received?.Invoke(data);
}
public Communication()
{
_logger = new Logger();
_bus = RabbitHutch.CreateBus("host=localhost", reg => reg.Register<IEasyNetQLogger>(log => _logger));
SubscribeAllQueues();
}
private void SubscribeAllQueues()
{
_bus.Receive<Message>("pipeline", message =>
{
OnReceive(message.Body);
});
}
public void SubscribeQueue(string queueName)
{
_bus.Receive<Message>(queueName, message =>
{
OnReceive(message.Body);
});
}
}
An event handler is then added.
This means that as soon as a message arrives to the bus, the data will be relayed to the event handler which will subsequently relay it to the first connected TCP client in the list.
public void Handler(string data)
{
//Console.WriteLine(data);
_clients[0].Client.Send(Encoding.UTF8.GetBytes(data));
}
...
_comPipe.Received += Handler;
And finally on the WebApi's controller:
public string Get(int id)
{
ServiceCom.SendMessage("ID: " + id);
return "value";
}
ServiceCom class. Allows sending a string message on the bus.
public static class ServiceCom
{
public static void SendMessage(string messageBody)
{
var messageBus = RabbitHutch.CreateBus("host=localhost");
messageBus.Send("pipeline", new Message { Body = messageBody });
}
}
Now that this is done, I am now looking to implement a way for the connected TCP clients to trigger updates/events in an additional SPA project that will act as a Portal / Client Management App.
My approach will probably make use of KnockOut.js and SignalR to achieve dynamic Views where TCP client events are displayed immediately and similarly actions on to WebAPI will trigger events in the TCP clients. I know it sounds like a bizarre combination of processes but it is all according to plan and working out as expected :)

SignalR group registration server methods not being hit

I've followed this guide, ASP.NET SignalR Hubs API Guide (How to manage group membership from the Hub class) and yet am unable to get my server side ShipmentHub methods to execute.
My ShipmentHub class looks like this:
public class ShipmentHub : Hub
{
IShipmentLogic shipmentLogic;
public ShipmentHub(IShipmentLogic shipmentLogic)
{
this.shipmentLogic = shipmentLogic;
}
public void CreateShipment(IEnumerable<Shipment> shipments)
{
// Clients.All.createShipment(shipments.OrderByDescending(s => s.CreatedDate));
Clients.Group(shipments.FirstOrDefault().ShipmentId)
.createShipment(shipments.OrderByDescending(s => s.CreatedDate));
}
public async Task WatchShipmentId(string shipmentId)
{
await Groups.Add(Context.ConnectionId, shipmentId);
Clients.Group(shipmentId).createShipment(shipmentLogic.Get(shipmentId, true));
}
public Task StopWatchingShipmentId(string shipmentId)
{
return Groups.Remove(Context.ConnectionId, shipmentId);
}
}
My client, more or less, looks like this:
var shipmentHub = $.connection.shipmentHub;
$.connection.hub.logging = true;
$.connection.hub.start();
var shipmentId = "SHP-W-GE-100122";
if (previousShipmentId) {
shipmentHub.server.stopWatchingShipmentId(previousShipmentId);
}
if (shipmentId.length) {
previousShipmentId = shipmentId;
shipmentHub.server.watchShipmentId(shipmentId);
}
In the SignalR client logs I see that these are being called:
SignalR: Invoking shipmenthub.WatchShipmentId
SignalR: Invoking shipmenthub.StopWatchingShipmentId
SignalR: Invoking shipmenthub.WatchShipmentId
And, aside from just the logs, these methods are being hit:
proxies['shipmentHub'].server = {
createShipment: function (shipments) {
return proxies['shipmentHub'].invoke.apply(proxies['shipmentHub'], $.merge(["CreateShipment"], $.makeArray(arguments)));
},
stopWatchingShipmentId: function (shipmentId) {
return proxies['shipmentHub'].invoke.apply(proxies['shipmentHub'], $.merge(["StopWatchingShipmentId"], $.makeArray(arguments)));
},
watchShipmentId: function (shipmentId) {
return proxies['shipmentHub'].invoke.apply(proxies['shipmentHub'], $.merge(["WatchShipmentId"], $.makeArray(arguments)));
}
};
And, as a final note, before I added the Watch and StopWatching methods, everything else worked (i.e., CreateShipment would call the Client.All.createShipment method without issue).
You need to wait for the connection to the server to be established before you can start calling methods on the server from the client. hub.start() returns a promise, here is the basic pattern for doing something once that promise is resolved.
var shipmentHub = $.connection.shipmentHub;
$.connection.hub.logging = true;
$.connection.hub.start().done(talkToServer);
var talkToServer=function(){
var shipmentId = "SHP-W-GE-100122";
if (previousShipmentId) {
shipmentHub.server.stopWatchingShipmentId(previousShipmentId);
}
if (shipmentId.length) {
previousShipmentId = shipmentId;
shipmentHub.server.watchShipmentId(shipmentId);
}
}
The issue is due to the parameterized constructor in ShipmentHub. According to Dependency Injection in SignalR:
By default, SignalR expects a hub class to have a parameterless constructor. However, you can easily register a function to create hub instances, and use this function to perform DI. Register the function by calling GlobalHost.DependencyResolver.Register.
So, you need to modify your Startup.Configuration(IAppBuilder app) method to resolve the dependency for you:
GlobalHost
.DependencyResolver
.Register(
typeof(ShipmentHub),
() => new ShipmentHub(new ShipmentLogic()));

SignalR Client to MVC5

I'm trying to hit my MVC5 SignalR Hub via a separate, tiny client application, to no avail.
Some background:
I have a regular ASP.NET application using SingalR 1.10, that I can hit with my client. Code:
ASP.NET Hub:
namespace SignalrTest
{
public class ScanHub : Hub
{
public void SendScan(string data, string xmlData)
{
Clients.All.broadcastMessage(data, xmlData);
}
}
}
Client:
connection = new HubConnection("http://localhost:2446/");
hubProxy = connection.CreateHubProxy("ScanHub");
connection.Start();
........
private static async Task RunAsync()
{
object[] param = new object[2];
param[0] = _Data;
param[1] = _xmlData;
await hubProxy.Invoke("SendScan", param);
}
and again, that's working fine. My MVC Hub is identical to the other (I've made sure to change the client HubConnection address), and I have my Startup.cs as:
[assembly: OwinStartupAttribute(typeof(SignalrTest.Startup))]
namespace SignalrTest
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
running my client, it fires off with no errors, but I get no response or any indication that anything has occurred on the MVC side.
Can anyone see where I'm going wrong with the MVC app? I'm unclear on whether I need to alter the routing. I'm happy to post any other code that would help resolve my issue. Thankyou in advance.
Are you really using SignalR 1.1? SignalR 1.1 doesn't use OWIN startup classes, and the MapSignalR method shouldn't even compile.
Try throwing your connection on the .NET client into an async method like so and doing a quick test if your connection is good or not.
private async void Connect()
{
connection = new HubConnection("http://localhost:2446/");
hubProxy = connection.CreateHubProxy("ScanHub");
await connection.Start();
//If using WPF you can test like so if not use whatever output method you prefer to see if it connects
if (Connection.State == Microsoft.AspNet.SignalR.Client.ConnectionState.Connected)
{
MessageBox.Show("Connected!");
}
else
{
MessageBox.Show("Fail!");
}
}

How do I call a SignalR hub method from the outside?

This is my Hub code:
public class Pusher : Hub, IPusher
{
readonly IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<Pusher>();
public virtual Task PushToOtherInGroup(dynamic group, dynamic data)
{
return _hubContext.Clients.Group(group).GetData(data);
}
}
I want call this method in another project with this code:
var pusher = new Pusher.Pusher();
pusher.PushToOtherInGroup("Test", new {exchangeTypeId, price});
I want call PushToOtherInGroup,when calling the method i don't get any error.but pusher does not work.
This is my Ui Code:
$(function() {
hub = $.connection.pusher;
$.connection.hub.start()
.done(function() {
hub.server.subscribe('newPrice');
console.log('Now connected, connection ID=' + $.connection.hub.id);
})
.fail(function() { console.log('Could not Connect!'); });
});
(function() {
hub.client.GetData = function (data) {
debugger;
};
});
What is my problem?
You can't instantiate and call a hub class directly like that. There is much plumbing provided around a Hub class by the SignalR runtime that you are bypassing by using it as a "plain-old class" like that.
The only way to interact with a SignalR hub from the outside is to actually get an instance of an IHubContext that represents the hub from the SignalR runtime. You can only do this from within the same process, so as long as your other "project" is going to be running in process with the SignalR code it will work.
If your other project is going to be running in another process then what you would want to do is expose a sort of "companion" API which is either another SignalR hub or a regular old web service (with ASP.NET web API) that you can call from this other application to trigger the behavior you want. Whichever technology you choose, you would probably want to secure this so that only your authenticated applications can call it.
Once you decide which approach you're going to take, all you would do to send messages out via the Pusher hub would be:
// Get the context for the Pusher hub
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<Pusher>();
// Notify clients in the group
hubContext.Clients.Group(group).GetData(data);
If you're looking to call a method in your hub from another project then it needs to reside within the same app domain. If it does here's how you can do it:
Call a hub method from a controller's action (don't mind the title, it works for your scenario)
Take a look at this link at the topic of (How to call client methods and manage groups from outside the Hub class).
Code example simply creates a singleton instance of the caller class and pass in the IHubContext into it's constructor. Then you have access to desired context.Clients in caller class's methods:
// This sample only shows code related to getting and using the SignalR context.
public class StockTicker
{
// Singleton instance
private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));
private IHubContext _context;
private StockTicker(IHubContext context)
{
_context = context;
}
// This method is invoked by a Timer object.
private void UpdateStockPrices(object state)
{
foreach (var stock in _stocks.Values)
{
if (TryUpdateStockPrice(stock))
{
_context.Clients.All.updateStockPrice(stock);
}
}
}
The methods within Hub are supposed to be called FROM a CLIENT.
If you want to send something TO a CLIENT - indeed, you have to use hubContext.

Categories

Resources