SignalR (v.1.1.1) Call from Server - c#

I am using latest SignalR (v:1.1.1), and trying to simple call the Hub method periodically, every 3 seconds. I have seen many questions here and have duplicated the way, but GetHubContext method doesn't seem to be returning the correct instance of the class, so I can't call the methods of that class. You can duplicate the case with the following steps:
MyHub.cs:
public class MyHub : Hub
{
public void SendMessage(string message)
{
Clients.All.triggerMessage(message);
}
}
Global.asax:
Task.Factory.StartNew(() =>
{
while (true)
{
var myHub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
myHub.Clients.All.SendMessage("Hello World");
System.Threading.Thread.Sleep(3000);
}
})
.ContinueWith(t => { throw new Exception("The task threw an exception", t.Exception); }, TaskContinuationOptions.OnlyOnFaulted);
I think this is as simple as it gets. I think this is correct way of doing it, but the debugger never hits SendMessage method. Does anyone know am I missing something very obvious? I am just trying to schedule a call to the SignalR client from the server for every 3 seconds.

On your Global.asax file, when you call 'myHub.Clients.All.SendMessage("Hello World")' it sends a message to the client, it does not call the SendMessage method in your class MyHub.
Please read SignalR Documentation to see some samples

I ended changing the way the hub was created:
MyHostHub.cs
private readonly MyHost _host;
public MyHostHub(){ _host = new MyHost(); }
MyHost:
private readonly static Lazy<IHubConnectionContext> _clients = new Lazy<IHubConnectionContext>(() => GlobalHost.ConnectionManager.GetHubContext<MyHostHub>().Clients);
private IHubConnectionContext Clients
{
get { return _clients.Value; }
}
public void SendMessage(string message)
{
Clients.All.triggerMessage(message);
}
My Global.asax:
Task.Factory.StartNew(() =>
{
while (true)
{
var myHost = ObjectFactory.GetInstance<MyHost>();
myHost.SendMessage();
Thread.Sleep(3000);
}
})
.ContinueWith(t => { throw new Exception("The task threw an exception", t.Exception); }, TaskContinuationOptions.OnlyOnFaulted);
This seems to be working just fine. Basically I moved out the code from the Hub class to another class that I can call in Global.asax but my hub has a reference for host.

Related

UseExceptionHandler won't catch tasks exceptions?

I've created a simple webapi .net core 3.1 app.
I want to catch all unhandled exceptions.So I put this code according to the docs :
app.UseExceptionHandler(c => c.Run(async context =>
{
var exception = context.Features
.Get<IExceptionHandlerPathFeature>()
.Error;
var response = new { error = exception.Message };
log.LogDebug(exception.Message);
}));
This is my action:
[HttpGet]
public IActionResult Get()
{
throw new Exception("this is a test");
}
When this code runs, I do see that UseExceptionHandler is working.
But when my code in the action is :
[HttpGet]
public IActionResult Get()
{
Task.Run(async () =>
{
await Task.Delay(4000);
throw new Exception("this is a test");
});
return Ok();
}
Then UseExceptionHandler is NOT working.
However - the following code does catch the task's exception :
AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
{
Debug.WriteLine(eventArgs.Exception.ToString());
};
Question:
Why does the task exception isn't recognized by UseExceptionHandler?
How can I catch ALL types of exceptions? Should I rely only on AppDomain.CurrentDomain.FirstChanceException?
nb , I did disabled app.UseDeveloperExceptionPage();
To answer your questions.
Why does the task exception isn't recognized by UseExceptionHandler?
As already suggested in the comments, you cannot use UseExceptionHandler to catch exceptions initiated inside non-awaited tasks. UseExceptionHandler wraps your request in ASP.NET Core middleware. Once the action returns OK to the client, the middleware is no longer able to catch any exceptions happening in tasks started from within the action.
How can I catch ALL types of exceptions? Should I rely only on AppDomain.CurrentDomain.FirstChanceException?
You can catch exceptions globally and log them this way if you'd like. But I wouldn't recommend you to do it this way. The only reason you need to implement this event, is that you are starting tasks/threads inside your web requests. You have no way of knowing if these tasks are kept running (application restart, recycle, etc.). If you are looking to launch background tasks with ASP.NET Core, you should use Worker Services which is the intended way of doing this:
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyWorker>();
});
public class MyWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// Do work
}
catch (Exception e)
{
// Log it?
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
The cause of this particular symptom is that Get is starting a fire-and-forget task that the server knows nothing about. The request will complete before the task even has a chance to execute, so the UseExceptionHandler middleware will never see any exceptions. This is no longer a fire-and-forget task.
The real problem though, is executing a long running task in the background. The built-in way to do this is using a Background Service. The docs show how to create timed and queued background service, that act as job queues.
It's equally easy, if not easier, to publish messages with the desired data from, eg a controller to the background service using, eg Channels. No need to create our own queue, when the BCL already has an asynchronous one.
The service could look like this :
public class MyService: BackgroundService
{
private readonly ChannelReader<T> _reader;
public QueuedBspService(MessageQueue<T> queue)
{
_reader = queue.Reader;
}
protected internal async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
await foreach (var msg in _reader.ReadAllAsync(stoppingToken))
{
try
{
//Process the message here
}
catch (Exception exc)
{
//Handle message-specific errors
}
}
}
catch (Exception exc)
{
//Handle cancellations and other critical errors
}
}
}
The MessageQueue<T> wraps the Channel, making it easier to inject it to both the BackgroundService and any publishers like eg, a Controller action:
public class MessageQueue<T>
{
private readonly Channel<T> _channel;
public ChannelReader<T> Reader => _channel;
public ChannelWriter<T> Writer => _channel;
public MessageChannel()
{
_channel = Channel.CreateBounded<T>(1);
}
}
I adjusted this code from a service that only allows a single operation at a time. That's a quick&dirty way of preventing controllers from making requests that can't be handled.
On the contolle side, this action will post a request to the queue if possible, and return a Busy response otherwise :
public class MyController
{
private readonly ChannelWriter<T> _writer;
public MyController(MessaggeQueue<T> queue)
{
_writer = queue.Writer;
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<ActionResult> Post(....)
{
var jobName="SomeJob";
var id=Guid.NewGuid();
var jobMsg=CreateMessage(id,...);
try
{
if (_writer.TryWrite(msg))
{
return CreatedAtAction("GetItem","Jobs",new {id});
}
else
{
return Problem(statusCode:(int) HttpStatusCode.ServiceUnavailable,detail:"Jobs in progress",title:"Busy");
}
}
catch (Exception exc)
{
_logger.LogError(exc,"Queueing {job} failed",jobName);
throw;
}
}
}
The Post action first checks if it can even post a job message. If it succeeds, it returns a 201 - Created response with a URL that could be checked eg to check the status of the jobs. return Created() could be used instead, but once you create a long running job, you also want to check its status.
If the channel is at capacity, the core returns 503 with an explanation

Invoking SignalR Hub not working for Asp.Net Core Web API

I'm a newb to SignalR. I'm trying to set up a Asp.Net Core WebAPI so that other clients can connect to it using SignalR and get real-time data.
My Hub class is:
public class TimeHub : Hub
{
public async Task UpdateTime(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
}
I have a relay class as follows:
public class TimeRelay : ITimeRelay
{
private readonly IHubContext<TimeHub> _timeHubContext;
public TimeRelay(IHubContext<TimeHub> context)
{
_timeHubContext = context;
Task.Factory.StartNew(async () =>
{
while (true)
{
await context.Clients.All.SendAsync("UpdateTime", DateTime.Now.ToShortDateString());
Thread.Sleep(2000);
}
});
}
}
Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
app.UseSignalR((x) =>
{
x.MapHub<TimeHub>("/timeHub");
});
app.UseMvc();
}
The client is a console application and the code is:
class Program
{
static Action<string> OnReceivedAction = OnReceived;
static void Main(string[] args)
{
Connect();
Console.ReadLine();
}
private static async void Connect()
{
var hubConnectionBuilder = new HubConnectionBuilder();
var hubConnection = hubConnectionBuilder.WithUrl("http://localhost:60211/timeHub").Build();
await hubConnection.StartAsync();
var on = hubConnection.On("ReceiveMessage", OnReceivedAction);
Console.ReadLine();
on.Dispose();
await hubConnection.StopAsync();
}
static void OnReceived(string message)
{
System.Console.WriteLine($"{message}");
}
}
I tried debugging the application. The client got connected to the TimeHub succesfully. The no of connections in Clients.All changed from 0 to 1, when the client got connected. But, when await context.Clients.All.SendAsync("UpdateTime", DateTime.Now.ToShortDateString()); is executed, the UpdateTime function in TimeHub is not getting executed and the client is not getting any message.
I tried using "UpdateTime", "SendMessage", and "ReceiveMessage" as method in Clients.All.SendAsync in TimeRelay class. Nothing worked. Could someone point out my mistake in this.
For Clients, it will be null if there is no client connecting to server. For starting Asp.Net Core SignalR and Console App at the same time, the Clients may be null since Index may be called before Console App connects the signalR server.
Try steps below:
Change TimeHub
public class TimeHub: Hub
{
public async Task UpdateTime(string message)
{
if (Clients != null)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
}
}
Register TimeHub
services.AddSingleton<TimeHub>();
Controller
public class HomeController : Controller
{
private readonly TimeHub _timeHub;
public HomeController(TimeHub timeHub)
{
_timeHub = timeHub;
}
public IActionResult Index()
{
Task.Factory.StartNew(async () =>
{
while (true)
{
try
{
await _timeHub.UpdateTime(DateTime.Now.ToShortDateString());
Thread.Sleep(2000);
}
catch (Exception ex)
{
}
}
});
return View();
}
I got it to work and thought I will answer it here. Thanks #TaoZhou for the tip.
My mistake was sending "UpdateTime" from server and waiting on "ReceiveMessage" at the client.
Ideally the code should look like the following:
SignalR Server:
await context.Clients.All.SendAsync("UpdateTime", DateTime.Now.ToShortDateString());
SignalR Client:
var on = hubConnection.On("UpdateTime", OnReceivedAction);
In this case any message send from the server would be received at the client instantly.
Please refer the code provided in the question for more info.

C# SignalR Exception - Connection started reconnecting before invocation result was received

I am developing 2 applications, the first being a C# console application and the other an Asp.net web application. I am using SignalR to connect the two.
This is my C# console application (Client)
public class RoboHub
{
public static IHubProxy _hub;
public RoboHub()
{
StartHubConnection();
_hub.On("GetGoals", () => GetGoals());
_hub.On("PrintMessageRobot", x => PrintMessageRobot(x));
Thread thread = new Thread(MonitorHubStatus);
thread.Start();
}
public void GetGoals()
{
//TODO: Does stuff
}
public void PrintMessageRobot(string msg)
{
Console.WriteLine(msg);
}
public void StartHubConnection()
{
Console.WriteLine("Robo Hub Starting");
string url = #"http://localhost:46124/";
var connection = new HubConnection(url);
_hub = connection.CreateHubProxy("WebHub");
connection.Start().Wait();
Console.WriteLine("Robo Hub Running");
}
public void MonitorHubStatus()
{
while (true)
{
Thread.Sleep(1000);
_hub.Invoke("Ping", "ping").Wait();
Console.WriteLine("WebHub Pinged : " + DateTime.Now);
}
}
}
When the console application runs, it creates an instance of the RoboHub class. Which in turn starts a connection to the SignalR hub and on a separate thread starts the method MonitorHubStatus which is something I implemented to check if the C# console application client is still actively connected to the hub.
This is my Web hub (within the Asp.net Web application)
public class WebHub : Hub
{
/// <summary>
/// This method should be called by the Web Clients.
/// This method should call the method on the robot clients.
/// </summary>
public void GetGoalsHub()
{
lock (UserHandler.Connections)
{
if (UserHandler.Connections.Any(connection => connection.Contains("Robot")))
{
Clients.All.GetGoals();
}
}
//TODO add Error method to call on the client
}
/// <summary>
/// Override Methods
/// </summary>
/// <returns></returns>
public override Task OnConnected()
{
lock (UserHandler.Connections)
{
//Find out who is connecting based on the User-Agent header
var query = (from r in Context.Headers
where r.Key == "User-Agent"
select r).SingleOrDefault().ToString();
if (query.Contains("SignalR.Client.NET45"))
{
UserHandler.Connections.Add("Robot : " + Context.ConnectionId);
}
else
{
UserHandler.Connections.Add("Web Application : " + Context.ConnectionId);
GetGoalsHub();
}
}
Clients.All.UpdateConnections(UserHandler.Connections);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
lock (UserHandler.Connections)
{
for (int i = 0; i < UserHandler.Connections.Count; i++)
{
if (UserHandler.Connections[i].Contains(Context.ConnectionId))
{
UserHandler.Connections.Remove(UserHandler.Connections[i]);
}
}
}
Clients.All.UpdateConnections(UserHandler.Connections);
return base.OnDisconnected(stopCalled);
}
public void Ping(string msg)
{
Clients.All.PrintMessageRobot("Pong : " + DateTime.Now);
}
}
public static class UserHandler
{
public static List<string> Connections = new List<string>();
}
Currently the 2 applications seem to work for a time, until after a while this error randomly appears:
Connection started reconnecting before invocation result was received.
Further more should the web hub call any other method on the C# console client such as the GetGoals method. The 'Ping Pong' method freezes and after time a similar exception is thrown. throughout this the web client continues to function perfectly and the web client can communicate back and forth with the hub server.
Can anyone suggest what the issue could be?
Edit: Further investigation leads me to believe it to be something to do with threading, however it is difficult to find the source of the issues.
The problem is with the invoke call:
_hub.Invoke("MethodName", "Parameters").Wait();
Here I am telling it to wait for a response however I did not program any reply mechanism in the web server.
This error was fix by switching to:
_hub.Invoke("MethodName", "Parameters");
Now it follows a 'fire and forget' methodology and it now no longer gets the error. Should anyone else get this error be sure to check whether you need a response or not.
You will get the same error message if the data being sent to server side is a 'Non-serializable' (e.g.) List of business objects which don't have [Serializable] attribute
I got this same exception when the payload was to big. I fixed it by changing the following line, in the Startup declaration for signlr.net. It removes the size limit
GlobalHost.Configuration.MaxIncomingWebSocketMessageSize = null;

ASP.NET MVC 4.5 WebSocket server loses connection after a few messages have been transferred

I've just started to play around with WebSockets and ASP.NET and have run into a weird issue. I'm building a very primitive ASP.NET 4.5 WebAPI application that is supposed to function as an echo-server like so:
using Microsoft.Web.WebSockets;
// ...
namespace MyControllers
{
internal class EchoHandler : WebSocketHandler
{
public override void OnClose()
{
System.Diagnostics.Debug.Write("Close");
}
public override void OnError()
{
System.Diagnostics.Debug.Write("Error: " + this.Error.ToString());
}
public override void OnOpen()
{
System.Diagnostics.Debug.Write("Open");
}
public override void OnMessage(string message)
{
System.Diagnostics.Debug.Write("Message: " + message);
this.Send("Echo: " + message);
}
}
public class EchoController : ApiController
{
public HttpResponseMessage Get()
{
if (HttpContext.Current.IsWebSocketRequest)
{
HttpContext.Current.AcceptWebSocketRequest(new EchoHandler());
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
}
}
I'm connecting to this service using a Windows Store Application written in C#. The relevant code looks like this:
class WebsocketTest
{
private MessageWebSocket webSocket;
private DataWriter messageWriter;
private async Task Connect()
{
var server = new Uri("ws://127.0.0.1:81/");
webSocket = new MessageWebSocket();
webSocket.Control.MessageType = SocketMessageType.Utf8;
webSocket.MessageReceived += messageWebSocket_MessageReceived;
webSocket.Closed += messageWebSocket_Closed;
await webSocket.ConnectAsync(server);
messageWebSocket = webSocket;
messageWriter = new DataWriter(webSocket.OutputStream);
}
private async Task Send(string message)
{
try
{
messageWriter.WriteString(message);
await messageWriter.StoreAsync();
}
catch (Exception ex)
{
var error = WebSocketError.GetStatus(ex.GetBaseException().HResult);
}
}
}
This works well for a while, but after an arbitrary number of messages have been sent back and forth, OnError() is invoked on the server and I get the following exception: "The I/O operation has been aborted because of either a thread exit or an application request" (It's the "this.Send(...)" that seems to be causing it). If I keep sending stuff on the client, I get a "ConnectionAborted" error when calling "dataWriter.StoreAsync()".
The error occurs every time, but it takes a varying number of messages before it does. Using longer messages seems to speed up the process.
For testing, I also tried using plain AspNetWebSockets instead of a WebSocketHandler but with the same outcome.
Any ideas?
Thanks a ton in advance,
Kai
Its a bug (reported by me):
https://connect.microsoft.com/VisualStudio/feedbackdetail/view/976851/server-websocket-closed-abruptly-the-i-o-operation-has-been-aborted-because-of-either-a-thread-exit-or-an-application-request
I have been trying to find a workaround for quite some time without being successful. I'm using the HttpListener but the symptom is the same. Now I have changed implementation to a third party library and the problem seems to have been resolved.

Unable to connect RabbitBus to RabbitMQ

I am creating a RabbitBus.Bus with the RabbitBus.BusBuilder in my RabbitAdapter class.
public class RabbitAdapter
{
private Bus _bus;
public RabbitAdapter()
{
// The exchange and queue values are the same as what I see in RabbitMQ in browser
_bus = new BusBuilder()
.Configure(ctx => ctx.Consume<StatusUpdate>()
.WithExchange("exchange")
.WithQueue("Log"))
.Build();
}
public void Init()
{
// The [url] and [port] values are the same as what I see in browser
_bus.Connect("amqp://guest:guest#[url]:[port]/#/", TimeSpan.FromSeconds(10));
_bus.Subscribe<StatusUpdate>(OnHandle);
}
private void OnHandle(IMessageContext<StatusUpdate> statusUpdateContext)
{
Console.WriteLine(statusUpdateContext.Id);
}
public void Start()
{
}
}
I know that I'm probably just missing something here. The _connectionFactory is not null in the Bus, but the _connection is. It seems to timeout, I've even tried making the timeout one minute.
What you miss is although the console listens on 15672, the actual server listens on 5672 port.

Categories

Resources