Wrapping a C# service in a console app to debug it

僤鯓⒐⒋嵵緔 提交于 2019-11-28 22:40:48

You can do something like this in the main entry point:

static void Main()
{
#if DEBUG
    Service1 s = new Service1();
    s.Init(); // Init() is pretty much any code you would have in OnStart().
#else
    ServiceBase[] ServicesToRun;
    ServicesToRun=new ServiceBase[] 
    { 
        new Service1() 
    };
    ServiceBase.Run(ServicesToRun);
#endif
}

and in your OnStart Event handler:

protected override void OnStart(string[] args)
{
    Init();
}

The approach I always take is to isolate out all of your application's logic into class libraries. This makes your service project really just a shell that hosts your class libraries as a service.

By doing this, you can easily unit test and debug your code, without having to deal with the headache of debugging a service by attaching to a process. I'd recommend unit testing of course, but if you're not doing that then adding a console application that calls the same entry point as your service is the way to go.

To avoid using global defines I generally test at run-time whether I'm a service or regular application through the Environment.UserInteractive property.

    [MTAThread()]
    private static void Main()
    {
        if (!Environment.UserInteractive)
        {
            ServiceBase[] aServicesToRun;

            // More than one NT Service may run within the same process. To add
            // another service to this process, change the following line to
            // create a second service object. For example,
            //
            //   ServicesToRun = New System.ServiceProcess.ServiceBase () {New ServiceMain, New MySecondUserService}
            //
            aServicesToRun = new ServiceBase[] {new ServiceMain()};

            Run(aServicesToRun);
        }
        else
        {
            var oService = new ServiceMain();
            oService.OnStart(null);
        }
   }

TopShelf is another project that is perfect for this approach. It allows you to run a process as a service, or as a regular console application with minimal configuration.

You could call the service methods via reflection as seen below.

Using Environment.UserInteractive enables us to know if we are running as a console app or as a service.

ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
    new MyService()
};

if (!Environment.UserInteractive)
{
    // This is what normally happens when the service is run.
    ServiceBase.Run(ServicesToRun);
}
else
{
    // Here we call the services OnStart via reflection.
    Type type = typeof(ServiceBase);
    BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
    MethodInfo method = type.GetMethod("OnStart", flags);

    foreach (ServiceBase service in ServicesToRun)
    {
        Console.WriteLine("Running " + service.ServiceName + ".OnStart()");
        // Your Main method might not have (string[] args) but you could add that to be able to send arguments in.
        method.Invoke(service, new object[] { args });
    }

    Console.WriteLine("Finished running all OnStart Methods.");

    foreach (ServiceBase service in ServicesToRun)
    {
        Console.WriteLine("Running " + service.ServiceName + ".OnStop()");
        service.Stop();
    }
}

I tend to have either a config setting or use a directive for debug builds:

 #if DEBUG
    Debugger.Break();
 #endif

or

if(Settings.DebugBreak)
            Debugger.Break();

I put that in the OnStart method of the service component. Then you are prompted automatically and attached to the process.

Here is a blog post about running your windows service as a console app.

You could also just create a new Console Application that references the same logic as your service to test methods, or set up Unit Tests on your services' logic

I have used unit tests to debug difficult setups in the past, just write a unit test that calls whatever service method with whatever parameters and set debug breakpoints in the unit test.

Using testdriven.net or jetbrains testrunner makes it easier.

I use this to check if my process is running as a service or not.

public class ServiceDiagnostics
{
    readonly bool _isUserService;
    readonly bool _isLocalSystem;
    readonly bool _isInteractive;

    public ServiceDiagnostics()
    {
        var wi = WindowsIdentity.GetCurrent();
        var wp = new WindowsPrincipal(wi);

        var serviceSid = new SecurityIdentifier(WellKnownSidType.ServiceSid, null);
        var localSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
        var interactiveSid = new SecurityIdentifier(WellKnownSidType.InteractiveSid, null);

        this._isUserService = wp.IsInRole(serviceSid);

        // Neither Interactive or Service was present in the current user's token, This implies 
        // that the process is running as a service, most likely running as LocalSystem.
        this._isLocalSystem = wp.IsInRole(localSystemSid);

        // This process has the Interactive SID in its token.  This means that the process is 
        // running as a console process.
        this._isInteractive = wp.IsInRole(interactiveSid);
    }

    public bool IsService
    {
        get { return this.IsUserService || this.IsLocalSystem || !this.IsInteractive; }    
    }

    public bool IsConsole
    {
        get { return !this.IsService; }
    }

    /// <summary>
    /// This process has the Service SID in its token. This means that the process is running 
    /// as a service running in a user account (not local system).
    /// </summary>
    public bool IsUserService
    {
        get { return this._isUserService; }
    }

    /// <summary>
    /// Neither Interactive or Service was present in the current user's token, This implies 
    /// that the process is running as a service, most likely running as LocalSystem.
    /// </summary>
    public bool IsLocalSystem
    {
        get { return this._isLocalSystem; }
    }

    /// <summary>
    /// This process has the Interactive SID in its token.  This means that the process is 
    /// running as a console process.
    /// </summary>
    public bool IsInteractive
    {
        get { return this._isInteractive; }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!