I'd like to handle timeout callback on a hub call to garantee that server received my "message", how can I do that?
I looked at signalR wiki, googled about it but found nothing!
That was the way I thought the interface would be:
var hubcon = new HubConnection("myurl", useDefaultUrl: false);
IHubProxy chatHub = hubcon.CreateHubProxy("chatHub");
chatHub.On("timeout", data =>
{
//do something
});
hubcon.Start().Wait(1000);
chatHub.Invoke("EnviarMensagem", new { nome = nome, mensagem = mensagem }).Wait();
hubcon.Stop();
Since you are calling Wait anyway, you can set a timeout and look at the return value to see if the server finished processing your invocation:
if (!chatHub.Invoke("EnviarMensagem", ...).Wait(10000))
{
// The server did not respond to the invocation within 10 seconds
}
http://msdn.microsoft.com/en-us/library/dd270644.aspx
Related
I have an infinitely running process that pushes events from a server to subscribed SignalR clients. There may be long periods where no events take place on the server.
Currently, the process all works fine -- for a short period of time-- but eventually, the client stops responding to events pushed by the server. I can see the events taking place on the server-side, but the client becomes unaware of the event. I am assuming this symptom means some timeout period has been reached and the client has unsubscribed from the Hub.
I added some code to reconnect if the connection was dropped, and that has helped, but the client still eventually stops seeing new events. I know there are many different timeout values that can be adjusted, but it's all pretty confusing to me and not sure if I should even be tinkering with them.
try
{
myHubConnection = new HubConnectionBuilder()
.WithUrl(hubURL, HttpTransportType.WebSockets)
.AddMessagePackProtocol()
.AddJsonProtocol(options =>
{
options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
})
.Build();
// Client method that can be called by server
myHubConnection.On<string>("ReceiveInfo", json =>
{
// Action performed when method called by server
pub.ShowInfo(json);
});
try
{
// connect to Hub
await myHubConnection.StartAsync();
msg = "Connected to Hub";
}
catch (Exception ex)
{
appLog.WriteError(ex.Message);
msg = "Error: " + ex.Message;
}
// Reconnect lost Hub connection
myHubConnection.Closed += async (error) =>
{
try
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await myHubConnection.StartAsync();
msg = "Reconnected to Hub";
appLog.WriteWarning(msg);
}
catch (Exception ex)
{
appLog.WriteError(ex.Message);
msg = "Error: " + ex.Message;
}
};
This all works as expected for a while, then stops without errors. Is there something I can do to (1) ensure the client NEVER unsubscribes, and (2) if the connection is lost (network outage for example) ensures the client resubscribes to the events. This client must NEVER timeout or give up trying to reconnect if required.
Below is the code I wrote for SignalR implementation based on ASP.Net documentation and I use manual proxy creation method. I Could see only negotiate happening and received a Connection id.
I can't see OnConnected method in my hub gets executed when I start connection. According to the note section in the document I have attached event handler before I call start method
SignalR Hub
public class MyTestHub: Hub
{
private static Dictionary<int, List<string>> userConnections
= new Dictionary<int, List<string>>();
public override Task OnConnected()
{
RegisterUserConnectionInMap();
return base.OnConnected();
}
}
Startup.cs
app.Map(
"/signalr",
map =>
{
var hubConfiguration = new HubConfiguration { EnableDetailedErrors = true};
map.RunSignalR(hubConfiguration);
});
Javascript Client Code
var connection = $.hubConnection();
var contosoChatHubProxy = connection.createHubProxy('MyTestHub');
contosoChatHubProxy.on('addContosoChatMessageToPage', function(userName:any, message:any) {
console.log(userName + ' ' + message);
});
connection.start()
.done(function(){ console.log('Now connected, connection ID=' + connection.id); })
.fail(function(){ console.log('Could not connect'); });
Note section in documentation
Normally you register event handlers before calling the start method
to establish the connection. If you want to register some event
handlers after establishing the connection, you can do that, but you
must register at least one of your event handler(s) before calling the
start method. One reason for this is that there can be many Hubs in an
application, but you wouldn't want to trigger the OnConnected event on
every Hub if you are only going to use to one of them. When the
connection is established, the presence of a client method on a Hub's
proxy is what tells SignalR to trigger the OnConnected event. If you
don't register any event handlers before calling the start method, you
will be able to invoke methods on the Hub, but the Hub's OnConnected
method won't be called and no client methods will be invoked from the
server.
I could not figure out what I miss for past two days.
UPDATE:
Even I tried with auto generated proxy class by including <script src="~/SignalR/hubs" with the following client code. Still OnConnected Not fired
var contosoChatHubProxy = $.connection.myTestHub;
contosoChatHubProxy.client.addContosoChatMessageToPage = function (name, message) {
console.log(userName + ' ' + message);
};
$.connection.hub.start()
.done(function(){ console.log('Now connected, connection ID=' + $.connection.hub.id); })
.fail(function(){ console.log('Could not Connect!'); });
Console Log after connectton
I have ended with the below solution. Hope it will help some one.
declare var $: any;
#Injectable()
export class CityChangeNotifier {
constructor(private appService: AppService, private router: Router) {
this.connection = $.hubConnection();
this.CityChangeHub = this.connection.createHubProxy('CityChangeNotificationHub');
this.CityChangeHub
.on('CityUpdatedByServer', (newLocation:any, connectionId:string) => this.onCityUpdatedByServer(newLocation, connectionId));
this.connection.transportConnectTimeout = 10000;
this.startConnection();
}
private startConnection(): void {
let that = this;
this.connection.start()
.done((connection: any) => { that.connectionId = connection.id; })
.fail(() => { });
}
}
I'm using Long Polling with Atmosphere JS for session timeout, so the server tells me when the user has been logged out.
The issue is that once subscribed, Atmosphere JS keeps sending a get request every 60 seconds, which restarts the user session and never logs them out.
I've read the documentation and searched around, but can't see any way to stop this happening. Here is my code:
var socket = atmosphere;
var subSocket;
// subscribe
function subscribe() {
var request = {
url : "/web-service/notifier",
transport: 'long-polling'
};
request.onMessage = function (response) {
var jsonStringArray = response.responseBody.split('|');
// go through each notification and convert from string to object
$.each(jsonStringArray, function(index, elem){
if (elem != ""){
var parsedObject = JSON.parse(elem);
// if notification states user is logged out, log them out
if (parsedObject.action === 'LOGGED_OUT'){
// DO LOGOUT STUFF
}
}
});
};
subSocket = socket.subscribe(request);
}
Thanks for any help.
That's how long-polling works, e.g the connection will be closed/resumed by the server after 60 seconds (you have set that value server side...check your code)
-- Jeanfrancois
I have setup a SignalR hub which has the following method:
public void SomeFunction(int SomeID)
{
try
{
Thread.Sleep(600000);
Clients.Caller.sendComplete("Complete");
}
catch (Exception ex)
{
// Exception Handling
}
finally
{
// Some Actions
}
m_Logger.Trace("*****Trying To Exit*****");
}
The issue I am having is that SignalR initiates and defaults to Server Sent Events and then hangs. Even though the function/method exits minutes later (10 minutes) the method is initiated again ( > 3 minutes) even when the sendComplete and hub.stop() methods are initiated/called on the client prior. Should the user stay on the page the initial "/send?" request stays open indefinitely. Any assistance is greatly appreciated.
To avoid blocking the method for so long, you could use a Taskand call the client method asynchronously.
public void SomeFunction(Int32 id)
{
var connectionId = this.Context.ConnectionId;
Task.Delay(600000).ContinueWith(t =>
{
var message = String.Format("The operation has completed. The ID was: {0}.", id);
var context = GlobalHost.ConnectionManager.GetHubContext<SomeHub>();
context.Clients.Client(connectionId).SendComplete(message);
});
}
Hubs are created when request arrives and destroyed after response is sent down the wire, so in the continuation task, you need to create a new context for yourself to be able to work with a client by their connection identifier, since the original hub instance will no longer be around to provide you with the Clients method.
Also note that you can leverage the nicer syntax that uses async and await keywords for describing asynchronous program flow. See examples at The ASP.NET Site's SignalR Hubs API Guide.
I'm using Web API to stream large files to clients, but I'd like to log if the download was successful or not. That is, if the server sent the entire content of the file.
Is there some way to get a a callback or event when the HttpResponseMessage completes sending data?
Perhaps something like this:
var stream = GetMyStream();
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
// This doesn't exist, but it illustrates what I'm trying to do.
response.OnComplete(context =>
{
if (context.Success)
Log.Info("File downloaded successfully.");
else
Log.Warn("File download was terminated by client.");
});
EDIT: I've now tested this using a real connection (via fiddler).
I inherited StreamContent and added my own OnComplete action which checks for an exception:
public class StreamContentWithCompletion : StreamContent
{
public StreamContentWithCompletion(Stream stream) : base (stream) { }
public StreamContentWithCompletion(Stream stream, Action<Exception> onComplete) : base(stream)
{
this.OnComplete = onComplete;
}
public Action<Exception> OnComplete { get; set; }
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var t = base.SerializeToStreamAsync(stream, context);
t.ContinueWith(x =>
{
if (this.OnComplete != null)
{
// The task will be in a faulted state if something went wrong.
// I observed the following exception when I aborted the fiddler session:
// 'System.Web.HttpException (0x800704CD): The remote host closed the connection.'
if (x.IsFaulted)
this.OnComplete(x.Exception.GetBaseException());
else
this.OnComplete(null);
}
}, TaskContinuationOptions.ExecuteSynchronously);
return t;
}
}
Then I use it like so:
var stream = GetMyStream();
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContentWithCompletion(stream, ex =>
{
if (ex == null)
Log.Info("File downloaded successfully.");
else
Log.Warn("File download was terminated by client.");
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return response;
I am not sure if there is direct signaling that all is ok, but you can use a trick to find out that the connection is exist just before you end it up, and right after you fully send the file.
For example the Response.IsClientConnected is return true if the client is still connected, so you can check something like:
// send the file, make a flush
Response.Flush();
// and now the file is fully sended check if the client is still connected
if(Response.IsClientConnected)
{
// log that all looks ok until the last byte.
}
else
{
// the client is not connected, so maybe have lost some data
}
// and now close the connection.
Response.End();
if the server sent the entire content of the file
Actually there is nothing to do :)
This might sound very simplistic but you will know if an exception is raised - if you care about server delivering and not client cancelling halfway. IsClientConnected is based on ASP.NET HttpResponse not the WebApi.