tesicg tesicg - 16 days ago 3
ASP.NET (C#) Question

SignalR hub hangs on IIS

We use SignalR library in our ASP.NET web application. The code looks as following:

Server:

[HubName("ticketsCounterHub")]
public class MassivePrintHub : Hub
{
public void PostTicketsCount(long count)
{
Clients.All.Send(count);
}
}

public class HubFactory
{
private HubFactory() {}

public static readonly HubFactory Current = new HubFactory();

public IHubProxy GetMassivePrintHubProxy()
{
var hubConnection = new HubConnection(ConfigUtils.GetRequiredSettingValue("adminPath"));
var hubProxy = hubConnection.CreateHubProxy("ticketsCounterHub");
hubConnection.Start().Wait();
return hubProxy;
}
}


Client (JavaScript):

MassivePrintApp.controller("ListController", function ($scope, Dates) {
var hubManager = (function () {
var massivePrintHub = $.connection.ticketsCounterHub;
$.connection.hub.start();
return { massivePrintHub: massivePrintHub };
} ());

hubManager.massivePrintHub.client.Send = function (ticketsCount) {
$scope.action.Quantity = ticketsCount;
$scope.$digest();
};
});


The key part of code is in MVC controller:

public FileResult PrintAction(int actionId, int count, DateTime actionDate, bool isThermo=false)
{
var ticketsCount = _ticketService.GetTicketsInStatusCount(actionId, actionDate, TicketStatusEnum.ToPrint);

HubFactory.Current.GetMassivePrintHubProxy().Invoke("PostTicketsCount", ticketsCount);

var stream = new MemoryStream();
xmlResponse.Save(stream);
stream.Flush();
stream.Position = 0;
return File(stream,ContentTypeEnum.XML.ToString(),String.Format("массовая {0} мероприятия {1} {2}шт.xml", isThermo?"термопечать":"печать", action.Artist,count));
}


As you can see, we have this line:

HubFactory.Current.GetMassivePrintHubProxy().Invoke("PostTicketsCount", ticketsCount);


And that causes the issue, that is whenever we call it one more instance of hub was added to "Requests" section on IIS.

I understand we already started hub in JavaScript code, but I'm not sure how can I use the existing connection or how to get rid of HubFactory or delete created hub instance.

And I don't understand why hub hangs on IIS.

Answer

Starting from a more simple example will help you a lot I guess. After that you can look into hosting your SignalR server differently (Console App or Windows Service) the basics won't change

(First installed SignalR: NuGet: install-package Microsoft.AspNet.SignalR)

I made a simple web-app example. The project has a Hub class:

using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SRTest
{
    public class MassivePrintHub : Hub
    {
        private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MassivePrintHub>();

        // Can be called from your Javascript code
        public void PostTicketsCount(long count)
        {
            Clients.All.Send(count);
        }

        // Can be called from your c# code
        public static void Static_PostTicketsCount(long count)
        {
            hubContext.Clients.All.Send(count);
        }
    }
}

An Owin startup class:

using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(SRTest.Startup))]
namespace SRTest
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var hubConfiguration = new HubConfiguration();
            hubConfiguration.EnableDetailedErrors = true;
            app.MapSignalR(hubConfiguration);
        }
    }
}

Page (Razor just to be able to call a simulator which calls a c# class to post message from backend):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>TEST PAGE</title>

    <!--Reference the jQuery library. -->
    <script src='Scripts/jquery-1.6.4.js'></script>
    <!--Reference the SignalR library. -->
    <script src='Scripts/jquery.signalR-2.2.0.js'></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="signalr/hubs"></script>

</head>
<body>

    THIS IS A TEST PAGE
    <!-- Call simulator (trigger event every 5 seconds) -->
    @{SRTest.SendFromBackEnd.SimulateSend();}

<script>
    $(function () {
        var printHub = $.connection.massivePrintHub;

        // when send event happens
        printHub.client.send = function (count) {
            console.log("Send " + count + " tickets");
        };

        $.connection.hub.start().done(function () {
            console.log("Connected");
        });

        $.connection.hub.logging = true;
    });
</script>

</body>
</html>

And I added a dummy class which triggers the event through hubcontext every 5 seconds.

using System.Threading;
using System.Web;

namespace SRTest
{
    public class SendFromBackEnd
    {
        public static void SimulateSend()
        {
            new Thread(() =>
            {
                Thread.CurrentThread.IsBackground = true;
                while (true)
                {
                    MassivePrintHub.Static_PostTicketsCount(2);
                    Thread.Sleep(5000);
                }
            }).Start();

        }
    }
}

I added some loggings to the SignalR, add some debug points, it will help you understand the basics, then it will be much easier to build what you are planning to build.

EDIT

About the hanging request: As long as you have a client connected to your SignalR server with SSE or AJAX Long-Polling, you will have an ongoing request, which never finishes. (In case of AJAX Long-polling, it finishes for very short times and comes back). In the apps where I use only Javascript clients, I only see the request if a page is open where I am listening to events. If no page or static page open then no request. In the apps where I am using .NET clients, as long as the two apps are running, and both Sartup classes executed, the request will always be there, even if no page open. (Since the .NET client is still listening to events.)

For more info: http://hanselminutes.com/291/damian-edwards-explains-the-realtime-web-for-aspnet-with-signalr