How to Run DNX Applications in a Windows Service
Today I’m going to show you how you can run a DNX application in a Windows service because we recently needed to do this, and while there are many posts out there asking how to do it, the answers would lead you to believe it’s impossible. For this example we’re going to be using the beta6 version of ASP.NET 5. If you don’t yet have DNX, you should go here to get started.
Creating the Application
For demonstrative purposes, we’ll be creating a very simple DNX console application that will simply write to the event log when the service is started/stopped. To begin, create a new DNX Console Application (Package) in Visual Studio. You can find this template in the “Web” category, for some reason. Next we need to make a few tweaks to the project.json file:
- First, remove the DNX Core reference, this is a windows service after all, so no point in trying to be cross platform today.
- Under
frameworks:dnx451
, add aframeworkAssemblies
reference to"System.ServiceProcess": "4.0.0.0"
- Change your
command
key to something simple like “run” or “runservice”. You don’t have to do this, but it makes it more clear later what we’re doing.
When you’re all done, your project.json should look something like this:
{ "version": "1.0.0-*", "description": "MyDnxService Console Application", "commands": { "run": "MyDnxService" }, "frameworks": { "dnx451": { "frameworkAssemblies": { "System.ServiceProcess": "4.0.0.0" } } } }
Next, make the Program
class inherit from System.ServiceProcess.ServiceBase
. This lets us create overrides for OnStart and OnStop service methods. We’re going to use these methods to simply log out messages to the event log. Finally, in the Main(string[] args)
method of the Program
class we add Run(this);
in order to bootstrap the Windows service. Here is our program.cs file in it’s entirety:
using System.Diagnostics; using System.ServiceProcess; namespace MyDnxService { public class Program : ServiceBase { private readonly EventLog _log = new EventLog("Application") { Source = "Application" }; public void Main(string[] args) { _log.WriteEntry("Test from MyDnxService.", EventLogEntryType.Information, 1); Run(this); } protected override void OnStart(string[] args) { _log.WriteEntry("MyDnxService started."); } protected override void OnStop() { _log.WriteEntry("MyDnxService stopped."); } } }
Installing the Service
Once our application is written and building successfully we can install it as a service. To do this, open a command prompt in administrator mode and enter the following command:
sc.exe create <service-name> binPath= "\"<dnx-exe-path>\" \"<project-path>\" run"
UPDATE
If you’re targeting beta8 or beyond, the way we tell dnx.exe where to find our project has changed to include a –project (-p for short) argument. So the above command changes to:
sc.exe create <service-name> binPath= "\"<dnx-exe-path>\" -p \"<project-path>\" run"
sc.exe is a built-in tool that lets us perform a number of operations on a Windows service. Here we’re using it to create a new service. The very first argument that the create operation needs is the service name. This is the name that will show up in the services snap in. You can set this to any value, but since our example application is called MyDnxService we put our adventurous nature aside and simply used that.
The binPath=
parameter is the only other parameter we’re specifying to sc.exe, even though it looks like three separate parameters. We’re basically telling sc.exe what command to execute when starting the service, which is DNX. The other parameters after that are actually parameters to dnx.exe, not sc.exe. Because there are spaces in the argument value, we are wrapping the entire argument in quotes (I’ll explain the escaped quotes in a minute). One gotcha to keep in mind when working with sc.exe is that the “=” you see after “binPath” is actually part of the parameter name for sc.exe, so that space you see between binPath=
and the value is necessary as it tells sc.exe that we’re moving from the parameter name to the value. Now let’s look at the three components in that binPath=
argument:
- Since dnx.exe is the application that runs a DNX application, we’re going to need to point to it via its full path. If you install DNX via DNVM the default install directory (at least on our machines) is
c:\users\\.dnx\runtimes\\bin\dnx.exe
but if you aren’t sure where on your machine it is just open a command prompt and runwhere.exe dnx.exe
and it should tell you where to find it. Since the path to DNX could have spaces, we’re wrapping that parameter in quotes too, but since these quotes are inside the quotes we specified for thebinPath=
parameter to sc.exe, we need to escape them. - Next we need to tell DNX where to find our application. If you use
dnu publish
to publish your application it will generate a .cmd file for you that you normally would use to launch your application. You cannot use that .cmd file when running your application in a Windows service but if you open that .cmd file you’ll see a path for--appbase
and that’s the one we want. Again, escape the quotes around this path for safety. - Finally we tell DNX what command within our application to run. Remember above in the project.json file we named our command key “run”? that’s what this value specifies. So if you named your command key something else, just replace the “run” parameter to DNX with whatever command key name you chose in your project.json.
So putting all of that together, the complete sc.exe command we used for the application above (without the generic tokens) was:
sc.exe create MyDnxService binpath= "\"C:\Users\dave\.dnx\runtimes\dnx-clr-win-x86.1.0.0-beta6\bin\dnx.exe\" \"C:\Users\dave\Desktop\DnxWindowsService\src\MyDnxService\" run"
UPDATE
As mentioned above, if you’re targeting beta8 or beyond, the way we tell dnx.exe where to find our project has changed to include a –project (-p for short) argument. So the above command changes to:
sc.exe create MyDnxService binpath= "\"C:\Users\dave\.dnx\runtimes\dnx-clr-win-x86.1.0.0-beta8\bin\dnx.exe\" -p \"C:\Users\dave\Desktop\DnxWindowsService\src\MyDnxService\" run"
Running the Service
Now that our application has been installed as a service, it’s time to revel in the fruits of our labor. To run the service, open the Services MMC Snap-in, find the service we installed by its name and start it. Open Event Viewer, go to the Application log and you should see the text that we output from our OnStart()
override in our application. Stopping the service and refreshing the event log will show the stop message. As I said earlier, this is a super basic demonstration of functionality, but it’s the basis for running any DNX application in the context of a Windows service.
Why Do This?
Taskmatics Scheduler currently hosts the administration website from inside one of the Windows services that are installed. In its current implementation we couldn’t support ASP.NET applications because only IIS would provide the environment necessary to properly run each request through the pipeline. With ASP.NET 5, the game changes, and you can host ASP.NET applications from a console application without being dependent on IIS. After some headbanging we are successfully running our MVC 6 web application inside of a Windows service, which you can now read about here.
Update!
Check out Erez’s follow up post showing you how to host a fully functional ASP.NET site from a Windows service, which has just been posted!
Hi Dave, thanks for the article…very helpful. An issue I ran into was that after deploying I’m having a hard time accessing configuration values from my Config.json. While in Dev, I can access them easily using Environment.CurrentDirectory. But after installing as a service, Environment.CurrentDirectory resolves to C:\Windows\System32…trying to get the appdomain base directory goes to location of the dnx.exe. Is there an easy way you discovered to determine/access the appbase from code?
We had encountered the same issue a while back, and at the time we countered it by moving the DNX executable to the app base folder. It appears that now there’s an easier way to do this. Just create a constructor in the Program class that takes in the
IApplicationEnvironment
interface, which contains a property calledApplicationBasePath
. DNX will inject the dependency for you when the application is bootstrapped. The signature will look like this:public Program(IApplicationEnvironment environment)
Hope this helps.
Thanks Dave, for some reason I thought that was only applicable to web projects…worked like a charm.
Hi, I’ve tried to create a service following your steps, but I am getting a “Windows could not start the SERVICENAME service on Local Computer. Error 1053: The service did not respond to the start….”
the DNX path is correct (available from a command window), I am pointing to the folder inside the SRC folder (when publishing the windows service) and the name of the command is correct…Any ideas of what may be going on?
I am using dnx-clr-win-x86.1.0.0-beta8 and the machine is running windows 10
Thanks
Carlos,
In the Beta8 release they changed the way you specify the location of the project to the dnx executable. As such, you would now change your service creation command to this:
sc.exe create binPath= "\"<dnx-exe-path>\" -p \"<project-path>\" run"
You can see the change and the rationale for it here: https://github.com/aspnet/Announcements/issues/52
Thank you so much!! All sorted now. Now I just have to code the current service I need!
C
Is it possible to publish the service as a standalone application/executable which is then registered by sc.exe? or does it always have to be run via dnx? This means I need to have DNX installed on the target machine.
The hosting model is in the process of being changed. DNX is actually going away. Applications will compiled using a new CLI called ‘dotnet’. Parts of DNX will be embedded in an application’s compile output. You will be able to launch your app directly using this new approach. With that, you can have SC point directly to that.
I will be posting a follow up to this article on how to do this when the RC2 bits are released in late February 2016.
Hello everybody, I found this topic while looking for a solution to a similar problem. Ended up implementing something that works pretty much like very (very) simplified Topshelf. You can easily run as console and/or install, uninstall and so one as a service. I think it can be pretty useful for somebody. You can find the code here : https://github.com/PeterKottas/DotNetCore.WindowsService and there’s nuget here as well https://www.nuget.org/packages/PeterKottas.DotNetCore.WindowsService/ . Enjoy