SignalR client in asp.net - asp.net

I created a server hub in an asp.net application like below
public class Calc : Hub
{
public void CalculateSomething(int a, int b)
{
// start working in a new thread
var task = Task.Factory.StartNew(() => DoCalculate(a, b));
// attach a continuation task to notify
// the client when the work is done
task.ContinueWith(t =>
{
System.Threading.Thread.Sleep(2000);
Clients.addMessage(t.Result);
Caller.notifyCalculateResult(t.Result);
System.Threading.Thread.Sleep(2000);
Caller.notifyCalculateResult("Completed");
Clients.addMessage("Completed");
});
}
private int DoCalculate(int p1, int p2)
{
// do some slow work on the input,
// e.g. call webservice or I/O.
int result = p1 + p2;
//int result = DoSlowWork(p1, p2);
return result;
}
}
Now in another asp.net application I created a client using SiganlR client. But it's not working correctly. I am looking to get data from server as it pushes to client
using System.Threading.Tasks;
using SignalR;
using SignalR.Client;
using SignalR.Client.Hubs;
namespace WebApplication2
{
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Connect to the service
var hubConnection = new HubConnection("http://localhost:3119/");
// Create a proxy to the chat service
var chat = hubConnection.CreateProxy("Calc");
// Print the message when it comes in
chat.On("addMessage", message =>Print(message));
// Start the connection
hubConnection.Start().Wait();
// Send a message to the server
chat.Invoke("CalculateSomething", 1, 2).Wait();
}
private async void Print(object message)
{
Response.Write(message);
}
}
}
The console client application works fine. The main problem is with asp.net beacause it fails to the handle call back from server.

Looks like you calling the server side method wrongly, try this
chat.Invoke("CalculateSomething", 1, 2).ContinueWith(task =>
{
Console.WriteLine("Value from server {0}", task.Result);
});

Related

How to make a Blazor page update the content of one html tag with incoming data from gRPC service

So i'm testing with Blazor and gRPC and my dificulty at the moment is on how to pass the content of a variable that is on a class, specifically the gRPC GreeterService Class to the Blazor page when new information arrives. Notice that my aplication is a client and a server, and i make an initial comunication for the server and then the server starts to send to the client data(numbers) in unary mode, every time it has new data to send. I have all this working, but now i'm left it that final implementation.
This is my Blazor page
#page "/greeter"
#inject GrpcService1.GreeterService GreeterService1
#using BlazorApp1.Data
<h1>Grpc Connection</h1>
<input type="text" #bind="#myID" />
<button #onclick="#SayHello">SayHello</button>
<p>#Greetmsg</p>
<p></p>
#code {
string Name;
string Greetmsg;
async Task SayHello()
{
this.Greetmsg = await this.GreeterService1.SayHello(this.myID);
}
}
The method that later receives the communication from the server if the hello is accepted there is something like this:
public override async Task<RequestResponse> GiveNumbers(BalconyFullUpdate request, ServerCallContext context)
{
RequestResponse resp = new RequestResponse { RequestAccepted = false };
if (request.Token == publicAuthToken)
{
number = request.Number;
resp = true;
}
return await Task.FromResult(resp);
}
Every time that a new number arrives i want to show it in the UI.
Another way i could do this was, within a while condition, i could do a call to the server requesting a new number just like the SayHello request, that simply awaits for a server response, that only will come when he has a new number to send. When it comes the UI is updated. I'm just reluctant to do it this way because i'm afraid that for some reason the client request is forgotten and the client just sit's there waiting for a response that will never come. I know that i could implement a timeout on the client side to handle that, and on the server maybe i could pause the response, with a thread pause or something like that, and when the method that generates the new number has a new number, it could unpause the response to the client(no clue on how to do that). This last solution looks to me much more difficult to do than the first one.
What are your thoughts about it? And solutions..
##################### UPDATE ##########################
Now i'm trying to use a singleton, grab its instance in the Blazor page, and subcribe to a inner event of his.
This is the singleton:
public class ThreadSafeSingletonString
{
private static ThreadSafeSingletonString _instance;
private static readonly object _padlock = new object();
private ThreadSafeSingletonString()
{
}
public static ThreadSafeSingletonString Instance
{
get
{
if (_instance == null)
{
lock(_padlock)
{
if (_instance == null)
{
_instance = new ThreadSafeSingletonString();
_instance.number="";
}
}
}
return _instance;
}
set
{
_instance.number= value.number;
_instance.NotifyDataChanged();
}
}
public int number{ get; set; }
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
And in Blazor page in code section i have:
protected override void OnInitialized()
{
threadSafeSingleton.OnChange += updateNumber();
}
public System.Action updateNumber()
{
this.fromrefresh = threadSafeSingleton.number + " que vem.";
Console.WriteLine("Passou pelo UpdateNumber");
this.StateHasChanged();
return StateHasChanged;
}
Unfortunatly the updatenumber function never gets executed...
To force a refresh of the ui you can call the StateHasChanged() method on your component:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-3.1
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
Hope this helps
Simple Request
After fully understanding that your problem is just to Update the Page not to get unsyncronous messages from the server with a bi directional connection. So jou just have to change your page like (please not there is no need to change the files generated by gRPC, I called it Number.proto so my service is named NumberService):
async Task SayHello()
{
//Request via gRPC
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
var client = new this.NumberService.NumberServiceClient(channel);
var request = new Number{
identification = "ABC"
};
var result = await client.SendNumber(request).RequestAccepted;
await channel.ShutdownAsync();
//Update page
this.Greetmsg = result;
InvokeAsync(StateHasChanged);//Required to refresh page
}
Bi Directional
For making a continious bi directional connection you need to change the proto file to use streams like:
service ChatService {
rpc chat(stream ChatMessage) returns (stream ChatMessageFromServer);
}
This Chant sample is from the https://github.com/meteatamel/grpc-samples-dotnet
The main challenge on this is do divide the task waiting for the gRPC server from the client. I found out that BackgroundService is good for this. So create a Service inherited from BackgroundService where place the while loop waiting for the server in the ExecuteAsyncmethod. Also define a Action callback to update the page (alternative you can use an event)
public class MyChatService : BackgroundService
{
Random _random = new Random();
public Action<int> Callback { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Replace next lines with the code request and wait for server...
using (_call = _chatService.chat())
{
// Read messages from the response stream
while (await _call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = _call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
if (Callback != null) Callback(displayMessage);
}
// Format and display the message
}
}
}
}
On the page init and the BackgroundService and set the callback:
#page "/greeter"
#using System.Threading
<p>Current Number: #currentNumber</p>
#code {
int currentNumber = 0;
MyChatService myChatService;
protected override async Task OnInitializedAsync()
{
myChatService = new MyChatService();
myChatService.Callback = i =>
{
currentNumber = i;
InvokeAsync(StateHasChanged);
};
await myChatService.StartAsync(new CancellationToken());
}
}
More information on BackgroundService in .net core can be found here: https://gunnarpeipman.com/dotnet-core-worker-service/

How to solve the issue like app service will not be call on every time?

I am working on Microsoft band technology, in one of my current scenario as I am sending Sms in background (means even app is not open in foreground also its working like sending Sms successfully).
For that I created the appservice by taking with Windows Runtime Component as a template.
In that app service I wrote the code for how to connect to the band and how to create the tile and how to register the Events.
And also wrote the code in Button_Pressed event for sending the Sms to others.
After that I configured the app service in package. Manifest file like this below figure.
enter image description here
My issue is appservice will not be call on every time.it will be call on two or three times per day.
Is the problem in my code or issue of Microsoft health app?
using Microsoft.Band;
using Microsoft.Band.Notifications;
using Microsoft.Band.Tiles;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
using Windows.UI.Popups;
namespace AppServiceEvents
{
public sealed class MyTileEventHandler : IBackgroundTask
{
private BackgroundTaskDeferral backgroundTaskDeferral;
private AppServiceConnection appServiceConnection;
IBandClient bandClient;
//BandTile tile;
//IEnumerable<BandTile> tiles;
//Guid tileGuid;
public void Run(IBackgroundTaskInstance taskInstance)
{
//throw new NotImplementedException();
this.backgroundTaskDeferral = taskInstance.GetDeferral(); taskInstance.Canceled += OnTaskCanceled;
//await ConnectBand();
// Add handlers for tile events
BackgroundTileEventHandler.Instance.TileOpened += EventHandler_TileOpened;
BackgroundTileEventHandler.Instance.TileClosed += EventHandler_TileClosed;
BackgroundTileEventHandler.Instance.TileButtonPressed += EventHandler_TileButtonPressed;
// Set up handler for incoming app service request messages
var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
this.appServiceConnection = details.AppServiceConnection;
this.appServiceConnection.RequestReceived +=OnRequestReceived;
}
private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
//throw new NotImplementedException();
var messageDeferral = args.GetDeferral();
ValueSet response = new ValueSet();
ValueSet request = args.Request.Message;
// Decode the received message and call the appropriate handler
BackgroundTileEventHandler.Instance.HandleTileEvent(request);
// Send the response
await args.Request.SendResponseAsync(response);
messageDeferral.Complete();
}
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
//throw new NotImplementedException();
DisconnectBand();
if (this.backgroundTaskDeferral != null)
{
this.backgroundTaskDeferral.Complete();
}
}
private void EventHandler_TileOpened(object sender, BandTileEventArgs<IBandTileOpenedEvent> e)
{
// TODO: Handle the tile opening
LogEvent(String.Format("EventHandler_TileOpened: TileId={0} Timestamp={1}", e.TileEvent.TileId, e.TileEvent.Timestamp));
// We create a Band connection when the tile is opened and keep it connected until the tile closes.
ConnectBand();
}
private void EventHandler_TileClosed(object sender, BandTileEventArgs<IBandTileClosedEvent> e)
{
// TODO: Handle the tile closing
LogEvent(String.Format("EventHandler_TileClosed: TileId={0} Timestamp={1}", e.TileEvent.TileId, e.TileEvent.Timestamp));
// Disconnect the Band now that the user has closed the tile.
DisconnectBand();
}
private void EventHandler_TileButtonPressed(object sender, BandTileEventArgs<IBandTileButtonPressedEvent> e)
{
// TODO: Handle the button push
LogEvent(String.Format("EventHandler_TileButtonPressed: TileId={0} PageId={1} ElementId={2}", e.TileEvent.TileId, e.TileEvent.PageId, e.TileEvent.ElementId));
// We should have a Band connection from the tile open event, but in case the OS unloaded our background code
// between that event and this button press event, we restore the connection here as needed.
ConnectBand();
var tileid = e.TileEvent.TileId;
SendMessage(tileid);
//await new MessageDialog("This is an background task"+tileid).ShowAsync();
}
private void ConnectBand()
{
if (this.bandClient == null)
{
// Note that we specify isBackground = true here to avoid conflicting with any foreground app connection to the Band
Task<IBandInfo[]> getBands = BandClientManager.Instance.GetBandsAsync(isBackground: true);
getBands.Wait();
IBandInfo[] pairedBands = getBands.Result;
if (pairedBands.Length == 0)
{
LogEvent("ERROR - No paired Band");
}
try
{
Task<IBandClient> connect = BandClientManager.Instance.ConnectAsync(pairedBands[0]);
connect.Wait();
this.bandClient = connect.Result;
}
catch
{
LogEvent("ERROR - Unable to connect to Band");
}
}
}
/// <summary>
/// If currently connected to the Band, then disconnect.
/// </summary>
private void DisconnectBand()
{
if (bandClient != null)
{
bandClient.Dispose();
bandClient = null;
}
}
const string LogFileName = "EventLog.txt";
/// <summary>
/// Log event strings to a text file
/// </summary>
/// <param name="eventString">String describing the event</param>
private void LogEvent(string eventString)
{
using (FileStream stream = new FileStream("EventLog.txt", FileMode.Append))
{
string outputString = String.Format("{0}: {1}\r\n", DateTime.Now, eventString);
byte[] outputASCII = Encoding.ASCII.GetBytes(outputString);
stream.Write(outputASCII, 0, outputASCII.Length);
}
}
private async void SendMessage(Guid tileGuid)
{
try
{ // Send a message to the Band for one of our tiles,
// and show it as a dialog.
await bandClient.NotificationManager.SendMessageAsync(tileGuid, "Task", "This is an AppService Task", DateTimeOffset.Now, MessageFlags.ShowDialog);
}
catch (BandException ex)
{
// handle a Band connection exception
}
}
}
}
Please tell me how to resolve the above issue and tell me how to debug the appservice in VS 2015 but not a background task.
Regards,
Pradeep

SignalR: How to call .Net client method from server?

I want to send data to my console application wich have a connection to my "someHub". I tried to do as described in example from a link but got no result.
Server side code:
[HubName("somehub")]
public class SomeHub : Hub
{
public override Task OnConnected()
{
//Here I want to send "hello" on my sonsole application
Clients.Caller.sendSomeData("hello");
return base.OnConnected();
}
}
Clien side code:
public class Provider
{
protected HubConnection Connection;
private IHubProxy _someHub;
public Provider()
{
Connection = new HubConnection("http://localhost:4702/");
_someHub = Connection.CreateHubProxy("somehub");
Init();
}
private void Init()
{
_someHub.On<string>("sendSomeData", s =>
{
//This code is not reachable
Console.WriteLine("Some data from server({0})", s);
});
Connection.Start().Wait();
}
}
What is the best solution for implementing this and what is the reason why i am not able to invoke the client method?
Are you trying to talk to clients outside of Hub? If yes then you will have to get a HubContext outside of Hub. And then you can talk all the clients.
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
SignalR Server using Owin Self Host
class Program
{
static void Main(string[] args)
{
string url = "http://localhost:8081/";
using (WebApplication.Start<Startup>(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(3000);
context.Clients.All.addMessage("Current integer value : " + i.ToString());
}
Console.ReadLine();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
// Turn cross domain on
var config = new HubConfiguration { EnableCrossDomain = true };
config.EnableJavaScriptProxies = true;
// This will map out to http://localhost:8081/signalr by default
app.MapHubs(config);
}
}
[HubName("MyHub")]
public class MyHub : Hub
{
public void Chatter(string message)
{
Clients.All.addMessage(message);
}
}
Signalr Client Console Application consuming Signalr Hubs.
class Program
{
static void Main(string[] args)
{
var connection = new HubConnection("http://localhost:8081/");
var myHub = connection.CreateHubProxy("MyHub");
connection.Start().Wait();
// Static type
myHub.On<string>("addMessage", myString =>
{
Console.WriteLine("This is client getting messages from server :{0}", myString);
});
myHub.Invoke("Chatter",System.DateTime.Now.ToString()).Wait();
Console.Read();
}
}
To run this code, create two separate applications, then first run server application and then client console application, then just hit key on server console and it will start sending messages to the client.

periodically sending messages to all clients using signalr

I want to send some data from server to all connected clients using hubs after a specific interval. How can I accomplish this using signalr hubs.
Spin up the System.Threading.Timer, and from it's callback broadcast the message using specific hub.
Global.asax:
private Timer timer;
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHubs("~/signalr2");
timer = new Timer(TimerCallback(timerCallback), null, Timeout.Infinite, 1000);
}
}
Check the “Broadcasting over a Hub from outside of a Hub” section in SignalR wiki page.
Use ReactiveExtensions and then setup an Observable.Interval call. Then reactive will automatically call the lambda which can broadcast to your clients.
I have stumbled upon this post by Jason Roberts => http://dontcodetired.com/blog/post/Using-Server-Side-Timers-and-SignalR-in-ASPNET-MVC-Applications.aspx
He uses IRegisteredObject and HostingEnvironment.RegisterObject then a System.Threading.Timer in the class that does the work, I haven't tried it myself, but it looks exactly the sort of thing.
Just add
Thread.Sleep(5000);
in your send Method.
Ex:
public void Send(string name, string message)
{
Thread.Sleep(5000);
//call the broadcast message to upadate the clients.
Clients.All.broadcastMessage(name, message);
}
Hope it helps.
Edit
The following code renders the current time for every 5 seconds.
Here is script for it:
<script type="text/javascript">
$(function () {
$.connection.hub.logging = true;
$.connection.hub.start();
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
//Appending the responce from the server to the discussion id
chat.client.currentTime = function (time) {
$('#discussion').append("<br/>" + time + "<br/>");
};
// Start the connection.
$.connection.hub.start().done(function () {
//Call the server side method for every 5 seconds
setInterval(function () {
var date = new Date();
chat.client.currentTime(date.toString());
}, 5000);
});
});
</script>
<div id="discussion"></div>
And on the HubClass write the following:
public class ChatHub: Hub
{
public void currentTime(string date)
{
Clients.All.broadCastTime(date);
}
}

Cannot receive messages wrong context connectionid

So I was using SignalR version .5 and everything was working fine. But trying to upgrade to version 1 to use the connectionSlow method. Unfortunately it seems to have broken when I have upgraded. I have an mvc application, and I am trying to use signalr to push data to the client. I want the connection to be open forever.
The server will not send messages to the client. After some investigations using a LoggingPiplineModule i found that the context.Connection.Identifier is not the connextionID of the connected browser, its asif it is trying to send it to someone else.
My Hub only has a few methods:
public void JoinGroup(string groupID)
{
if (!String.IsNullOrEmpty(Context.User.Identity.Name) && (!String.IsNullOrEmpty(groupID)))
{
Groups.Add(Context.ConnectionId, groupID.Trim());
}
else
{
LoggerSingleton.Instance.Logger.Error("Error: Could not join group as user is not logged in or group is null");
}
}
public void LeaveGroup(string groupID)
{
if (!String.IsNullOrEmpty(Context.User.Identity.Name) && (!String.IsNullOrEmpty(groupID)))
{
Groups.Remove(Context.ConnectionId, groupID.Trim());
}
else
{
LoggerSingleton.Instance.Logger.Error("Error: Could not leave group as user is not logged in or group is null");
}
}
public static void SendCallLog(CallLog newCall, int groupID)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CommandCentreHub>();
context.Clients.Group(groupID.ToString()).addMessage(CallLog.ToJson(newCall), groupID.ToString());
}
And my javascript:
conChat = $.connection.commandcentrehub;
// Push method for signalR, process the pushed message passed from the server
conChat.addMessage = function (message, groupID) {
var call = JSON.parse(message);
updateTableImages($('#groupContent' + groupID), call, groupID);
updateTableImages($('#groupContent' + 'All'), call, 'All');
applyFilter();
};
$.connection.hub.start().done(function () {
$('.groupID').each(function () {
conChat.server.joinGroup(this.id.replace("group", ""));
});
});
And my global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteTable.Routes.MapHubs();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
log4net.Config.XmlConfigurator.Configure();
}
I get no errors in chrome dev, and joingroup is working properly but when the server calls addMessage I get nothing.
Ok I fixed the issue.
It was with my javascript.
The below:
conChat.addMessage = function (message, groupID) { ...
should be:
conChat.client.addMessage = function (message, groupID) {
Hope this helps someone...

Resources