Nothing happens when calling method from within SignalR3 rc1 vnext application - c#

In my current setup, i have an ASP.NET 5 vNext project running.
I have setup SignalR Server 3.0.0-rc1-final and i am able to connect to my hub through my webinterface:
var visitorHub = $.connection.visitorsHub;
visitorHub.client.visitorEvent = function (message) {
$("#visitorinfo").append("<li>" + message + "</li>");
};
$.connection.hub.start().done(function () {
visitorHub.invoke("listenToEvents", "TestID");
});
So we are listening on visitorEvent from the hub and visitorEvent is called when listenToEvents is invoked.
My challenge come now, that i'm trying to notify from within the ASP.NET application. Using the build in IoC an SqlDependency is used to listen to events in the SQL server. Again this is working as intended, but when i'm trying to invoke the hub through it's HubContext nothing happens.
I have injected the IConnectionManager and able to get hold of my HubContext using:
var context = this.manager.GetHubContext<VisitorsHub>();
but when i do the following:
context.Clients.All.visitorEvent("2");
nothing happens.
I'm not sure why nothing happens and how i'm going to debug this?
My VisitorHub code is:
public class VisitorsHub : Hub
{
public async Task ListenToEvents(string visitorId)
{
this.NotifyVisitorListeners();
}
public void NotifyVisitorListeners()
{
this.Clients.All.visitorEvent("Event");
}
}

You can enable client side logging via:
$.connection.hub.logging = true;
$.connection.hub.start();
or without generated proxy:
var connection = $.hubConnection();
connection.logging = true;
connection.start();
Otherwise when using invoke (method without a proxy), methods aren't renamed to camelCase, but remain as they are, in your case CamelCase.
To overcome this you can simply rename method name on either end, or add HubMethodName decorator in your backend:
[HubMethodName("listenToEvents")]
public async Task ListenToEvents(string visitorId)
{
this.NotifyVisitorListeners();
}

Related

Unable to return a value from SignalR Client from a different method

I'm working on a Winforms app that executes SQL Procedures through a SignalR client. I'm relatively new to using SignalR and am still wrapping my head around it.
I start off by running my connection method to establish a connection with my SignalR service. I have two addresses configured ready for when I puslish but the DEV configuration leads to the SignalR service I am hosting locally.
Connection to SignalR (ConnectHub)
private async Task ConnectHub()
{
string hubAddress = "";
#if DEBUG
HubAddress = ConfigurationManager.AppSettings["HubAddress_DEV"];
#else
HubAddress = ConfigurationManager.AppSettings["HubAddress_PROD"];
#endif
if (string.IsNullOrEmpty(hubAddress))
{
MessageBox.Show("Hub Address is missing from configuration.");
}
ConnectionHandler.Client = new HubClient(hubAddress, "MyHub");
ConnectionHandler.Client.MyAlert += ConnectionHandler.ClientOnMyAlert;
ConnectionHandler.Client.ServerErrorEvent += ConnectionHandler.ClientOnServerErrorEvent;
await ConnectionHandler.Client.Connect(new List<string>() {
VehicleInfo.ThisVehicle.WarehouseCode,
VehicleInfo.ThisVehicle.VehicleName
});
}
My client is stored globally in my ConnectionHandler class where my event handlers are also kept. (I have breakpoints on these as I have not implemented them yet)
ConnectionHandler Class
public static class ConnectionHandler
{
public static HubClient Client { get; set; }
public static void ClientOnServerErrorEvent(string error)
{
throw new NotImplementedException(); //Currently not implemented
}
public static async Task ClientOnMyAlert(EnumMyAlertType alerttype, string message, Exception exception)
{
await Task.Yield(); //Currently not implemented
}
}
When I call the code to Invoke the procedure in my SignalR client, it returns a DataTable to me which is the intended result.
Call to SignalR
await ConnectHub();
DataTable dt = await ConnectionHandler.Client.Connection.InvokeCoreAsync<DataTable>(
"FetchStatuses",
new object[0]); //This call works as intended and returns a populated DataTable
StatusInfo = new CStatuses();
All the above code is currently done on the main form, however I wanted to move this call to SignalR into a constructor to try and tidy things up.
The problem comes when I try to move this call into another method, the program hangs as I don't think it has received the return value from SignalR, I have placed a breakpoint beneath it and it is not reached. A TryCatch reveals nothing as it hangs within the "Try" with no exception.
Calling from contructor
public CStatuses()
{
Statuses = new List<CStatus>();
var dataTable = ConnectionHandler.Client.Connection.InvokeCoreAsync<DataTable>("FetchStatuses",
new object[0])
.Result; //My program hangs on this line and proceeds no further
I am at a loss as to why it is doing this when I can get a value from the client from the form and when other members of my team have tried to do the same thing they can make a call to SignalR also from a different method.
Does anyone have any ideas as to how I can make this work?
I realize this has gotten quite long but if I can elaborate on things please let me know
FIXED CODE THANKS TO SOLUTION:
I have moved the code from my CStatuses constructor into a new async method within the same class and called it after initialization. This removes the need for .Result and appears to solve the problem for me.
public async Task PopulateStatuses()
{
var dataTable = await ConnectionHandler.Client.Connection.InvokeCoreAsync<DataTable>("FetchStatuses",
new object[0]);
Statuses = new List<CStatus>();
foreach (DataRow row in dataTable.Rows)
{
var status = new CStatus
{
StatusId = Common.Utility.GetInt16Value(row["StatusID"]),
StatusCode = Common.Utility.GetStringValue(row["StatusCode"]),
Description = Common.Utility.GetStringValue(row["Description"])
};
Statuses.Add(status);
}
}
You are running into a deadlock with the .Result call, I would suggest creating an async method in the CStatuses class and after you initialize your CStatuses class call the websocket for data.

Xamarin - Using httpClient in .Net Standard Library

I've created Xamarin project, and I added several .Net standard class libraries to it (one for each layer: data access, service layer etc.). Then, In ServiceLayer project, I implemented method, which fetch data from my Web API (external ASP Net Core project). When it comes to httpClient.GetAsync(), android app crash. What's more, when I cut this piece of code, and paste it in default xamarin .Net standard library, everything works. Any ideas?
Code is here:
HttpClient httpClient = new HttpClient();
var responseMessage = await httpClient.GetStringAsync(uri);
UPDATE:
In Viewmodel:
constructor(IServiceLayerService service){
_ServiceLayerService = service;
GetTestCommand = new DelegateCommand(async () => await GetTests());
}
public async Task GetTests()
{
TestObservableCollection = new ObservableCollection<List<Test>>(await _ServiceLayerService.GetTestModels());
}
Update 2:
I've changed my async method call, in the way presented in first answer. Now, when I'm trying to execute code, app also crashes, but I'm receiving error messages:
07-05 14:39:04.518 F/ (25383): /Users/builder/jenkins/workspace/xamarin-android-d15-7/xamarin-android/external/mono/mono/mini/debugger-agent.c:4897: Could not execute the method because the containing type is not fully instantiated. assembly:<unknown assembly> type:<unknown type> member:(null) signature:<none>
07-05 14:39:04.518 F/libc (25383): Fatal signal 6 (SIGABRT), code -6 in tid 25383 (com.companyname), pid 25383 (com.companyname)
Maybe I'm doing something wrong with Unity dependency injection, so here is registration of service layer classes in App.xaml.cs
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<TestsListPage, TestsListViewModel>();
containerRegistry.Register<IServiceLayerService, ServiceLayerService>();
}
The problem is with the async command delegate
GetTestCommand = new DelegateCommand(async () => await GetTests());
The command delegate results in an async void, which wont allow exceptions to be caught as they are considered fire and forget methods
Reference Async/Await - Best Practices in Asynchronous Programming
Create an event and handler to manage the async call
private event EventHandler gettingTests = delegate { };
private async void OnGettingTests(object sender, EventArgs args) {
try {
await GetTests();
} catch (Exception ex) {
//...handle exception
}
}
Note that event handlers are the one exception to the rule that allows async void
Subscribe to the event in the constructor and raise the event in the command delegate
constructor (IServiceLayerService service) {
_ServiceLayerService = service;
this.gettingTests += OnGettingTests;
GetTestCommand = new DelegateCommand(() => gettingTests(this, EventArgs.Empty));
}
So now when the command is invoked, it will raise the event and the async handler can make async calls correctly.

Why is the hub context connection ID inaccessible at runtime in my VERY simple method?

Let me start by saying this all works perfectly at the moment except for one thing - the notification update for the progress bar goes to all clients (as you would expect given that the code example I used sends to ...Clients.All).
All I want to do is send the notification back to the client that initiated the current call to the hub. That's it, nothing else. There is no concept of "logging in" on this website so there's no user identity stuff to work with.
My method is:
public void NotifyUpdates(decimal val)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();
if (hubContext != null)
{
//"updateProgress" is javascript event trigger name
await hubContext.Clients.All.updateProgress(val);
}
}
So back in my view I subscribe to "updateProgress" and it works fine - the progress bar updates as desired. But if another client happens to be connected to the hub, when one runs the async task and causes the NotifyUpdates method to run, then ALL connected clients see the taskbar update, which is a bit confusing for them!
In debug, if I inspect hubContext at runtime I can see a Clients property and that has a Connection property which has an Identity property with a unique GUID. Perfect! Just what I want to use. But... I cannot access it! If I try:
var currentConnection = hubContext.Clients.Connection;
...then I simply get a
"does not contain a definition for 'Connection'"
error, which I simply don't understand.
I've tried accessing Context.ConnectionId from the method too, but Context is null at that point so I'm a bit confused. The server method that uses NotifyUpdates to send information back to the client is called via a normal asp.net button, not via AJAX.
Clarification on structure
I think there is a degree of confusion here. It's a very simply webpage with an asp.net button control on it. The eventhandler for that button invokes an async method to return data from the server via a service call/repository.
Inside the async method, it has to process each returned data line and make three or four remote web api calls. On each loop I make a call back to the NotifyUpdates SignalR method shown above with a percentage complete number so this can update back to the client via an eventhandler for the method name specified (updateProgress as shown above). There could be dozens of data lines and each data line requires several Web API calls to a remote server to add data. This can take several seconds per iteration, hence me sending back the "update progress" to the client via that updateProgress method call.
NEW ANSWER
Based on your comments I made the following little test:
It will allow clients to connect to the hub with a clientName, and every client is listening to updates send to them. We will have a group defined for them to be able to notify them from the server side.
I made a dummy progress simulator class to throw some update values to the users.
The code:
Hub class:
public class EventsForceHub : Hub {
public static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();
// allow users to join to hub and get s dedicated group/channel for them, so we can update them
public async Task JoinGroup(string clientName) {
string clientID = Context.ConnectionId;
ClientInfo.clients.Add(clientID, new MyAppClient(clientID, clientName));
await Groups.Add(clientID, clientName);
// this is just mockup to simulate progress events (this uis not needed in real application)
MockupProgressGenerator.DoJob(clientName, 0);
}
public static void NotifyUpdates(decimal val, string clientName) {
// update the given client on his group/channel
hubContext.Clients.Group(clientName).updateProgress(val);
}
}
Some little helper classes:
// client "storage"
public static class ClientInfo {
public static Dictionary<string, MyAppClient> clients = new Dictionary<string, MyAppClient>();
// .. further data and methods
}
// client type
public class MyAppClient {
public string Id { get; set; }
public string Name { get; set; }
// .. further prooerties and methods
public MyAppClient(string id, string name) {
Id = id;
Name = name;
}
}
// this a completely made up and dumb class to simulate slow process and give some simple progress events
public static class MockupProgressGenerator {
public static void DoJob(string clientName, int status) {
if (status < 100) {
Task.Delay(1000).ContinueWith(a =>
{
EventsForceHub.NotifyUpdates(status += 20, clientName);
DoJob(clientName, status);
});
}
}
}
Let's see two simple clients in JS:
$(function () {
var eventsForceHub = $.connection.eventsForceHub;
$.connection.hub.start().done(function () {
$('body').append("Joining with Name: Jerry");
eventsForceHub.server.joinGroup("Jerry");
});
eventsForceHub.client.updateProgress = function (val) {
// message received
$('body').append('<br>').append("New Progress message: " + val);
};
});
For simplicity, same code, with different params, I even put this in two different html pages and stated execution in slightly different timing.
$(function () {
var eventsForceHub = $.connection.eventsForceHub;
$.connection.hub.start().done(function () {
$('body').append("Joining with Name: Tom");
eventsForceHub.server.joinGroup("Tom");
});
eventsForceHub.client.updateProgress = function (val) {
// message received
$('body').append('<br>').append("New Progress message: " + val);
};
});
See it in action:
FIRST ANSWER
I made a small web application to verify your claim. You may create the following to be able to isolate the issue from other possible problems.
I created an empty Web Application and included SignalR.
This is the hub class:
public class EventsForceHub : Hub {
public void NotifyUpdates(decimal val) {
var hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();
if (Context != null) {
string clientID = Context.ConnectionId; // <-- on debug: Ok has conn id.
object caller = Clients.Caller; // <-- on debug: Ok, not null
object caller2 = Clients.Client(clientID); // <-- on debug: Ok, not null
Clients.Caller.updateProgress(val); // Message sent
Clients.Client(clientID).updateProgress(val); // Message sent
}
if (hubContext != null) {
//"updateProgress" is javascript event trigger name
hubContext.Clients.All.updateProgress(val); // Message sent
}
}
}
This is the web page:
<script src="Scripts/jquery-1.10.2.min.js"></script>
<script src="Scripts/jquery.signalR-2.2.2.min.js"></script>
<script src="signalr/hubs"></script>
<script type="text/javascript">
$(function () {
var eventsForceHub = $.connection.eventsForceHub;
$.connection.hub.start().done(function () {
// send mock message on start
console.log("Sending mock message: " + 42);
eventsForceHub.server.notifyUpdates(42);
});
eventsForceHub.client.updateProgress = function (val) {
// message received
console.log("New Progress message: " + val);
};
});
</script>
Try to build an application as little as this to isolate the issue. I have not had any of the issues you mentioned.
For the sake of simplicity and using the debugger I took away the await and async.
Actually, SignalR will take care of that for you. You will get a new instance of your Hub class at every request, no need to force asynchrony into the methods.
Also, GlobalHost is defined as static which should be shared between instances of your Hub class. Using in an instance method does not seem like a very good idea. I think you want to use the Context and the Clients objects instead. However, while debugging we can verify that using GlobalHost also works.
Some debugger screenshots showing runtime values of callerId, Clients.Caller and Clients.Client(clientID):
Understanding SignalR better will help you a lot in achieving your goal.
Happy debugging!
If you want to send the notification back to the client
you should not call
hubContext.Clients.All.updateProgress(val);
instead try
accessing the current user's ConnectionId and use Clients.Client
hubContext.Clients.Client(Context.ConnectionId);

SignalR - Send message OnConnected

I've been experimenting with SignalR today and It's really neat. Basically what I wanted to achieve is the following:
As soon as a device connects it should send a message to the first one. If there are more devices than 1 connected I would like to send two messages. One to all except the last connected client. And one message to only the last connected client.
The code I've been using works perfect when I place it in a custom API controller and basically call the action, but that's not what I want.
I would like to send the messages as soon as a device connects within OnConnected without any user interaction, but when I place my code inside the OnConnected override it stops working. It doesn't send to the specific clients anymore (first connected and last connected).
I hope someone is able to help me out with this, because I've been banging my head for a few hours now.
public override System.Threading.Tasks.Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId, UserHandler.ConnectedIds.Count + 1);
int amountOfConnections = UserHandler.ConnectedIds.Count;
var lastConnection = UserHandler.ConnectedIds.OrderBy(x => x.Value).LastOrDefault();
var allExceptLast = UserHandler.ConnectedIds.Take(amountOfConnections - 1).Select(x => x.Key).ToList();
if (amountOfConnections == 1)
{
Clients.Client(UserHandler.ConnectedIds.First().Key).hello("Send to only(also first) one");
}
else
{
Clients.Clients(allExceptLast).hello("Send to everyone except last");
Clients.Client(lastConnection.Key).hello("Send to only the last one");
}
return base.OnConnected();
}
Unless I miss something from your question, the solution looks pretty simple to me, you just need to switch to using
Clients.Caller.hello("Send to only the last one");
instead of trying to understand yourself who's the last connected id. Same for the other ones, you can use:
Clients.Others.hello("Send to everyone except last");
You do not need all the logic you setup, those 2 lines do what you need, and they work inside OnConnected.
Thanks for all the help (upvoted you guys). Actually found the problem.. it was inside my client. I first subscribed to the 'hello' function and after that I started the HubConnection. As soon as I changed this order everything worked fine.
It worked with the following client code:
private async Task ConnectToSignalR()
{
var hubConnection = new HubConnection("url");
hubConnection.Headers["x-zumo-application"] = "clientapikey";
IHubProxy proxy = hubConnection.CreateHubProxy("ChatHub");
proxy.On<string>("hello", async (msg) =>
{
Console.WriteLine(msg);
});
await hubConnection.Start();
}
Since you haven't established a connection yet, trying to call your client .hello() function within OnConnected is not possible at this point. However, we can define a server hub method and immediately call that upon our connection .done callback. Then, in our new server method we can reallocate the logic you currently have in OnConnected.
This will change our setup quite a bit and introduce some additional steps, but observe the following example...
// WhateverHub
public override Task OnConnected()
{
return base.OnConnected()
}
public void AfterConnected()
{
// if(stuff) -- whatever if/else first user/last user logic
// {
Clients.Caller.hello("message")
// }
}
var proxy= $.connection.whateverHub;
proxy.client.hello = function(message) {
// last step in event chain
}
$.connection.hub.start().done(function () {
proxy.server.afterConnected() // call AfterConnected() on hub
});
So the basic idea here is to first
Connect => .done(function() { ... });
Call server.afterConnected()
Execute logic within that method
If we're satisfied with conditions call our .hello() client function
Note - this implementation is for a JavaScript client - but the same idea can be translated to a .net client. This is mostly an architectural issue.
well... you are returning a task... so i think that may be the issue...
you should first execute your code and then return the task... or put a ContinueWith... like...
public override Task OnConnected()
{
Task task = new Task(() =>
{
UserHandler.ConnectedIds.Add(Context.ConnectionId, UserHandler.ConnectedIds.Count + 1);
int amountOfConnections = UserHandler.ConnectedIds.Count;
var lastConnection = UserHandler.ConnectedIds.OrderBy(x => x.Value).LastOrDefault();
var allExceptLast = UserHandler.ConnectedIds.Take(amountOfConnections - 1).Select(x => x.Key).ToList();
if (amountOfConnections == 1)
{
Clients.Client(UserHandler.ConnectedIds.First().Key).hello("Send to only(also first) one");
}
else
{
Clients.Clients(allExceptLast).hello("Send to everyone except last");
Clients.Client(lastConnection.Key).hello("Send to only the last one");
}
});
task.ContinueWith(base.OnConnected());
return task;
}
I haven't tested that... its just a guess..

C# winform application with threaded WCF client

I'm building an application that uses a WCF client to retrieve data from my server.
I want my call to the service to be asynchronous because many of them need to change the UI and I don't want to lose responsiveness from my app.
I tried using *Completed and *Async:
ServiceUserClient client = new ServiceUserClient();
client.FindUserCompleted += delegate(object sender, FindUserCompletedEventArgs e)
{
// here e.Result always fails
};
client.FindUserAsync(text);
Inside the *Completed delegate I always get an error (Connection closed by remote host: I enabled every logging I could find but I still don't understand why I get these errors)
Synchronous calls always work.
I have a class that handles all the calls to the service.
Is there a way to have syncronous calls inside something like a threaded class?
Are you setting the client side bindings to match what the server accepts?
You should also try testing it with the WCF test client (normally under %Program Files%\Microsoft Visual Studio 10.0\Common7\IDE\WcfTestClient.exe). If the test client works then check the bindings.
Is your call even getting to the server? I've had similar errors happen when serializing the response from the server to the client, so you might want to check for that. If you get to your server then the bindings are not the problem but rather there is a serialization problem. Do you have "sets" on the data model properties that are trying to get deserialized on the server?
I know this is no answer but I haven't been here enough to be allowed comments...and I've been where you are, totally frustrating.
I ended up creating my own async methods using BackgroundWorker this way (probably not the best way but it works):
// this is the click event on my search button
private void FindUser_Click(object sender, EventArgs e)
{
this.UserListSearch.Enabled = false;
this.UserListSearch.Items.Clear();
Model.FindUser(FindText.Text.ToUpper(), userlist =>
{
foreach (User u in userlist)
{
ListViewItem item = new ListViewItem(u.UserName);
item.Name = u.UserName;
item.SubItems.Add(u.Description);
this.UserListSearch.Items.Add(item);
}
this.UserListSearch.Enabled = true;
});
}
// this is the function I call when I need async call
public void FindUser(string text, Action<User[]> callback)
{
CreateBackgroundWorker<User[]>(() =>
{
ServiceUsersClient client = new ServiceUsersClient();
var results = client.FindUser(text);
client.Close();
return results;
}, callback);
}
// this is my utility function to create a bgworker "on demand"
private void CreateBackgroundWorker<T>(Func<T> dowork, Action<T> callback)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
T result = dowork.Invoke();
(callback.Target as Form).Invoke(callback, result);
};
worker.RunWorkerAsync();
}

Categories

Resources