可调试的 Windows 服务模板项目(C#)





5.00/5 (10投票s)
用 C# 编写的易于调试的 Windows 服务项目模板。
引言
Windows 服务是非常强大的应用程序,可用于在后台执行许多不同类型的任务。它们无需任何用户登录即可启动,并且可以使用与已登录用户不同的用户帐户运行。但是,如果 Windows 服务应用程序是按照常规服务开发步骤开发的,即使在开发环境中也很难调试。
本文提出了一种在开发阶段无需使用任何服务开发库(如 Topshelf)即可轻松监控和调试 Windows 服务的新开发方式。
特点
示例项目具有以下功能
- 它在调试模式下作为控制台应用程序运行,在发布模式下作为常规 Windows 服务运行。
- 根据日志类型使用不同的文本颜色在控制台上显示日志消息。
- Windows 服务相关的操作和实际后台任务处理操作是分开的,因此实际服务操作可以轻松进行单元测试。
- 可以使用 InstallUtil.exe 安装和卸载服务。但您不需要安装它即可进行测试和调试。
准备此应用程序的步骤
示例项目是使用 Visual Studio 2017 准备的。
1. 创建 Windows 服务项目
在 Visual Studio 中,单击 文件\新建\项目。选择“Visual C#\Windows 桌面\Windows 服务”项目类型。
2. 将项目输出类型从 Windows 应用程序更改为控制台应用程序
右键单击项目名称并选择“属性”以打开项目属性。
从“输出类型”选择列表中选择控制台应用程序。
3. 安装 log4net 包
从包管理器控制台或从管理 Nuget 包菜单选项安装 log4net nuget 包。
4. 配置 log4net
log4net 是一个非常强大的日志记录库,可以将日志写入许多目标,例如文本文件、控制台、数据库、Windows 事件日志,甚至以电子邮件形式发送。这些日志写入器(或目标)称为“追加器”。log4net 配置必须至少有一个追加器,但它可以有多个。每个追加器都有自己的设置。
log4net 配置可以添加到 app.config 文件中,也可以是一个单独的文件。我更喜欢单独文件的方法,因此为 log4net 添加两个配置文件,它们将在调试和发布模式下使用。名称无关紧要,您可以将它们命名为“log4net.debug.config”和“log4net.prod.config”。调试配置文件有两个追加器:RollingFileAppender
和 ColoredConsoleAppender
。生产配置文件也有两个追加器:RollingFileAppender
和 EventLog
。但是 EventLog
追加器已注释掉,如果您想写入 Windows 事件日志,可以取消注释。
最小日志级别在 <root>
元素下定义为 <level>
配置元素。对于调试配置,级别是 DEBUG
,对于生产,它是 INFO
。有效级别是:DEBUG
、INFO
、WARN
、ERROR
。有关更多信息,请参阅 log4net 文档。
下一个配置步骤是告诉 log4net
库在调试和发布模式下使用哪个文件。为此,打开 AssemblyInfo.cs 文件并为 log4net 添加程序集级别属性。添加这些行以在调试和发布模式下切换两个文件。
#if DEBUG
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.debug.config", Watch = true)]
#else
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.prod.config", Watch = true)]
#endif
5. 添加 SampleBackgroundService 类,其中包含我们的 Windows 服务将执行的实际后台操作
这是一个非常简单的类,包含用于启动和停止后台任务处理线程的 Start
、Stop
方法以及一个持续写入日志的线程方法。
class SampleBackgroundService
{
//Get a logger for this class from log4net LogManager
private static ILog logger = LogManager.GetLogger(typeof(SampleBackgroundService));
//Starts the thread
public void Start() { ... }
//Stops the thread
public void Stop() { ... }
//Service thread that performs background tasks and writes logs
private void serviceThread()
{
while (!stopRequested)
{
//Write different types of logs...
}
}
}
6. 更新自动生成的 Windows 服务类中的代码
我已将自动添加的 Windows Services 类重命名为 SampleWindowsService
。此类继承自 ServiceBase
,并且在 Windows 服务启动或停止时会调用其 OnStart
和 OnStop
方法。此类只创建 SampleBackgroundService
类的一个实例并调用其 Start
和 Stop
方法。
public partial class SampleWindowsService : ServiceBase
{
SampleBackgroundService sampleBackgroundService;
public SampleWindowsService()
{
InitializeComponent();
}
//Called when the Windows service is started
protected override void OnStart(string[] args)
{
sampleBackgroundService = new SampleBackgroundService();
sampleBackgroundService.Start();
}
//Called when the Windows service is stopped
protected override void OnStop()
{
sampleBackgroundService.Stop();
}
}
7. 更新 Program.cs 文件中的 Main 方法,使其在调试和发布模式下行为不同
创建新的 Windows 服务项目时,自动生成的 Main
方法包含用于创建和启动自动生成的 Windows 服务的代码。但是 Windows 服务不能作为常规应用程序启动和托管。因此,当您尝试运行应用程序时,会出现错误消息。
为了运行和测试我们的应用程序,我们不需要创建真实的 Windows 服务实例,因为它除了创建和启动 SampleBackgroundService
类的一个实例之外不包含任何代码。Main
方法中更新的代码在调试模式下创建并启动 SampleBackgroundService
类的一个实例,并作为控制台应用程序运行。但在发布模式下创建并运行真实的 Windows 服务。
static void Main()
{
ILog logger = LogManager.GetLogger(typeof(Program));
#if DEBUG //Run as a regular console application in Debug mode
//Manually create an instance of SampleBackgroundService class and call its start method
logger.Info("Starting services...");
SampleBackgroundService _backgroundService = new SampleBackgroundService();
_backgroundService.Start();
logger.Info("Services started. Press enter to stop...");
Console.ReadLine();
logger.Info("Stopping service...");
_backgroundService.Stop();
logger.Info("Stopped.");
#else //Create and run the real Windows service instance in Release mode
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new SampleWindowsService()
};
ServiceBase.Run(ServicesToRun);
#endif
}
8. 添加服务安装程序组件,以便能够使用 InstallUtil.exe 安装此服务
要添加安装程序组件,请双击解决方案资源管理器中的 SampleWindowsService.cs。它将显示服务的设计视图。
右键单击设计区域并在上下文菜单中单击“添加安装程序”。
这将向项目添加 ProjectInstaller.cs 和一个设计器文件。删除自动生成的代码 ProjectInstaller.InitializeComponent()
方法和自动生成的变量 (serviceProcessInstaller1
, serviceInstaller1
)。
将以下代码添加到 ProjectInstaller.cs 文件中
public partial class ProjectInstaller : Installer
{
public const string SERVICE_NAME = "Sample Background Service";
private readonly ServiceProcessInstaller m_ServiceProcessInstaller;
private readonly ServiceInstaller m_ServiceInstaller;
public ProjectInstaller()
{
//Installer that installs the process (in this case 'DebuggableWindowsService.exe')
//There can be only one ServiceProcessInstaller
m_ServiceProcessInstaller = new ServiceProcessInstaller();
m_ServiceProcessInstaller.Account = ServiceAccount.LocalSystem;
//Installer that registers actual Windows Service implementations in the application
//There may be one or more ServiceInstaller
m_ServiceInstaller = new ServiceInstaller();
m_ServiceInstaller.ServiceName = SERVICE_NAME;
m_ServiceInstaller.Description = "";
m_ServiceInstaller.StartType = ServiceStartMode.Automatic;
m_ServiceInstaller.DelayedAutoStart = true;
Installers.Add(m_ServiceProcessInstaller);
Installers.Add(m_ServiceInstaller);
InitializeComponent();
}
//...
}
如果您想在服务安装前后执行任何任务,您可以重写适当的基类方法,例如 OnBeforeInstall
、OnBeforeUninstall
等。
您不需要安装(或将服务注册到 Windows 服务注册表)您的服务即可运行和调试。但如果您愿意,可以使用 InstallUtil.exe 安装和卸载服务。InstallUtil.exe 位于 .NET Framework 安装文件夹下。例如,“C:\Windows\Microsoft.NET\Framework\v4.0.30319”。
要注册服务,请打开命令行窗口并使用可执行文件的完整路径运行 InstallUtil.exe。您可能需要“以管理员身份”运行命令行窗口才能注册服务。
C:\Windows\Microsoft.NET\Framework\v4.0.30319>InstallUtil.exe
"D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe"
要卸载服务,请使用 /u
选项运行相同的命令。
C:\Windows\Microsoft.NET\Framework\v4.0.30319>InstallUtil.exe
/u "D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe"
9. 在调试模式下运行应用程序以查看日志输出和调试
以下是应用程序在调试模式下运行时显示的示例输出。
关于 Windows 服务的重要注意事项
Windows 服务应用程序在某些方面与其他常规应用程序不同。因此,应用程序在调试(控制台应用程序模式)和发布(Windows 服务模式)模式下可能表现不同。
首先,当您在调试模式下运行应用程序时,其工作目录将是可执行文件所在的路径,例如,“D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe”。
但是,当您使用 InstallUtil.exe 或安装应用程序安装它并从 Windows 服务管理应用程序运行它时,其工作目录将是“C:\Windows\System32”或“C:\Windows\SysWOW64”,具体取决于您的服务是 64 位还是 32 位,以及它是在 32 位还是 64 位 Windows 上运行。
如果您想在安装目录而不是系统目录中读取或写入文件,您可以在启动时更改工作目录。例如
Environment.CurrentDirectory =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
其次,Windows 服务可以使用与已登录用户不同的用户帐户运行。在调试模式下运行的一些操作在应用程序作为真实的 Windows 服务运行时可能无法运行。这些操作的示例包括访问目录或网络路径、打开端口...
第三,Windows 服务没有用户界面,通常无法显示用户界面。它们对显卡的访问被 Windows 操作系统阻止。不幸的是,不可能从 Windows 服务中使用强大的 GPU。要了解有关此限制的更多信息,请搜索“会话 0 隔离”。
要从 Windows 服务显示用户界面,请查看这篇文章:Windows 服务可以有 GUI 吗?Windows 服务应该有 GUI 吗? 感谢 Burak-Ozdiken 分享此链接。
历史
- 2019 年 7 月 18 日 初始版本。