如何開發並一鍵發布WindowsService項目(netcore普通項目)
netcore下開發windows服務如果是web項目的話,由於aspnetcore本身是支持的,把默認的host.Run改為host.RunAsService就可以了。
但是普通的netcore的控制台項目我終於找到了如下方式來實現:
1. 打開vs 選擇創建一個新的netcore 控制台項目
Nuget添加如下引用
- Microsoft.Extensions.Hosting
- System.ServiceProcess.ServiceController
新建一個ServiceBaseLifetime.cs 並將下面的內容復制進去
public class ServiceBaseLifetime : ServiceBase, IHostLifetime { private readonly TaskCompletionSource _delayStart = new TaskCompletionSource(); public ServiceBaseLifetime(IApplicationLifetime applicationLifetime) { ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime)); } private IApplicationLifetime ApplicationLifetime { get; } public Task WaitForStartAsync(CancellationToken cancellationToken) { cancellationToken.Register(() => _delayStart.TrySetCanceled()); ApplicationLifetime.ApplicationStopping.Register(Stop); new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing. return _delayStart.Task; } private void Run() { try { Run(this); // This blocks until the service is stopped. _delayStart.TrySetException(new InvalidOperationException("Stopped without starting")); } catch (Exception ex) { _delayStart.TrySetException(ex); } } public Task StopAsync(CancellationToken cancellationToken) { Stop(); return Task.CompletedTask; } // Called by base.Run when the service is ready to start. protected override void OnStart(string[] args) { _delayStart.TrySetResult(null); base.OnStart(args); } // Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync. // That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion. protected override void OnStop() { ApplicationLifetime.StopApplication(); base.OnStop(); } }
新建一個ServiceBaseLifetimeHostExtensions.cs 並將下面的內容復制進去
public static class ServiceBaseLifetimeHostExtensions { public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder) { return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>()); } public static void RunAsService(this IHostBuilder hostBuilder) { hostBuilder.UseServiceBaseLifetime().Build().Run(); } public static Task RunAsServiceAsync(this IHostBuilder hostBuilder) { return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(CancellationToken.None); } }
新建一個服務類 TestService.cs 並寫入以下內容(該服務就是每1秒往d:\log.txt寫入當前時間)
public class TestService: IHostedService,IDisposable { readonly System.Timers.Timer tmBak = new System.Timers.Timer(); public TestService() { tmBak.Interval = 1000;//1秒執行1次 tmBak.AutoReset = true;//執行1次false,一直執行true tmBak.Enabled = true; tmBak.Elapsed += (sender, eventArgs) => { using (StreamWriter sw = new StreamWriter("D:\\log.txt",true)) { sw.WriteLine($"AntDeploy Windows服務:{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); } }; } public Task StartAsync(CancellationToken cancellationToken) { tmBak.Start(); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { tmBak.Stop(); return Task.CompletedTask; } public void Dispose() { this.tmBak.Dispose(); } }
編輯 Program.cs 寫入如下內容,保證既可以作為控制台打開又可以作為windows服務運行:
class Program { // P/Invoke declarations for Windows. [DllImport("kernel32.dll")] static extern IntPtr GetConsoleWindow(); [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr hWnd); public static bool HaveVisibleConsole() { return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? IsWindowVisible(GetConsoleWindow()) : Console.WindowHeight > 0; } private static async Task Main(string[] args) { var pathToExe = Process.GetCurrentProcess().MainModule.FileName; var pathToContentRoot = Path.GetDirectoryName(pathToExe); Directory.SetCurrentDirectory(pathToContentRoot); var isService = !(Debugger.IsAttached || args.Contains("--console")); if (HaveVisibleConsole()) isService = false; var builder = new HostBuilder() .ConfigureServices((hostContext, services) => { services.AddHostedService<TestService>(); }); if (isService) { await builder.RunAsServiceAsync(); } else { await builder.RunConsoleAsync(); } } }
一鍵發布到服務器,在工程上點擊右鍵 然后選擇 AntDeploy
AntDeploy是我開發的一款開源一鍵部署vs插件(也是支持脫離vs單獨使用的一個開源工具)
開源地址:https://github.com/yuzd/AntDeployAgent
配置AntDeploy
添加一個環境 名字叫 測試 然后 在 測試環境里面添加 windows服務器 這里我做測試就添加就是我本機,注意Host里面是填寫格式為:ip:端口號
注意:Token不是windows服務器的密碼!!!是安裝agent后,agent的配置文件里面配置的Token(你自己自定義配置的)
注意:Port不是你要發布的項目的端口號!!!是安裝agent后,agent的配置文件里面配置的端口號(你自己自定義配置的)
點擊【Connect Test】按鈕進行確認agent可以成功鏈接,否則會發布失敗
如果【Connect Fail】失敗 請查看 #10
進入 WindowsService Tab界面
Sdk類型選擇 netcore
ServiceName 填寫上面我們設置的名稱:[TestService]
點擊 【Deploy】按鈕進行發布
確認服務器無誤 點擊 【是】開始執行一鍵部署
如果發布出現錯誤會出現下圖所示:
可以在Log里面查看失敗原因是因為我部署agent沒有用管路員權限 報權限不足失敗 需要用管理員權限運行agent才行
部署成功 如下圖:
查看D盤下是否log.txt是否正常每隔1秒寫入了當前時間
這里演示的是windows服務上沒有這個service
所以自動創建了。
如果service已存在的情況 Deploy 就會全量覆蓋 不會重新創建site的。
如果想要覆蓋時排除指定文件 可以在 Setting Tab界面的IgnoreList里面增加(支持正則)