I have Web Api which serves to CRUD Posts from Web App, Android App and Desktop.
I want to add SignalR to the Web Api, every time when Action Create in Controller gets called I want to notify all users that Post is created.
Problem is, I can't find any implementation in only Web Api, all implementations are in Web App with Web Api or something like that. I read all MSDN documentation about it. I'm strugling for 3-4 days now.
I managed to get to the point where I implemented SignalR, and my server isn't created any signalr/hubs file that I need to call from Web App script. It's only created when I run app locally - if I publish it on Azure that file isn't created.
Anyone have concrete steps for Implementation only in Web Api?
I tried this blog, it has Web Api stuff but have js in project and local html added. It's not standalone REST api.
This is not about not creating signalr/hubs file. It's about creating standalone Web Api with SignalR.
I have startup:
public void Configuration(IAppBuilder app) {
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
app.MapSignalR("/signalr", new Microsoft.AspNet.SignalR.HubConfiguration());
}
My Hub:
public class ServiceStatusHub : Hub {
private static IHubContext hubContext =
GlobalHost.ConnectionManager.GetHubContext<ServiceStatusHub>();
public static void SetStatus(string message) {
hubContext.Clients.All.acknowledgeMessage(message);
}
}
And in my Api Controler I call:
ServiceStatusHub.SetStatus("Status changed!");
I made console application to test Api, added Signal R client and class:
class SignalRMasterClient {
public string Url { get; set; }
public HubConnection Connection { get; set; }
public IHubProxy Hub { get; set; }
public SignalRMasterClient(string url) {
Url = url;
Connection = new HubConnection(url, useDefaultUrl: false);
Hub = Connection.CreateHubProxy("ServiceStatusHub");
Connection.Start().Wait();
Hub.On<string>("acknowledgeMessage", (message) =>
{
Console.WriteLine("Message received: " + message);
/// TODO: Check status of the LDAP
/// and update status to Web API.
});
}
public void SayHello(string message) {
Hub.Invoke("hello", message);
Console.WriteLine("hello method is called!");
}
public void Stop() {
Connection.Stop();
}
}
program.cs:
var client = new SignalRMasterClient("myUrl.com/signalr");
// Send message to server.
client.SayHello("Message from client to Server!");
I getting 500 Internal Server Error.
How can I test is my Web Api signalR works for sure?
I see some problems:
1) You do not need the hubContext field in your hub. You inherit from Hub. This class contains allready a "Clients" property .
public class ServiceStatusHub : Hub {
public static void SetStatus(string message) {
Clients.All.acknowledgeMessage(message);
}
}
2) Log errors at starting of server.
public SignalRMasterClient(string url) {
Url = url;
Connection = new HubConnection(url, useDefaultUrl: false);
Hub = Connection.CreateHubProxy("ServiceStatusHub");
Connection.Start().ContinueWith(task => { if (task.IsFaulted) {
Console.WriteLine("There was an error opening the connection:{0}",
task.Exception.GetBaseException());
} else {
Console.WriteLine("Connected");
}
});
Hub.On<string>("acknowledgeMessage", (message) =>
{
Console.WriteLine("Message received: " + message);
/// TODO: Check status of the LDAP
/// and update status to Web API.
}).Wait();
}
3) Check Client url. You need no signalr there in your path at creation of your client.
just:
var client = new SignalRMasterClient("myUrl.com/");
Here you will find a running sample which does all you need:
SignalR Console app example
Related
I'm trying to make a connection between my ASP.NET Core 3.0 Blazor (server-side) application and the Azure SignalR Service. I'll end up injecting my SignalR client (service) in to a few Blazor components so they'll update my UI/DOM in realtime.
My issue is that I'm receiving the following message when I call my .StartAsync() method on the hub connection:
Response status code does not indicate success: 404 (Not Found).
BootstrapSignalRClient.cs
This file loads my configuration for the SignalR Service including the URL, connection string, key, method name, and hub name. These settings are captured in the static class SignalRServiceConfiguration and used later.
public static class BootstrapSignalRClient
{
public static IServiceCollection AddSignalRServiceClient(this IServiceCollection services, IConfiguration configuration)
{
SignalRServiceConfiguration signalRServiceConfiguration = new SignalRServiceConfiguration();
configuration.Bind(nameof(SignalRServiceConfiguration), signalRServiceConfiguration);
services.AddSingleton(signalRServiceConfiguration);
services.AddSingleton<ISignalRClient, SignalRClient>();
return services;
}
}
SignalRServiceConfiguration.cs
public class SignalRServiceConfiguration
{
public string ConnectionString { get; set; }
public string Url { get; set; }
public string MethodName { get; set; }
public string Key { get; set; }
public string HubName { get; set; }
}
SignalRClient.cs
public class SignalRClient : ISignalRClient
{
public delegate void ReceiveMessage(string message);
public event ReceiveMessage ReceiveMessageEvent;
private HubConnection hubConnection;
public SignalRClient(SignalRServiceConfiguration signalRConfig)
{
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConfig.Url + signalRConfig.HubName)
.Build();
}
public async Task<string> StartListening(string id)
{
// Register listener for a specific id
hubConnection.On<string>(id, (message) =>
{
if (ReceiveMessageEvent != null)
{
ReceiveMessageEvent.Invoke(message);
}
});
try
{
// Start the SignalR Service connection
await hubConnection.StartAsync(); //<---I get an exception here
return hubConnection.State.ToString();
}
catch (Exception ex)
{
return ex.Message;
}
}
private void ReceiveMessage(string message)
{
response = JsonConvert.DeserializeObject<dynamic>(message);
}
}
I have experience using SignalR with .NET Core where you add it so the Startup.cs file using .AddSignalR().AddAzureSignalR() and map a hub in the app config and doing it this way requires certain 'configuration' parameters to be established (i.e. connection string).
Given my situation, where does HubConnectionBuilder get the connection string or a key to authenticate to the SignalR Service?
Is it possible the 404 message is a result of the missing key/connection string?
Okay so it turns out the documentation is lacking a key piece of information here. If you're using the .NET SignalR Client connecting to the Azure SignalR Service, you need to request a JWT token and present it when creating the hub connection.
If you need to authenticate on behalf of a user you can use this example.
Otherwise, you can set up a "/negotiate" endpoint using a web API such as an Azure Function to retrive a JWT token and client URL for you; this is what I ended up doing for my use case. Information about creating an Azure Function to get your JWT token and URL can be found here.
I created a class to hold these two values as such:
SignalRConnectionInfo.cs
public class SignalRConnectionInfo
{
[JsonProperty(PropertyName = "url")]
public string Url { get; set; }
[JsonProperty(PropertyName = "accessToken")]
public string AccessToken { get; set; }
}
I also created a method inside my SignalRService to handle the interaction with the web API's "/negotiate" endpoint in Azure, the instantiation of the hub connection, and the use of an event + delegate for receiving messages as follows:
SignalRClient.cs
public async Task InitializeAsync()
{
SignalRConnectionInfo signalRConnectionInfo;
signalRConnectionInfo = await functionsClient.GetDataAsync<SignalRConnectionInfo>(FunctionsClientConstants.SignalR);
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConnectionInfo.Url, options =>
{
options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
})
.Build();
}
The functionsClient is simply a strongly typed HttpClient pre-configured with a base URL and the FunctionsClientConstants.SignalR is a static class with the "/negotiate" path which is appended to the base URL.
Once I had this all set up I called the await hubConnection.StartAsync(); and it "connected"!
After all this I set up a static ReceiveMessage event and a delegate as follows (in the same SignalRClient.cs):
public delegate void ReceiveMessage(string message);
public static event ReceiveMessage ReceiveMessageEvent;
Lastly, I implemented the ReceiveMessage delegate:
await signalRClient.InitializeAsync(); //<---called from another method
private async Task StartReceiving()
{
SignalRStatus = await signalRClient.ReceiveReservationResponse(Response.ReservationId);
logger.LogInformation($"SignalR Status is: {SignalRStatus}");
// Register event handler for static delegate
SignalRClient.ReceiveMessageEvent += signalRClient_receiveMessageEvent;
}
private async void signalRClient_receiveMessageEvent(string response)
{
logger.LogInformation($"Received SignalR mesage: {response}");
signalRReservationResponse = JsonConvert.DeserializeObject<SignalRReservationResponse>(response);
await InvokeAsync(StateHasChanged); //<---used by Blazor (server-side)
}
I've provided documentation updates back to the Azure SignalR Service team and sure hope this helps someone else!
Update: the sample with the serverless sample is deprecated for the management SDK (sample). The management SDK uses a negotiation server.
I am trying to send a message from a controller to client(s) group using SignalR.
When an event occurs in one of my controllers, I need to push a message using a signalr hub to be displayed like an alert on my clients screens.
i know a lot of questions have been asked here, and I've read and tried a lot of them. Since I am new to SignalR some of them even already helped me to put things in place.
At the moment everything seems in place. Clients can connect to the hub and join groups and controller can call methods from the hub. But the client never receive the message and I can't figure out why. I suspect that hub's method called by controller don't "see" the clients but I can't understand what's wrong.
Hub's code :
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
[HubName("myHub")]
public class MyHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
public void Notify(string groupName, string message)
{
Clients.Group(groupName).displayNotification(message);
}
public static void Static_Notify(string groupName, string message)
{
var toto = UserHandler.ConnectedIds.Count();
hubContext.Clients.Group(groupName).displayNotification(message);
hubContext.Clients.All.displayNotification(message);//for testing purpose
}
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public override Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool StopCalled)
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnected(StopCalled);
}
}
Here is the call from my controller :
//(For simplification and readability I define here variables actually obtained by treating some data )
//I already checked that problem did not come from missing data here
string groupName = "theGroupName";
string message = "My beautifull message.";
//prepare signalR call
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
//Following commented lines are different attempts made based on exemples and answers I found here and on others sites.
//The uncommented one is the one in use at the moment and is also based on an answer (from SO i think)
//context.Clients.Group(_userEmail).displayNotification(message);
//context.Clients.Group(_userEmail).Notify(_userEmail,message);
MyHub.Static_Notify(_userEmail, message);
And here is the client-side code :
$(document).ready(function () {
var userGroup = 'theGroupName';
$.connection.hub.url = 'http://localhost/SignalRHost/signalr';
var theHub = $.connection.myHub;
console.log($.connection)
console.log($.connection.myHub)
theHub.client.displayNotification = function (message) {
console.log('display message');
alert(message);
};
$.connection.hub.start()
.done(function () {
theHub.server.joinGroup(userGroup);
console.log("myHub hub started : " + $.connection.hub.id)
console.log(theHub)
})
.fail(function () {
console.log('myHub hub failed to connect')
});
});
Please help me understand what logic I failed to understand or what error I am missing.
EDIT :
To answer Alisson's comment:
The Startup.cs I forgot to show
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR();
});
}
Important stuff I forgot to mention too :
The Controller, the hub, and the client are 3 different projects (because of the global application architecture I had to separate hub logic)
All are on my localhost IIS but on different ports
I've set breakpoints on "OnConnected" and "onDiconnected" events and client connects and disconnects normally
You just need one app to act as server, in your case it should be the SIgnalRHost project. Your controller project should be a client of the server, therefore it just needs this package:
Install-Package Microsoft.AspNet.SignalR.Client
Your controller project doesn't actually need to reference the project containing the hub class. In your controller, you'll use C# SignalR client to connect to the server (as you would do in javascript client), join the group and invoke the hub method:
var hubConnection = new HubConnection("http://localhost/SignalRHost/signalr");
IHubProxy myHub = hubConnection.CreateHubProxy("MyHub");
await hubConnection.Start();
myHub.Invoke("JoinGroup", "theGroupName");
myHub.Invoke("Notify", "theGroupName", "My beautifull message.");
...finally, you don't need that Static_Notify at all.
Since you are passing the group name as a parameter on the Notify
method, you don't really need to join the group from your controller.
You'd need only if you were trying to send the message to the same
group the controller was connected (then you wouldn't need to pass the
group name as parameter anymore).
SignalR C# Client Reference.
I have created a class that inherits the Signalr Hub class and it runs on startup. When a connection is made, there are some custom headers sent from the client that I use to generate a user object. I want to store these in memory on the server so that I can retrieve the list and display them in a UI. Someone can then use this UI to see the user info and perform interactions with this connection. I have setup a hub class in an ASP MVC project and i am using a console app for the client. I can connect fine and the server can communicate back, but the property that I use in the hub class to keep track of the connected users is reset to null every time a request is made to the hub class.
public class JobRunHandler : Hub
{
private List<JobRunClient> RunningJobs { get; set; }
public JobRunHandler()
{
if(this.RunningJobs == null) this.RunningJobs = new List<JobRunClient>();
}
public override Task OnConnected()
{
JobRunClient runclient = new JobRunClient()
{
ConnectionID = Context.ConnectionId,
Someotherstuff = this.GetHeaderInt(Context.Headers["Someotherstuff"])
};
this.RunningJobs.Add(runclient);
return base.OnConnected();
}
public override Task OnReconnected()
{
var existingClient = this.GetConnectingClient();
if (existingClient == null)
{
JobRunClient runclient = new JobRunClient()
{
ConnectionID = Context.ConnectionId,
Someotherstuff = this.GetHeaderInt(Context.Headers["Someotherstuff"])
};
this.RunningJobs.Add(runclient);
}
return base.OnReconnected();
}
public override Task OnDisconnected(bool stopCalled)
{
this.RemoveClient(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public void TestMethod()
{
Clients.All.ping();
var client = this.GetConnectingClient();
}
}
I have put break points in every method so i know when it runs. The client never disconnects or triggers reconnect, so there is no issue with the connection being broken. The client connects and the OnConnected() method triggers and the value is added to this.RunningJobs. The client then calls the TestMethod() which works, but when i check this.RunningJobs it is empty.
When Clients.All.ping(); runs it does actually send a ping back to the client. So the connection is made successfully, the server maintains the connection and i can send a ping back to the client when a separate method is called, but for some reason the property is being reset and I dont know why. I can use redis for this if I have to, but I have seen others use this strategy and its not been an issue.
Here is the client code I have created to test this (the console app)
var hubConnection = new HubConnection("http://localhost:2497/");
hubConnection.Credentials = CredentialCache.DefaultNetworkCredentials;
IHubProxy myHubProxy = hubConnection.CreateHubProxy("JobRunHandler");
myHubProxy.On("ping", () => Console.Write("Recieved ping \n"));
hubConnection.Headers.Add("Someotherstuff", "1");
hubConnection.Start().Wait();
while(true)
{
myHubProxy.Invoke("BroadcastCompletion").ContinueWith(task =>
{
if (task.IsFaulted)
{
Console.WriteLine("!!! There was an error opening the connection:{0} \n", task.Exception.GetBaseException());
}
}).Wait();
Console.WriteLine("Broadcast sent to the server.\n");
Thread.Sleep(4000);
}
The hub is transient. SignalR creates a hub instance each time a hub method is invoked so you cannot store any state in an instance property between the calls.
I changed the property being used for this to a ConcurrentDictionary and this seems to be doing the trick. It allows me to store the client connections across all connections. I used the following code for this.
private static readonly ConcurrentDictionary<string, JobRunClient> RunningJobs = new ConcurrentDictionary<string, JobRunClient>();
I new to SignalR.I am developing a Project using signalR
Service side i am using WCF and SignalR.And for Client Side,i am using ASP.NET MVC 4.
I followed the steps in below website:-
Getting Started With SignalR and MVC 5
for my service side,i am installed the package called
Install-Package Microsoft.AspNet.SignalR
And also i am created my owin startup class and SignalR Hub Class in WCF Service.
Owin StartupClass:-
[assembly: OwinStartup(typeof(WCF3.Startup))]
namespace WCF3
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
SignalR HubClass:-
public class ConvHub : Hub
{
public void send(string user)
{
Clients.All.broadcastMesage(user);
}
}
and My WCF Service:-
public class Service1 : IService1
{
public string GetValue(string username)
{
string name = "username ="+username;
Console.WriteLine("u=" + name);
return name;
}
}
My Service side Configuration is Completed.
But i don't know,which package i need to install for ASP.NET MVC ,while Considering MVC as separate Client.
Could Anyone Tell me the package name ,need to install in MVC Client?
you need to install Microsoft.AspNet.SignalR.Client to your wcf service.
configure your start up in wcf service
var connection = new HubConnection("{enter your hub url}");
var myHub = connection.CreateHubProxy("ConvHub");
myHub.Invoke("send", "your username").ContinueWith(task => {
if (task.IsFaulted)
{
Console.WriteLine("There was an error calling send: {0}", task.Exception.GetBaseException());
}
else
{
Console.WriteLine("Send Complete.");
}
});
I have a SignalR hub, which i successfully call from JQuery.
public class UpdateNotification : Hub
{
public void SendUpdate(DateTime timeStamp, string user, string entity, string message)
{
Clients.All.UpdateClients(timeStamp.ToString("yyyy-MM-dd HH:mm:ss"), user, entity, message);
}
}
update message sent successfully from JS like so
var updateNotification = $.connection.updateNotification;
$.connection.hub.start({ transport: ['webSockets', 'serverSentEvents', 'longPolling'] }).done(function () { });
updateNotification.server.sendUpdate(timeStamp, user, entity, message);
and received successfully like so
updateNotification.client.UpdateClients = function (timeStamp, user, entity, message) {
I can't work out how to call sendUpdate from within my controller.
From your controller, in the same application as the hub (rather than from elsewhere, as a .NET client), you make hub calls like this:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<UpdateNotification>();
hubContext.Clients.All.yourclientfunction(yourargs);
See Broadcasting over a Hub from outside of a Hub near the foot of https://github.com/SignalR/SignalR/wiki/Hubs
To call your custom method is a little different. Probably best to create a static method, which you can then use to call the hubContext, as the OP has here: Server to client messages not going through with SignalR in ASP.NET MVC 4
Here's an example from SignalR quickstart
You need to create a hub proxy.
public class Program
{
public static void Main(string[] args)
{
// Connect to the service
var hubConnection = new HubConnection("http://localhost/mysite");
// Create a proxy to the chat service
var chat = hubConnection.CreateHubProxy("chat");
// Print the message when it comes in
chat.On("addMessage", message => Console.WriteLine(message));
// Start the connection
hubConnection.Start().Wait();
string line = null;
while((line = Console.ReadLine()) != null)
{
// Send a message to the server
chat.Invoke("Send", line).Wait();
}
}
}