Việc xây dựng các Windows Service để xử lý tác vụ ngầm hoặc chạy các công việc định kỳ là một mô hình thiết kế phần mềm phổ biến. Mặc dù các nền tảng điện toán đám mây như Azure Functions hay AWS Lambda đang dần thay thế vai trò này, Windows Service vẫn cực kỳ quan trọng trong các hệ thống on-premise hoặc khi cần chuyển đổi các ứng dụng legacy sang .NET Core mà chưa thể chuyển lên mô hình serverless.
Trong .NET Core, việc tạo Windows Service về cơ bản khá tương đồng với .NET Framework, nhưng yêu cầu một số thiết lập bổ sung để tương thích với môi trường đa nền tảng.
Cài đặt thư viện cần thiết
Do Visual Studio không cung cấp sẵn template riêng biệt cho Windows Service trên .NET Core, bạn cần khởi tạo một dự án Console Application. Sau đó, để truy cập vào các API đặc thù của Windows, bạn phải cài đặt gói NuGet sau thông qua Package Manager Console:
Install-Package Microsoft.Windows.Compatibility
Gói thư viện này cung cấp các lớp cần thiết để tương tác với Service Control Manager của hệ điều hành Windows.
Triển khai lớp dịch vụ
Thành phần quan trọng nhất trong gói thư viện trên là lớp ServiceBase. Đây là lớp cơ sở cho phép bạn ghi đè (override) các sự kiện quan trọng như khởi chạy, dừng hoặc tạm ngưng dịch vụ.
Dưới đây là một ví dụ về lớp dịch vụ đơn giản thực hiện việc ghi log hoạt động vào tệp tin:
using System;
using System.IO;
using System.ServiceProcess;
public class MyDataProcessor : ServiceBase
{
private readonly string _logPath = @"C:\Logs\service_status.txt";
private void RecordActivity(string message)
{
string folder = Path.GetDirectoryName(_logPath);
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
File.AppendAllText(_logPath, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}{Environment.NewLine}");
}
protected override void OnStart(string[] args)
{
RecordActivity("Dịch vụ đã được khởi động.");
base.OnStart(args);
}
protected override void OnStop()
{
RecordActivity("Dịch vụ đã dừng hoạt động.");
base.OnStop();
}
protected override void OnPause()
{
RecordActivity("Dịch vụ đang tạm nghỉ.");
base.OnPause();
}
}
Trong phương thức Main của ứng dụng, chúng ta sẽ thực hiện việc kích hoạt dịch vụ như sau:
static void Main(string[] args)
{
ServiceBase.Run(new MyDataProcessor());
}
Đóng gói và triển khai dịch vụ
Để triển khai ứng dụng dưới dạng một Windows Service, bạn không thể chỉ sử dụng lệnh build thông thường. Bạn cần chỉ định cụ thể Runtime Identifier (RID) để trình biên dịch tạo ra tệp thực thi (.exe) tương ứng cho Windows. Sử dụng lệnh sau trong terminal tại thư mục gốc của dự án:
dotnet publish -r win-x64 -c Release
Sau khi lệnh chạy xong, bạn sẽ tìm thấy tệp .exe tại thư mục /bin/release/netcoreappX.X/publish. Lưu ý rằng nếu không chỉ định tham số -r, bạn sẽ chỉ nhận được tệp .dll, thứ không thể đăng ký trực tiếp làm dịch vụ Windows.
Để cài đặt dịch vụ vào hệ thống, hãy mở Command Prompt với quyền quản trị (Administrator) và sử dụng lệnh sc:
sc create MyUniqueService binPath= "C:\Đường_dẫn_đến_tệp\MyDataProcessor.exe"
Sau khi nhận được thông báo thành công, bạn có thể bắt đầu dịch vụ bằng lệnh:
sc start MyUniqueService
Khi cần gỡ bỏ dịch vụ, hãy sử dụng chuỗi lệnh dừng và xóa:
sc stop MyUniqueService
sc delete MyUniqueService
Kinh nghiệm gỡ lỗi (Debugging)
Một trong những điểm yếu của việc sử dụng ServiceBase mặc định là khó khăn trong quá trình gỡ lỗi vì các phương thức như OnStart được bảo vệ (protected). Một kỹ thuật phổ biến để khắc phục điều này là tạo ra các phương thức public bọc ngoài và kiểm tra chế độ chạy trong Main:
public void DebugStart(string[] args) => OnStart(args);
static void Main(string[] args)
{
var service = new MyDataProcessor();
if (Environment.UserInteractive)
{
service.DebugStart(args);
Console.WriteLine("Dịch vụ đang chạy ở chế độ console. Nhấn phím bất kỳ để dừng...");
Console.ReadKey();
}
else
{
ServiceBase.Run(service);
}
}
Cách tiếp cận này cho phép bạn chạy ứng dụng như một Console app thông thường khi phát triển (F5 trong Visual Studio) và chạy như một Service thực thụ khi được triển khai bởi hệ thống.