So I have a C# WebApi Signalr webservice with a Angular 2 Signalr Client.
The WebService is supposed to update the client when a new message comes in from a post method that other services may call.
[HttpPost]
public void NotificationMessage([FromBody]ServiceInformation serviceInformation)
{
Messages.notificationMessage = serviceInformation;
notificationMessage.BroadCastNotificationMessage();
}
Another service posts to this method and this method sets a static variable in the Messages class and then calls the NotificationMessage Hub.
public class NotificationMessage : Hub<IClient>
{
public void BroadCastNotificationMessage()
{
Clients.All.serviceInfoBroadCast(JsonConvert.SerializeObject(Messages.notificationMessage));
}
}
The notification hub then calls Clients.All to broadcast the new message to all clients.
Angular Site:
Service:
constructor() {
this.connectionEstablished = new EventEmitter<Boolean>();
this.messageReceived = new EventEmitter<ServiceInformationObject>();
this.connectionExists = false;
this.connection = $.hubConnection(CONFIGURATION.baseUrls.server);
this.proxy = this.connection.createHubProxy(this.proxyName);
this.registerOnServerEvents();
this.startConnection();
}
private startConnection(): void {
this.connection.start({ jsonp: true, waitForPageLoad: false}).done((data: any) => {
console.log('Now connected ' + data.transport.name + ', connection ID= ' + data.id);
this.connectionEstablished.emit(true);
this.connectionExists = true;
//this.proxy.invoke('BroadCastNotificationMessage');
}).fail((error: any) => {
console.log('Could not connect ' + error);
this.connectionEstablished.emit(false);
});
}
private registerOnServerEvents(): void {
this.proxy.on('serviceInfoBroadCast', (data: string) => {
console.log('received in SignalRService: ' + JSON.stringify(data));
let jsonData = JSON.parse(data);
let newData = new ServiceInformationObject(jsonData.ServiceName, jsonData.Message, jsonData.State, jsonData.MachineName, Date.now());
this.messageReceived.emit(newData);
});
}
I setup the hub connection followed by the hub proxy. I call the proxy.on method connecting to the Clients.All dynamic method from the web service. Then I start the connection.
Component to display data on view:
private subscribeToEvents(): void {
this._signalRService.connectionEstablished.subscribe(() => {
this.canSendMessage = true;
});
this._signalRService.messageReceived.subscribe((message: ServiceInformationObject) => {
this._ngZone.run(() => {
this.testArray.push(message);
});
});
}
The issue:
If I leave the invoke call in the startConnection method, it will pull data down from the webservice, but it will never update ever again.
If I don't use the invoke nothing happens.
I'm not sure why the webservice is not pushing the information when Clients.All is called. I know the post method is being called from my own interal logging and I know the object is not empty.
Any ideas on why the webservice is not pushing the information? Or why the client is not displaying it?
Related
I have a ASPNet.Core WebApi, with signalR. I have angular app, that consumes the webAPI, and I want to replace it with a Blazor Webassembly app. I have a problem with signalR in the Blazor app.
I create a hubconnection, set it up, and when the server sends data, the Hubconnection.On method is not invoked. Here's my code:
protected override async Task OnInitializedAsync()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:45299/hubs/accounthub", cfg =>
{
cfg.SkipNegotiation = true;
cfg.AccessTokenProvider = () => Task.FromResult(token);
cfg.Transports = HttpTransportType.WebSockets;
})
.Build();
_hubConnection.On<IEnumerable<AccountResponse>>("accountschanged", (accounts) =>
{
foreach(var account in accounts)
{
Console.WriteLine(account.Name);
}
});
await _hubConnection.StartAsync();
}
In the network tab, I see that the connection is ok, I receive new data, but the method in hubconnection.On doesn't get fired. I double checked the method name, and it is the same. In the angular app it works fine and as data gets send from the server, I don't there's any problem with the server code.
I use Fluxor for state management, and I fire an action in the 'On' method, I just replaced is with a single Console.WriteLine for simplicity.
Edit: Added server code, and the message received
Here's the server code, 'AccountsChanged' is called when an account is changed:
public class AccountHub : Hub, IAccountHub
{
private readonly IHubContext<AccountHub> _accHub;
private readonly IAggregateMapper _mapper;
public AccountHub(IHubContext<AccountHub> accHub, IAggregateMapper mapper)
{
_accHub = accHub;
_mapper = mapper;
}
public async Task AccountsChanged(Guid userId, IEnumerable<Account> accounts)
{
var mapped = _mapper.MapAll<Account, AccountResponse>(accounts);
await _accHub.Clients.User(userId.ToString()).SendAsync("accountschanged", mapped);
}
}
And here's the message I receive (I make a request from Postman), copied from the network tab (I removed additional properties of accounts to keep it simple):
{
"type":1,
"target":"accountschanged",
"arguments":[
[
{
"id":1,
"name":"bank account 1"
},
{
"id":2,
"name":"wallet 1"
}
]
]
}
I finally found the problem. It was about serializing the received json message. I had to add .AddJsonProtocol(), and set it up, here is the final code:
_hubConnection = new HubConnectionBuilder()
.WithUrl("http://localhost:59225/hubs/accounthub", cfg =>
{
cfg.SkipNegotiation = true;
cfg.Transports = HttpTransportType.WebSockets;
cfg.AccessTokenProvider = () => Task.FromResult(token);
})
.AddJsonProtocol(cfg =>
{
var jsonOptions = new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
jsonOptions.Converters.Add(new JsonStringEnumConverter());
cfg.PayloadSerializerOptions = jsonOptions;
})
.Build();
I find it strange that I didn't get any error message btw.
I am developing a .Net core project with Angular on Frontend and c# on backend. I am new to web development and i am looking for ideas or little help on achieving a task.
I am connecting my Angular frontend to an External server using .net web server. I am able to post data to External server successfully using Http services. After i receive response from external server the results are queued in my controller. Before i send response to client i want to enable a download button on Client, only if i receive response from the External server and the results are queued in my controller successfully. If there is no response from External server I do not want to enable download button.
Appcomponent.html
<button mat-button id="getdoc" (click) = "getdoc()" [disabled] = "disabled" >Download</button>
Appcomponent.ts
getdoc() {
this.download.downloadDoc()
.subscribe(res => {
this.datadownload = new Blob([res], { type: 'application/txt' });
// { this.disabled = false };
saveAs(this.datadownload);
console.log(this.datadownload);
}, error => {
console.log('error while downloading', error)
})
}
Appservice.ts
export class DownloadService {
constructor(private http: HttpClient) { }
downloadDoc(): Observable<any> {
return this.http.get('URL', {/* headers, */ reportProgress: true, responseType : "blob"});
}
}
Controller
namespace ang.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class DoController : Controller
{
private void OnProvideData(DataAndHeader dat)
{
CommHeader header = (CommHeader)dat.Header;
switch (header.Type)
{
case CommHelpers.CommType.ServerToClientReceived:
break;
case CommHelpers.CommType.ServerToClientFinished:
string ClientID = header.ClientID;
ConcurrentQueue<DataAndHeader> queueResultsForClient = null;
if (!dicResults.TryGetValue(ClientID, out queueResultsForClient))
{
queueResultsForClient = new ConcurrentQueue<DataAndHeader>();
if (!dicResults.TryAdd(ClientID, queueResultsForClient))
{
}
}
queueResultsForClient.Enqueue(dat);
break;
}
}
[HttpGet]
// [Route("download")]
public byte[] download(string ClientID)
{
// some logic
return new byte[0];
}
}
}
I want to wait till i get the Response from the External Server and loaded into my Controller after that i want to enable the button. After the button is enabled and clicked by Client he should be able to download file.
The "getdoc()" works only when i click download button. but initially the download button is in disable state. I want to programatically enable it from my controller not from my Client
Thanks in Advance.
Try the following code:
getdoc() {
this.download.downloadDoc()
.subscribe(res => {
this.datadownload = new Blob([res], { type: 'application/txt' });
saveAs(this.datadownload);
console.log(this.datadownload);
this.disabled = false; // This is the change
}, error => {
this.disabled = true;
console.log('error while downloading', error)
})
}
My code in SignalR hub:
public class AlertHub : Hub
{
public static readonly System.Timers.Timer _Timer = new System.Timers.Timer();
static AlertHub()
{
_Timer.Interval = 60000;
_Timer.Elapsed += TimerElapsed;
_Timer.Start();
}
static void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//Random rnd = new Random();
//int i = rnd.Next(0,2);
Alert alert = new Alert();
i = alert.CheckForNewAlerts(EmpId);
var hub = GlobalHost.ConnectionManager.GetHubContext("AlertHub");
hub.Clients.All.Alert(i);
}
}
Somehow I need to pass EmpId parameter. How to accomplish this?
Some more client details:
On my aspx page I have the following code:
<script type="text/javascript">
$(function () {
var alert = $.connection.alertHub;
alert.client.Alert = function (msg) {
if (msg == 1) {
$("#HyperLink1").show();
$("#HyperLink2").hide();
}
else {
$("#HyperLink1").hide();
$("#HyperLink2").show();
}
//$("#logUl").append("<li>" + msg + "</li>");
};
$.connection.hub.start();
});
</script>
On ASPX page, my EmpID is in the session object and I need to somehow use it in the SignalR hub.
In addition to the accepted answer I have used this to pass multiple query strings from client to hub:
In Client:
Dictionary<string, string> queryString = new Dictionary<string, string>();
queryString.Add("key1", "value1");
queryString.Add("key2", "value2");
hubConnection = new HubConnection("http://localhost:49493/signalr/hubs", queryString);
---Rest of the code--------
In Hub class:
public override Task OnConnected()
{
var value1 = Convert.ToString(Context.QueryString["key1"]);
var value2 = Convert.ToString(Context.QueryString["key2"]);
return base.OnConnected();
}
I am using the “Microsoft.AspNet.SignalR.Client” library of version “2.3.0.0” in a windows form application using c#.
You can keep track of connected users (and any associated metadata) by connection (check here and here for examples) and on your timer tick check your stored local data for whatever you need.
By itself, signalR won't pass you anything. The client has to pass things along.
If your client has the employee ID, have it send it to the signalr hub on connect. You can add a handler know when the client connects in your aspx page javascript, and then send it to the hub. The hub can then keep track of ConnectionId, EmployeeID in a dictionary and you can use that to access back on the particular client or do whatever you want.
Check the links I posted, they show how to do this.
In ASP.NET Core 6 (not sure about earlier versions?) you can include a parameter in the hub path.
In Program.cs:
app.MapHub<AlertHub>("/myhub/{EmpId}");
Then in AlertHub class can reference the EmpId parameter using Context.GetHttpContext().GetRouteValue("EmpId"), eg adding the connection to a group:
public override async Task OnConnectedAsync()
{
var id = Context?.GetHttpContext()?.GetRouteValue("EmpId") as string;
await Groups.AddToGroupAsync(Context?.ConnectionId, id);
await base.OnConnectedAsync();
}
I was using Alchemy websockets for both my client and server but ran into a problem with corrupted/dropped messsages. So I'm trying out another server side implementation. I implemented the server using Fleck, and when I send messages using javascript, the server receives all the messages, solving my previous problem.
However, I need to be able to send messages to the websocket server from a C# client also. Since Fleck does not have a client side implementation in C#, I thought I'd stick with Alchemy. I left the client-side code unchanged so I thought it should just connect to the server as before, however, no messages are being received (though they are being sent according to the debugger).
Here is my server side implementation (Fleck):
private void OnStartWebSocketServer()
{
var server = new WebSocketServer("ws://localhost:11005");
server.Start(socket =>
{
socket.OnOpen = () => Console.WriteLine("Open!");
socket.OnClose = () => Console.WriteLine("Close!");
socket.OnMessage = message => OnReceive(message);
});
}
private static void OnReceive(String message)
{
UpdateUserLocation(message);
}
Here is my client side implementation (Alchemy):
class WSclient
{
WebSocketClient aClient;
public WSclient(String host, String port)
{
aClient = new WebSocketClient("ws://" + host + ":" + 11005 + "/chat")
{
OnReceive = OnReceive,
OnSend = OnSend,
OnConnect = OnConnected,
OnConnected = OnConnect,
OnDisconnect = OnDisconnect
};
aClient.Connect();
}
...
public void Send(String data)
{
aClient.Send(data);
}
I thought it might have something to do with the fact that the Alchemy client requires a channel at the end of the connection string '/chat'. However leaving it blank, or just the '/' gives an error.
I have implemented SignalR for my Windows Azure project. I have two clients - Javascript/HTML client in my web role and a console application in my project. And Web role is my SignalR server. When i put the web role and the console application as the start up projects, the messages i send from the HTML client are sent to the console application. But when i put the Cloud project and the console application as the start up projects, the messages from the HTML client are not being sent to the console application. Its really weird, i dont know what could be the difference between the two which is causing the problem.
And if i put a background thread in my web role which will send messages to connected clients periodically, it works on both occasions, i mean the console app and the HTML client are receiving messages irrespective of the start up projects.
Please let me know if you have any idea what the problem is
My Hub:
public class BroadcastHub : Hub
{
public void Send(PersistedAudioRecord record)
{
// Call the BroadcastAudio method to update clients.
Clients.All.BroadcastAudio(record);
}
}
My HTML/Javascript client:
<script type="text/javascript">
$(function () {
// Declare a proxy to reference the hub.
var broadcast = $.connection.broadcastHub;
// Create a function that the hub can call to broadcast messages.
broadcast.client.broadcastAudio = function (record) {
// Html encode user name, channel and title.
var encodedName = $('<div />').text(record.Username).html();
var encodedChannel = $('<div />').text(record.Channel).html();
var encodedTitle = $('<div />').text(record.Title).html();
// Add the broadcast to the page.
$('#broadcasts').append('<li><strong>' + encodedName
+ '</strong>: ' + encodedChannel + '</strong>: ' + encodedTitle + '</li>');
};
// Get the user name.
$('#displayname').val(prompt('Enter your name:', ''));
// Get the Channel name to which you want to broadcast.
$('#channelname').val(prompt('Enter Channel:', ''));
// Set initial focus to message input box.
$('#title').focus();
// Start the connection.
$.connection.hub.start().done(function () {
$('#sendbroadcast').click(function () {
// Call the Send method on the hub.
var broadcastMessage = {}
broadcastMessage.Username = $('#displayname').val();
broadcastMessage.Channel = $('#channelname').val();
broadcastMessage.Title = $('#title').val();
broadcast.server.send(broadcastMessage);
// Clear text box and reset focus for next broadcast.
$('#title').val('').focus();
});
});
});
</script>
My Console app client:
class Program
{
static void Main(string[] args)
{
HubConnection connection = new HubConnection("http://localhost:35540/");
IHubProxy proxy = connection.CreateHubProxy("BroadcastHub");
proxy.On<AudioRecord>("BroadcastAudio", BroadcastAudio);
connection.Start().Wait();
Console.ReadLine();
}
static void BroadcastAudio(AudioRecord record)
{
Console.WriteLine("Broadcast: {0} {1} {2}", record.Username, record.Channel, record.Title);
}
}
Background Thread:
public class BackgroundThread
{
private static Random _random = new Random();
public static void Start()
{
ThreadPool.QueueUserWorkItem(_ =>
{
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<BroadcastHub>();
while (true)
{
PersistedAudioRecord record = new PersistedAudioRecord();
record.Channel = _random.Next(10).ToString();
record.Username = new string('a', Convert.ToInt32(record.Channel));
record.Title = new string('b', Convert.ToInt32(record.Channel));
try
{
hubContext.Clients.All.BroadcastAudio(record);
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError("SignalR error thrown: {0}", ex);
}
Thread.Sleep(TimeSpan.FromSeconds(2));
}
});
}
}
I tried this scenario with my application and I was able to send messages from a webrole to a console application. Is it possible to zip your project and send it to see if this reproes...