Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

Self-Hosting SignalR in a Windows Service


A couple of months ago I wrote about a self-hosted SignalR application that I've been working on as part of a larger project. This particular application runs as a service and hosts a SignalR Hub that interacts with a message queue and pushes messages into an application dashboard for live information of the queue's status. Basically the queue service application asynchronously notifies the SignalR hub when messages are processed and the hub pushes these messages out for display into a Web interface to any connected SignalR clients. It's a wonderfully interactive tool that replaces an old WinForms based front end interface with a Web interface that is now accessible from anwhere, but it's a little different SignalR implementation in that the main broadcast agent aren't browser clients, but rather the Windows Service backend. The backend broadcasts messages from a self-hosted SignalR server.

As I mentioned in that last post, being able to host SignalR inside of external non-Web applications opens up all sorts of opportunities and the process of self-hosting - while not as easy as hosting in IIS - is pretty straight forward. In this post I'll describe the steps to set up SignalR for self-hosting and using it inside of a Windows Service.

Self-Hosting SignalR

The process of self-hosting SignalR is surprisingly simple. SignalR relies on the new OWIN architecture that bypasses ASP.NET for a more lightweight hosting environment. There's no dependencies on System.Web and so the hosting process tends to be pretty lean.

Creating a OWin Startup class

The first step is to create a startup class that is called when OWIN initializes. The purpose of this class is to allow you to configure the OWIN runtime, hook in middleware components (think of it like HttpModules) etc. If you're a consumer of a high level tool like SignalR, the OWIN configuration class simply serves as the entry point to hook up the SignalR configuration. In my case I'm using hubs and so all I have here is a HubConfiguration:

public class SignalRStartup
{
    public static IAppBuilder App = null;

    public void Configuration(IAppBuilder app)
    {
        var hubConfiguration = new HubConfiguration { 
            EnableCrossDomain = true,
            EnableDetailedErrors = true
        };
                                                                                   
        app.MapHubs(hubConfiguration);                        
    }        
}

SignalR provides the HubConfiguration class and IAppBuilder extension method called MapHubs that's used for the hub routing. MapHubs uses Reflection to find all the Hub classes in your app and auto-registers them for you. If you're using Connections then the MapConnect<T> class is used to register each connection class individually.

If you're using SignalR 2.0 (currently in RC) then the configuration looks a little different:

public void Configuration(IAppBuilder app)
{
    app.Map("/signalr", map =>
        {
            map.UseCors(CorsOptions.AllowAll);

            var hubConfiguration = new HubConfiguration
                {
                    EnableDetailedErrors = true,
                    EnableJSONP = true
                };

            map.RunSignalR(hubConfiguration);
        });
} 
Note the explicit CORS configuration, which enabled cross domain calls for XHR requests, has been migrated to the OWIN middleware rather than being directly integrated in SignalR. You'll need the Microsoft.OWIN.Cors NuGet package for this functionalty to become available. This is pretty much required on every self-hosted server accessed from the browser since self-host always implies a different domain or at least a different port which most browsers (except IE) also interpret as cross-domain.

And that's really all you need to do to configure SignalR.

Starting up the OWIN Runtime

Next we need to kickstart OWIN to use the Startup class created above. This is done by calling the WebApp.Start<T> factory method passing the startup class as a generic parameter:

SignalR = WebApp.Start<SignalRStartup>("http://*:8080/");

Start<T> passes in the startup class as  generic parameter and the hosting URI. Here I'm using the root site as the base on port 8080. If you're hosting under SSL, you'd use https://*:8080/.

The method returns an instance of the Web app that you can hold onto. The result is a plain IDisposable interface and when it goes out of scope so does the SignalR service. In order to keep the app alive, it's important to capture the instance and park it somewhere for the lifetime of your application. 

In my service application I create the SignalR instance on the service's Start() method and attach it to a SignalR property I created on the service. The service sticks around for the lifetime of the application and so this works great.

Running in a Windows Service

Creating a Windows Service in .NET is pretty easy - you simply create a class that inherits from System.ServiceProcess.ServiceBase and then override the OnStart() and OnStop() and Dispose() methods at a minimum.

Here's an example of my implementation of ServiceBase including the SignalR loading and unloading:

public class MPQueueService : ServiceBase { MPWorkflowQueueController Controller { get; set; } IDisposable SignalR { get; set; } public void Start() { Controller = new MPWorkflowQueueController(App.AdminConfiguration.ConnectionString); var config = QueueMessageManagerConfiguration.Current; Controller.QueueName = config.QueueName; Controller.WaitInterval = config.WaitInterval; Controller.ThreadCount = config.ControllerThreads; SignalR = WebApp.Start<SignalRStartup>(App.AdminConfiguration.MonitorHostUrl); // Spin up the queue
Controller.StartProcessingAsync(); LogManager.Current.LogInfo(String.Format("QueueManager Controller Started with {0} threads.", Controller.ThreadCount)); // Allow access to a global instance of this controller and service // So we can access it from the stateless SignalR hub Globals.Controller = Controller; Globals.WindowsService = this; } public new void Stop() { LogManager.Current.LogInfo("QueueManager Controller Stopped."); Controller.StopProcessing(); Controller.Dispose(); SignalR.Dispose(); Thread.Sleep(1500); } /// <summary> /// Set things in motion so your service can do its work. /// </summary> protected override void OnStart(string[] args) { Start(); } /// <summary> /// Stop this service. /// </summary> protected override void OnStop() { Stop(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (SignalR != null) { SignalR.Dispose(); SignalR = null; } } }

There's not a lot to the service implementation. The Start() method starts up the Queue Manager that does the real work of the application, as well as SignalR which is used in the processing of Queue Requests and sends messages out through the SignalR hub as requests are processed.

Notice the use of Globals.Controller and Globals.WindowsService in the Start() method. SignalR Hubs are completely stateless and they have no context to the application they are running inside of, so in order to pass the necessary state logic and perform tasks like getting information out of the queue or managing the actual service interface, any of these objects that the Hub wants access to have to be available somewhere globally.

public static class Globals
{
    public static MPWorkflowQueueController Controller;
    public static MPQueueService WindowsService;
}

By using a global class with static properties to hold these values they become accessible to the SignalR Hub which can then act on them. So inside of a hub class I can do things like Globals.Controller.Pause() to pause the queue manager's queue processing. Anything with persistent state you need to access from within a Hub has to be exposed in a similar fashion.

Bootstrapping the Windows Service

Finally you also need to also bootstrap the Windows service so it can start and respond to Windows ServiceManager requests in your main program startup (program.cs).

[STAThread] static void Main(string[] args) { string arg0 = string.Empty; if (args.Length > 0) arg0 = (args[0] ?? string.Empty).ToLower(); if (arg0 == "-service" ) { RunService(); return; } if (arg0 == "-fakeservice") { FakeRunService(); return; }

} static void RunService() { var ServicesToRun = new ServiceBase[] { new MPQueueService() }; LogManager.Current.LogInfo("Queue Service started as a Windows Service."); ServiceBase.Run(ServicesToRun); }

static void FakeRunService() { var service = new MPQueueService(); service.Start(); LogManager.Current.LogInfo("Queue Service started as FakeService for debugging."); // never ends but waits Console.ReadLine(); }

Once installed a Windows Service calls the service EXE with a  -service command line switch to start the service the first time it runs. At that point ServiceBase.Run is called on our custom service instance and now the service is running. While running the Windows Service Manager can then call into OnStart(),OnStop() etc. as these commands are applied against the service manager. After a OnStop() operation the service is shut down, which shuts down the EXE.

Note that I also add support for a -fakeservice command line switch. I use this switch for debugging, so that I can run the application for testing under debug mode using the same Service interface. FakeService simply instantiates the service class and explicitly calls the Start() method which simulates the OnStart() operation from the Windows Service Manager. In short this allows me to debug my service by simply starting a regular debug process in Visual Studio, rather than using Attach Process and attaching to a live Windows Service. Much easier and highly recommended while you're developing the service.

Windows Service Registration

Another thing I like to do with my services is provide the ability to have them register themselves. My startup program also corresponds to -InstallService and -UninstallService flags which allow self-registration of the service. .NET doesn't include a native interface for doing this however, with some API calls to the Service Manager API's it's short work to accomplish this. I'm not going to post the code here, but I have a self-contained C# code file that provides this functionality:

With this class in place, you can now easily do something like this in the startup program when checking for command line arguments:

else if (arg0 == "-installservice" || arg0 == "-i")
{
    WindowsServiceManager SM = new WindowsServiceManager();
    if (!SM.InstallService(Environment.CurrentDirectory + "\\MPQueueService.exe -service",
            "MPQueueService", "MP Queue Manager Service"))
        MessageBox.Show("Service install failed.");

    return;
}
else if (arg0 == "-uninstallservice" || arg0 == "-u")
{
    WindowsServiceManager SM = new WindowsServiceManager();
    if (!SM.UnInstallService("MPQueueService"))
        MessageBox.Show("Service failed to uninstall.");

    return;
}

So now we have the service in place - let's look a little closer at the SignalR specific details.

SSL Configuration

Once you deploy your service you'll most likely need to run it under SSL as well. Although not required, if any of your client applications run SSL, the service also needs to run SSL in order to avoid browser mixed content warnings. Unfortunately configuration of an SSL certificate is a little more work than just installing a certificate in IIS - you need to run a command line utility with some magic values to make this work.

If you need to do this, I have a follow up post that discusses SSL installation and creation in more detail.

Hub Implementation

The key piece of the SignalR specific implementation of course is the SignalR hub. The SignalR Hub is just a plain hub with any of the SignalR logic you need to perform. If you recall typical hub methods are called from the client and then typically use the Clients.All.clientMethodToCall to broadcast messages to all (or a limited set) of connected clients.

The following is a very truncated example of the QueueManager hub class that includes a few instance broadcast methods for JavaScript clients, as well as several static methods to be used by the hosting EXE to push messages to the client from the server:

public class QueueMonitorServiceHub : Hub { /// <summary> /// Writes a message to the client that displays on the status bar /// </summary> public void StatusMessage(string message, bool allClients = false) { if (allClients) Clients.All.statusMessage(message); else Clients.Caller.statusMessage(message); } /// <summary> /// Starts the service /// </summary> public void StartService() { // unpause the QueueController to start processing again Globals.Controller.Paused = false; Clients.All.startServiceCallback(true); Clients.All.writeMessage("Queue starting with " +
Globals.Controller.ThreadCount.ToString() +
" threads.", "Info", DateTime.Now.ToString("HH:mm:ss")); } public void StopService() { // Pause - we can't stop service because that'll exit the server Globals.Controller.Paused = true; Clients.All.stopServiceCallback(true); Clients.All.writeMessage("Queue has been stopped.","Info",
DateTime.Now.ToString("HH:mm:ss")); }

/// <summary> /// Context instance to access client connections to broadcast to /// </summary> public static IHubContext HubContext { get { if (_context == null) _context = GlobalHost.ConnectionManager.GetHubContext<QueueMonitorServiceHub>(); return _context; } } static IHubContext _context = null; /// <summary> /// Writes out message to all connected SignalR clients /// </summary> /// <param name="message"></param> public static void WriteMessage(string message, string id = null,
string icon = "Info", DateTime? time = null) { if (id == null) id = string.Empty; // if no id is passed write the message in the ID area // and show no message if (string.IsNullOrEmpty(id)) { id = message; message = string.Empty; } if (time == null) time = DateTime.UtcNow; // Write out message to SignalR clients HubContext.Clients.All.writeMessage(message, icon, time.Value.ToString("HH:mm:ss"), id, string.Empty); } /// <summary> /// Writes out a message to all SignalR clients /// </summary> /// <param name="queueItem"></param> /// <param name="elapsed"></param> /// <param name="waiting"></param> public static void WriteMessage(QueueMessageItem queueItem,
i
nt elapsed = 0, int waiting = -1,
DateTime? time = null) { string elapsedString = string.Empty; if (elapsed > 0) elapsedString = (Convert.ToDecimal(elapsed) / 1000).ToString("N2"); var msg = HtmlUtils.DisplayMemo(queueItem.Message); if (time == null) time = DateTime.UtcNow; // Write out message to SignalR clients HubContext.Clients.All.writeMessage(msg, queueItem.Status, time.Value.ToString("HH:mm:ss"), queueItem.Id, elapsedString, waiting); } }

This hub includes a handful of instance hub methods that are called from the client to update other clients. For example the ShowStatus method is used by browser clients to broadcast a status bar update on the UI of the browser app. Start and Stop Service operations start and stop the queue processing and also update the UI. This is the common stuff you'd expect to see in a SignalR hub.

Calling the Hub from within the Windows Service

However, the static methods in the Hub class are a little less common. These methods are called from the Windows Service application to push messages from the server to the client. So rather than having the browser initiate the SignalR broadcasts, we're using the server side EXE and SignalR host to push messages from the server to the client. The methods are static because there is no 'active' instance of the Hub and so every method call basically has to establish the context for the Hub broadcast request.

The key that makes this work is this snippet:

GlobalHost.ConnectionManager
.GetHubContext<QueueMonitorServiceHub>()
.Clients.All.writeMessage(msg, queueItem.Status, time.Value.ToString("HH:mm:ss"), queueItem.Id, elapsedString, waiting);

which gives you access to the Hub from within a server based application.

The GetHubContext<T>() method is a factory that creates a fully initialized Hub that you can pump messages into from the server. Here I simply call out to a writeMessage() function in the browser application, which is propagated to all active clients.

In the browser in JavaScript I then have a mapping for this writeMessage endpoint on the hub instance:

hub.client.writeMessage = self.writeMessage;

where self.writeMessage is a function on page that implements the display logic:

// hub callbacks writeMessage: function (message, status, time, id, elapsed, waiting) {

}

If you recall SignalR requires that you map a server-side method to a handler function (the first snippet) on the client, but beyond that there are no additional requirements. SignalR's client library simply calls the mapped method and passes any parameters you fired on the server to the JavaScript function.

For context, the result of all of this looks like the figure below where the writeMessage function is responsible for writing out the individual line request lines in the list display. The writeMessage code basically uses a handlebars.js template to merge the received data into HTML to be rendered in the page.

 

It's very cool to see this in action especially with multiple browser windows open. Even at very rapid queue processing of 20+ requests a second (for testing) you can see multiple browser windows update nearly synchronously. Very cool.

Summary

Using SignalR as a mechanism for pushing server side processing messages to the client is a powerful feature that opens up many opportunities for dashboard and notification style applications that used to run in server isolated silos previously. By being able to host SignalR in a Windows Service or any EXE based application really, you can now offload many UI tasks that previously required custom desktop applications and protocols, and push the output directly to browser based applications in real time. It's a wonderful way to rethink browser based UIs fed from server side data. Give it some thought and see what opportunities you can find to open up your server interfaces.

Resources

Make Donation
Posted in SignalR  OWIN  


Feedback for this Post

 
# re: Self-Hosting SignalR in a Windows Service
by Suraj Deshpande September 09, 2013 @ 9:57pm
Thanks for such nice post. SignalR is simply amazing. cheers!!
# re: Self-Hosting SignalR in a Windows Service
by Lex September 10, 2013 @ 3:39am
Do you know of any documentation/personal impressions of when you might consider Self host versus in IIS? Traditionally IIS was seen as faster and with a bigger feature set (caching, authentication etc...) whereas self host was a quick way to get a service up an running internally. Given this is web sockets using the new OWIN foundation I wonder if the trade offs have changed?
# re: Self-Hosting SignalR in a Windows Service
by Rick Strahl September 13, 2013 @ 6:12pm
@Lex - Self host just offers additional options for getting Web services into non-IIS scenarios, but it shouldn't be thought of as a replacement for IIS. IIS is very good at what it does and the services it provides, but you can't always use IIS for hosting as this article's example shows.

My preference is to always host inside IIS if possible because it's so much easier. Lifetime management, getting up the server, certificates caching etc. - it's all taken care of for you. When you self host you have to manage and configure all of this yourself and you have to maintain those configurations. THen there's deployment which is also more complex as you have to shut down a service or running EXE to update your code rather than live deployment.

This may change as OWIN gets more mature and more standard middleware shows up, but for now I much rather use IIS if I can.

That said, self-host IS very important for those scenarios where you actually need to host SignalR (or whatever) as part of another standalone application. The example here uses a Windows Service that is effectively a background application service - that sort of thing would not be a good fit for running in IIS (although it's possible if you launch SignalR off a background thread and ensure IIS stays alive by pinging - wrote about this in passing here: http://www.west-wind.com/weblog/posts/2007/May/10/Forcing-an-ASPNET-Application-to-stay-alive, but it's not the most elegant solution).
# re: Self-Hosting SignalR in a Windows Service
by Paul September 14, 2013 @ 11:17pm
Is there a way to add SignalR to an existing web application that uses Sessions (self-hosting in a web app)? Yes, I’ve read all the stuff about the Session object not being available within a Hub event, but I’m able to solve that problem with simple parameters to the server method.

What I’m hoping is that I can avoid having to deploy two web apps as it is simply a pain to automate and manage as part of a typical SaaS offering. Is it sufficient that I simply not attempt to access the session object or is it just the fact that sessions are used in the pipeline that cause a problem for SignalR? Is there some global.aspx event that I could catch and simply disable Sessions for SignalR requests? Anything to keep from having to deploy two web apps would be wonderful.
# re: Self-Hosting SignalR in a Windows Service
by Rick Strahl September 16, 2013 @ 1:42pm
@Paul - SignalR can be added to existing ASP.NET Web applications. In fact that's the easier route and I would recommend that if possible over self-hosting as there's no additional configuration - it just deploys as part of the ASP.NET app.

Session is not accessible because SignalR requests aren't running through the ASP.NET pipeline and the requests are completely stateless. As far as I know, other than parameters, the only way to 'pass state' to SignalR currently is by passing it on the query string via the query parameter when the SignalR hub is created (so it goes on every request). You can create some sort of ID value for a user, pass it in on creation and then store data for that user in a Database table or some other storage mechanism.
# re: Self-Hosting SignalR in a Windows Service
by Spaced September 19, 2013 @ 8:55pm

I am trying to figure out how to add a Dictionary at startup, stuff it with some read-only information, and then use that Dictionary much later in the pipeline, so all requests can use the same cached data.... Do you know how that might work with what you've explored?
# re: Self-Hosting SignalR in a Windows Service
by Rick Strahl September 21, 2013 @ 2:24am
@Spaced - Create a static Dictionary variable and then use it from anywhere in the app?
# re: Self-Hosting SignalR in a Windows Service
by Dave September 24, 2013 @ 9:27pm
Hi Rick,
This looks amazing and I could really use this for a similar problem to yours. After getting the samples working well I tried to hook it up to an empty Windws Service project but I constantly get this error:
"The server factory could not be located for the given input: Microsoft.Owin.Host.HttpListener"
Even if I copy the DLL in manually it still fails.
Do you have any ideas?
It'd be great if you could have to code above as a download (so I don't mess up pasting it in!)
Cheers,
Dave
# re: Self-Hosting SignalR in a Windows Service
by Paul September 29, 2013 @ 9:53am
One of the best features regarding the use of an ASP.NET session is that it allows a site to have a reasonable level of security. The problem with using SignalR in this same environment is that there is no access to the session so that makes it difficult to have any level of security.

Has anyone come up with a viable way to apply a security model around SignalR? I'm looking for a solution that would be high-performance (i.e. no database query for each SignalR call) but also work within or without a web farm?

My concern is this. If there is no security in a SignalR implementation, then any hacker could make the appropriate calls to listen to events.

Let me give you the real-world scenario that we are working on. We are using Twilio to receive notifications when an SMS messages is sent to a phone number. When our server receives the event, we use SignalR to pass it to any browser that has joined a hub based on that phone number. Without a way to secure the hub, any hacker could listen to all SMS messages sent to that phone number.
# re: Self-Hosting SignalR in a Windows Service
by Bill January 15, 2014 @ 4:13pm
Do you have code (a VS solution and projects) that go with this article so that we can inspect the code, see how you did it, and run it?

Thanks.
# re: Self-Hosting SignalR in a Windows Service
by Rick Strahl January 16, 2014 @ 12:01am
No project no, since it's proprietary code inside of a project, but there's another post that goes into much more detail and talks about the specifics:

http://weblog.west-wind.com/posts/2013/Sep/04/SelfHosting-SignalR-in-a-Windows-Service
# re: Self-Hosting SignalR in a Windows Service
by Sam Gentile January 29, 2014 @ 9:04am
Thanks for your post! Now I really know SignalR can be used in a Windows Server. I have a scenario with two parts to the infrastructure. There is a MVC 5 Front End Web Application and a background Windows Server that is the "broker" to the SQL database. I need a persistent HTTP connection between them to send data back from the backend and messages to the backend from the server. Would the SignalR server be in the front or the backend server? Would I use Hubs or Persistent Connections? Thanks!
# re: Self-Hosting SignalR in a Windows Service
by Rick Strahl January 29, 2014 @ 4:34pm
@Sam - You can do either - SignalR is pretty flexible. In general I think it'll be easier to integrate SignalR in IIS, because you get the hosting infrastructure for free. You don't need to configure anything - just plug-in and go. With Windows Service hosting you probably have to use a custom port and if you SSL you have to configure the certificate properly - it adds some admin overhead when you deploy the service. With IIS all of that goes away.

My general rule is, if I can use IIS I tend to use it since it reduces maintenance overhead. But if you need quick responsiveness from the backend app to the outbound requests (ie. as fast as possible to send messages and get back) nothing beats a local connection. 

For some situations like this backend monitoring service it made good sense to have the Service host the SignalR server, so that it can directly interface with the browser clients by sending out messages. But it's just as possible to host the SignalR server on IIS and have the Windows server interact with the SignalR server on IIS. Either way works - what fits best depends on your application. My reasoning for inlining was that it's likely faster to have the local SignalR connection on the same machine than making remote HTTP calls to push data out to clients in order to minimize overhead on the service (which is pretty busy).

As to hubs or connections - again up to you. Frankly I haven't had a need to use connections - I prefer the simpler Hub object model of calling remote methods, as opposed to a streaming event API. It's easier to work with for most scenarios, unless you really need the live streaming behavior (ie. video or other data, or continuous data feeds).
# re: Self-Hosting SignalR in a Windows Service
by Sam Gentile February 07, 2014 @ 7:33am
So I decided to host it in the MVC app. However, I am trying to define an asynchronous method in the Windows Service to set up SignalR. Please see http://tinyurl.com/mz4r7wu. I need your help.
# re: Self-Hosting SignalR in a Windows Service
by Matthew Nichols October 10, 2014 @ 8:11am
Thanks for this Rick...I have had a hard time finding an example of calling into a Hub from an outside source to publish data to connected SignalR clients...this gets me started.
# re: Self-Hosting SignalR in a Windows Service
by VirtualLife November 24, 2014 @ 4:47pm
Thanx. Couldn't have built my signalR service without you.

Ran into more issues than I should have, but your signalr tutorials have been extremely helpful.

In case anyone else runs into this, I don't understand why but:
If I use app.Map("", map => everything works as expected, but using
app.Map("/signalr", map => as in the example, it never works. Actually wanted the url without signalr, but lost why it didn't work. Realized this when trying to go to http://localhost:8080/signalr/hubs returned a 503 after changing and doing http://localhost:8080/hubs it worked fine.
 


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2014