Update: If you want to read about Topshelf, please go to the updated guide to Topshelf, which covers how to get started with Topshelf 3
This is the first post in a series of posts on the open source service bus implementation, MassTransit. In order to push myself through learning it, I will do these posts as I go and document stuff I learn on the way.
This first post will be about one thing, that you’ll probably end up doing quite a few times while building your awesome messaging-based distributed system: Creating a service.
Check out TopShelf and build it
I’ve described how to use TopShelf before, but a lot has changed since then. TopShelf, at the time of writing, is now in version 2.2.1.0, and since 2.0 it has a feature called “shelving”, which is the ability to host any number of services beneath one Windows Service – just like IIS hosts web apps. This is pretty awesome, but today I’ll just show “the old API”, which IMO is still pretty slick!
First, make sure you have a working Ruby installation with Albacore ( gem install albacore). You’ll need these to build Topshelf.
Next, git clone git://github.com/phatboyg/Topshelf.git and build.bat. That should leave a bunch of binaries in the NET35 and NET40 folders beneath build_output. This is open source, baby!
Put your app on the TopShelf
Create a new “Console Application” and make sure to target the full .NET 4 framework (and not that bastard .NET 4 Client Profile thingie, which is default…)
Now add a reference to Topshelf.dll and log4net.dll. Punch in something like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Program { static void Main() { HostFactory.Run(x => { x.SetDescription("MyBackend-description"); x.SetDisplayName("MyBackend-display-name"); x.SetServiceName("MyBackend-service-name"); }); } } |
This is all the necessary stuff to create a fully functional program, that can be run and debugged by running the resulting .exe – either from the command line, or with F5 inside Visual Studio.
Moreover, it can be installed as a Windows Service by going to the command line (as an administrator!) and executing NameOfYourApp.exe install. On my machine, I got this output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
C:\temp\TransitDemo\Backend\bin\Debug>Backend.exe install Running a transacted installation. Beginning the Install phase of the installation. Installing service MyBackend-service-name.. Service MyBackend-service-name has been successfully installed. Creating EventLog source MyBackend-service-name in log Application... The Install phase completed successfully, and the Commit phase is beginning. The Commit phase completed successfully. The transacted install has completed. |
Now I can net start MyBackend-service-name and net stop MyBackend-service-name on the command line, or I can go to Windows’ service control snap-in and pull the usual levers to control it. Nifty!
Now I have a perfectly functioning Windows Service that does nothing at all! Let’s output some stuff to see where we’ll put our code in a short while. Try extending the x => { ... } lambda to make the program look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Program { static void Main() { HostFactory.Run(x => { x.Service<MyBackend>(c => { c.SetServiceName("MyBackend"); c.ConstructUsing(() => new MyBackend()); c.WhenStarted(d => d.StartMyBackend()); c.WhenStopped(d => d.StopMyBackend()); }); x.SetDescription("MyBackend-description"); x.SetDisplayName("MyBackend-display-name"); x.SetServiceName("MyBackend-service-name"); }); } } |
and a class that looks something like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
class MyBackend { public void StartMyBackend() { Console.WriteLine("Started!"); } public void StopMyBackend() { Console.WriteLine("Stopped!"); } } |
Now, when I press F5, wait a little, and press Ctrl + C, I get this output on the console:
1 2 3 |
Started! Stopped! Press any key to continue . . . |
Awesome, huh?
I really like how TopShelf now uses a Func<TService> to construct its service instance via ConstructUsing, instead of depending on the common service locator as the old TopShelf did. This way, I can pull the instance from an IoC container if I want.
In the next post I will take a look at how to send a message from a web application when someone tries to register as a user.
This is awesome. I am ready for part 2 now.
When I hit Ctrl+C in my console I don’t get the Stopped message but visual studio breaks in the HostingProcess. Is there a setting I need to use?
Good to hear you’re ready for part 2 🙂
I’m guessing you break on some exception that the framework catches and handles, which is probably just how things work when the service shuts down.
Does the exception type reveal anything about its nature?
The debugger breaking on Control+C is a feature that was added in VS2010. It’s annoying, since you can’t cleanly shut down your debug processes with Topshelf.
Hopefully we’ll come up with a secret keystroke that gets around it, because disabling the “break on Control+C” in the VS settings doesn’t seem to fix it either.
Maybe if we are running under a debugger, we’ll switch to Control+Break or something (which normally FORCES an exit instead of signaling a clean exit).
I turned off breaking on exceptions – it just stops in Microsoft.VisualStudio.HostingProcess.HostProc.WaitForThreadExit and shows disassembly. if I hit run again focus goes back to the console but the stop is never reached. Something else must be trapping the control-c.
I just ran it without the debugger on and it stopped properly.