Calling SignalR from API at another project - No error nor notification - c#

I have a WebSite integrated with SignalR. It functions well, and it has a button which sends popup notification to all clients who are online. It works well when I click on the button.
My API is in another project but in the same Solution. I want to send the above notification by calling from the API side. Basically, a mobile app will send a request to API and then API will send a notification to all online web clients.
Below code runs and not gives the notification nor any error.
Is this fundamentally correct? Appreciate your help
API code (at WebAPI project)
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
GMapChatHub sendmsg = new GMapChatHub();
sendmsg.sendHelpMessage(record_id.ToString());
return "Done";
}
C# code (at Web project)
namespace GMapChat
{
public class GMapChatHub : Hub
{
public void sendHelpMessage(string token)
{
var context = GlobalHost.ConnectionManager.GetHubContext<GMapChatHub>();
context.Clients.All.helpMessageReceived(token, "Test help message");
}
}
}
Home.aspx file (at Web project)
var chat = $.connection.gMapChatHub;
$(document).ready(function () {
chat.client.helpMessageReceived = function (token,msg) {
console.log("helpMessageReceived: " + msg);
$('#helpMessageBody').html(msg)
$('#helpModal').modal('toggle');
};
}

You can not call that hub directly. Firs you need to install the .net client for SignalR from nuget. Then you need to initialize it like this :
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
using (var hubConnection = new HubConnection("your local host address"))
{
IHubProxy proxy= hubConnection.CreateHubProxy("GMapChatHub");
await hubConnection.Start();
proxy.Invoke("sendHelpMessage",record_id.ToString()); // invoke server method
}
// return sth. IEnumerable<string>
}
And opening a new connection per request may not be good idea you may make it per session (if you use) or static or time fashioned.

Related

.NET WPF equivalent of laravel-echo

I am building a simple restaurant management system in WPF. I have my backend in Laravel. I needed to setup a web socket to get real-time notifications on WPF app when a customer places an order from mobile app. I configured the web socket in Laravel using beyondcode/laravel-websockets. For ease, I tested the web socket on client side using laravel-echo with Vue. Everything works well there but I couldn't find any solution to replicate laravel-echo in C#.
Here is the code I am using in Vue.js with laravel-echo:
import Echo from "laravel-echo";
import Pusher from "pusher-js";
window.Pusher = Pusher;
const token = "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'laravel_rdb',
wsHost: '127.0.0.1',
authEndpoint: 'http://localhost/may-app/public/broadcasting/auth',
encrypted: false,
forceTLS: false,
wsPort: 6001,
wssPort: 6001,
disableStats: true,
enabledTransports: ['ws', 'wss'],
auth : {
headers : {
Authorization: "Bearer " + token,
Accept: "application/json",
}
},
})
window.Echo.private('customer-order')
.listen('OrderPlaced', (e) => {
console.log(e)
})
I found SocketIOClient is used to implement web socket functionality in .NET. I tried to use a solution I found here but it didn't work for me. Also, I didn't find any way to set up my authentication URL in this package. I read socket.io documentation for anything related to authentication but I couldn't find any.
How do I implement equivalent functionality in C# .NET as in laravel-echo?
There is probably no client like laravel-echo for .NET. However, you will be able to connect to your sockets using pusher client: pusher/pusher-websocket-dotnet and this is probably the highest level of compatibility you can reach. But you will need to parse your messages and subscribe to the channels by yourself, there will be no sweet wrapping like in laravel-echo =(
I was able to implement a solution using the package mentioned by PunyFlash in the answers. The NuGet package is available here and here is the GitHub repo.
My solution might be useful for someone in the future so, my equivalent code for the laravel-echo code above, in .NET is:
internal class OrderSocket
{
public static async void Connect()
{
try
{
//Setting authentication
var authorizer = new CustomAuthorizer("http://localhost/may-app/public/broadcasting/auth")
{
AuthenticationHeader = new System.Net.Http.Headers.AuthenticationHeaderValue("Authorization", "Bearer " + "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"),
};
//Creating pusher object with authentication
Pusher pusher = new Pusher("laravel_rdb", new PusherOptions
{
Authorizer = authorizer,
Host = "127.0.0.1:6001",
});
//Connecting to web socket
await pusher.ConnectAsync().ConfigureAwait(false);
//Subscribing to channel
Channel channel = await pusher.SubscribeAsync("private-customer-order").ConfigureAwait(false);
if (channel.IsSubscribed)
{
//Binding to an event
channel.Bind("App\\Events\\OrderPlaced", (PusherEvent eventResponse) =>
{
// Deserialize json if server returns json values
Debug.WriteLine(eventResponse.Data);
});
}
}
catch (Exception)
{
Debug.WriteLine("An exception occurred.");
}
}
}
//HttpAuthorizer child class to set default headers
internal class CustomAuthorizer : HttpAuthorizer
{
public CustomAuthorizer(string authEndpoint) : base(authEndpoint) { }
public override void PreAuthorize(HttpClient httpClient)
{
base.PreAuthorize(httpClient);
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
}

Azure SignalR Blazor app not receiving messages

I'm looking at incorporating Azure SignalR functionality into my .net core Blazor web application. To this end i've been following this tutorial - Azure Signalr Serverless. This is working fine - i have a project running the Azure functions app and can start up two browsers and have a chat session. What i'm trying to do is add the ability to receive these message notifications from the Azure signalR hub that's been configured into my Blazor app. I've added the following code in Index.razor.cs that mimics the javascript code in the example client:
public class IndexComponent : ComponentBase
{
private HubConnection _connection;
public string Message;
protected override Task OnInitializedAsync()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:7071/api")
.Build();
_connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Message = $"Got message {message} from user {user}";
this.StateHasChanged();
});
_connection.StartAsync();
return base.OnInitializedAsync();
}
}
The example javascript code btw is:
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${apiBaseUrl}/api`)
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on('newMessage', newMessage);
connection.onclose(() => console.log('disconnected'));
console.log('connecting...');
connection.start()
.then(() => data.ready = true)
.catch(console.error);
So the problem is that my Blazor app never receives any message notifications sent from the javascript chat clients (so the _connection.On handler is never hit). What am i missing in my Blazor code ?
Ok so this is what i needed to do to get it to work in my Blazor app:
_connection.On<object>("newMessage", update =>
{
Console.WriteLine(update);
//Message = update;
});
I needed to subscribe to the 'newMessage' target (since that's the JS is sending on) and also the type that's being posted isn't a string but a JObject type which i would need to deserialize to the correct type.

How can I call a method when my web API is initialized?

I am building a web API that will serve as a connector between a 3rd-party application and mine.
This application will be running on a server and will be receiving POST requests from the 3rd-party application and sending POST requests of its own as a response.
Before it starts sending these requests, my web API needs to make a POST to the 3rd-party service, so it can be registered and received an authorization token, that it will be used on the requests it sends back, kinda similar to an OAuth token, from what I understand.
Since my code is all inside an HttpPost method, it only gets activated when it receives a call, and that part work as expected. When the service is authenticated and is receiving requests, is fine. The problem is when my service or the 3rd-party is restarted or something, the current token is made invalid or lost and a new one needs to be requested again.
What I wish to do is make that the call to register my service and receive the token is sent when the service starts, automatically.
Currently I am doing a manual call to trigger when my service needs to be registered, but that make it necessary for me to be at my computer to do so, and the connection is not make until I call that request.
Here is a sample of my code:
public class Controller : ApiController
{
static string SessionToken = "";
[HttpPost]
[Route("connector/webhook")]
public async Task<HttpStatusCode> Webhook(UpdateContentRequestBody body)
{
var NO_ERROR = 0;
try
{
if (string.IsNullOrEmpty(SessionToken))
{
// This registers my service.
var registerConector = ConectorOSCCApi.RegisterConector();
if (respostaRegistrarConector.ErrorCode != NO_ERROR)
{
throw new Exception();
}
SessionToken = registerConector.SessionToken;
}
ConectorApi.KeepAliveRequest(SessionToken);
RepeatKeepAlive();
ProccessDataAndSendResponseRequest(body);
return HttpStatusCode.OK;
}
catch (Exception e)
{
SessionToken = "";
return HttpStatusCode.InternalServerError;
}
I want the method to register the service to run without the need of a call to "connector/webhook", but the rest of the processing and response to only happens when such a call is received. How can I do that?
EDIT:
My code is inside a ASP.NET Web Application.
I am using .NET Framework 4.5 and hosting my web application on IIS.
This should do it for you :)
public class Controller : ApiController
{
static string _sessionToken = "";
static string SessionToken
{
get
{
if (string.IsNullOrEmpty(_sessionToken))
{
InitToken();
}
return _sessionToken
}
}
void InitToken()
{
if (string.IsNullOrEmpty(_sessionToken))
{
// This registers my service.
var registerConector = ConectorOSCCApi.RegisterConector();
if (respostaRegistrarConector.ErrorCode != NO_ERROR)
{
throw new Exception();
}
_sessionToken = registerConector.SessionToken;
}
}
public Controller() : base()
{
InitToken();
// anything else
}
[HttpPost]
[Route("connector/webhook")]
public async Task<HttpStatusCode> Webhook(UpdateContentRequestBody body)
{
var NO_ERROR = 0;
try
{
ConectorApi.KeepAliveRequest(SessionToken);
RepeatKeepAlive();
ProccessDataAndSendResponseRequest(body);
return HttpStatusCode.OK;
}
catch (Exception e)
{
SessionToken = "";
return HttpStatusCode.InternalServerError;
}
}
}
You don't need to wait for a request to your service to request a token.
Prerequisites : make sure you know what error code you receive from the third party API if your token is no longer correct.
When your API initializes, you will have a method available, ApplicationStart or something else in Startup.cs, depending on version, setup etc. Use that method to request the token from the third party API. Cache the token in the application level cache.
An example of caching can be found here: Caching Data in Web API
When your application receives a request, grab the token from the cache and issue the call to the third part API. If everything works, happy days. If it fails with token issue error code, then re-issue the token request and try again this time with the fresh token. Replace the cached token with the new one.
So basically, keep using a token until it fails, then automatically request a new one and update it. This way you don't need to be there to request the token manually.
You could wrap up this token logic into a service class so you don't have a lot to do in the endpoints.

Can't send message to specific user with SignalR

I can't make works the message sending to one specific user from the code behind. Clients.All works, Clients.AllExcept(userId) works, but not Client.User(userId).
My hub:
public class MessagingHub : Hub
{
public override Task OnConnected()
{
var signalRConnectionId = Context.ConnectionId;
// for testing purpose, I collect the userId from the VS Debug window
System.Diagnostics.Debug.WriteLine("OnConnected --> " + signalRConnectionId);
return base.OnConnected();
}
}
My controller to send message from code behind:
public void PostMessageToUser(string ConnectionId)
{
var mappingHub = GlobalHost.ConnectionManager.GetHubContext<MessagingHub>();
// doesn't works
mappingHub.Clients.User(ConnectionId).onMessageRecorded();
// doesn't works
mappingHub.Clients.Users(new List<string>() { ConnectionId }).onMessageRecorded();
// works
mappingHub.Clients.All.onMessageRecorded();
// works (?!)
mappingHub.Clients.AllExcept(ConnectionId).onMessageRecorded();
}
How my hub is initialized on the JS:
var con, hub;
function StartRealtimeMessaging()
{
con = $.hubConnection();
hub = con.createHubProxy('MessagingHub');
hub.on('onMessageRecorded', function () {
$(".MessageContainer").append("<div>I've received a message!!</div>");
});
con.start();
}
And finally how I send a(n empty) message to the hub:
function TestSendToUser(connectionId)
{
$.ajax({
url: '/Default/PostMessageToUser',
type: "POST",
data: { ConnectionId: connectionId},// contains the user I want to send the message to
});
}
So, it works perfectly with mappingHub.Clients.All.onMessageRecorded(); but not with mappingHub.Clients.User(ConnectionId).onMessageRecorded(); or mappingHub.Clients.Users(new List<string>() { ConnectionId}).onMessageRecorded();.
But interestingly, it works with mappingHub.Clients.AllExcept(ConnectionId).onMessageRecorded(); : All users connected receive the message except the given userid, which means the userid is good, and the user is well identified. So, why Clients.User(ConnectionId) doesn't works?
If you want to send a message to one particular connection and when you want to use the ConnectionId, make sure you use Clients.Client, and not Clients.User
Like this:
public void PostMessageToUser(string connectionId)
{
var mappingHub = GlobalHost.ConnectionManager.GetHubContext<MessagingHub>();
// Like this
mappingHub.Clients.Client(connectionId).onMessageRecorded();
// or this
mappingHub.Clients.Clients(new List<string>() { connectionId }).onMessageRecorded();
}
I had the same problem. I couldn't get .User(ConnectionId) to work.
I have just spent days trying to get SignalR to report progress on a long processing job to only the client who requested the job. That is, it isn't a chat app which most of the examples describe.
Any 'long processing progress reporting' examples I found only have a sim of the job in the hub. I have a controller doing real work and need to send messages from the controller, not the hub.
I used this answer https://stackoverflow.com/a/21222303/3251300. as a workaround for your stated problem but have included all the code snippets I use for the long processing job in case they are useful for anyone who stumbles on this answer.
The workaround has an elegance in that it uses the .Group() feature. By setting each groupID equal to the internal userID, messages can be sent using .Group(userID) without having to separately maintain a list of the userID/connectionID relationships outside SignalR.
There may be a way to maintain the relationships in SignalR without using the .Group() feature but I haven’t found it yet.
Pass the userID to the view using a hidden type which then makes it available to the js.
<input type="hidden" value="#ViewBag.UserID" id="userID" />
Then in the js hub script use the following to send the userID to the hub when the hub connection starts up.
$.connection.hub.start()
.done(function () {
var userID = document.getElementById('userID').value;
$.connection.myHub.server.announce(userID);
})
.fail(function () { alert("Hub failed to start.") });
The hub then has one statement which associates the userID and connectionID to the groupID, which is then the same string as the userID.
public class MyHub : Hub
{
public void Announce(string userID)
{
Groups.Add(Context.ConnectionId, userID);
}
}
To send messages from the controller (Again, not the hub in this case, the message is reporting progress to the client on a long processing request running in the controller) after setting the hub context, use .Group() and the internal userID.
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
string fileMessage = "Some message";
hubContext.Clients.Group(userID).hubMessage(fileMessage);
This is then displayed in the view using the js to place the message in a div
$.connection.myHub.client.hubMessage = function (message) {
$("#hubMessages").html(message);
}
'#hubMessages' refers to this div in the view. Examples use .append which makes the div grow each time you send a message, .HTML replaces whatever is in the div with the new message.
<div id="hubMessages"></div>
Anyone who comes to this answer and is trying to get going on MVC and SignalR, a big shout out to Caleb who has a great series of intro vids for SignalR https://youtu.be/kr8uHeNjOKw Anyone who finds this answer who is new to SignalR I recommend you spend an hour watching these.
I face same problem.
I change from:
Clients.User(connectionId).SendAsync(CallbackDefinition.DirectMessage, directMessageResult);
to:
Clients.Client(connectionId).SendAsync(CallbackDefinition.DirectMessage, directMessageResult);
And it work :D
Thank to: Matthieu Charbonnier

C# WebSockets Client to MVC Server

I have an API controller that creates a web socket connection and can connect and interact with fine when using JavaScript, but I'd also like to be able to connect to it using a C# client. I've tried a few different libraries (Alchemy, WebSocket4Net, and WebSocket-Sharp), and they all seem to be timing out during connection open.
Here's the code for the server side:
class WsController : ApiController
{
public HttpResponseMessage Get(string id)
{
HttpContext.Current.AcceptWebSocketRequest(new WsHandler(id));
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
internal class WsHandler : WebSocketHandler
{
public WsHandler(string id) { /* ... */ }
}
}
If I'm using a JS client, I can use var ws = new WebSocket(url + "?id=someid"); to connect and then send and receive messages. When using a C# client, I get stuck during the handshake and the connect times out. Here's some example code using WebSocket4Net.
var ws = new WebSocket(url);
ws.Opened += new EventHandler(ws_Connect);
ws.Closed += new EventHandler(ws_Disconnect);
ws.Error += new EventHandler<ErrorEventArgs>(ws_Error);
ws.MessageReceived += new EventHandler<MessageReceivedEventArgs>(ws_Receive);
ws.Open(); // Timeout on this line. No errors are thrown to ws_Error.
Is there another way to create a websocket server using MVC's ApiController?

Categories

Resources