此文源于前公司在迁移项目到.NET Core的过程中,希望使用Generic Host来管理定时任务程序时,没法部署到Windows服务的问题,而且官方也没给出解决方案,只能关注一下官方issue #809 等他们方解决了。

官方文档只提供了一个《在 Windows 服务中托管 ASP.NET Core》的方案,可以使用Microsoft.AspNetCore.Hosting.WindowsServices类库来把Web应用部署为Windows服务。但是ASP.NET Core虽然是控制台程序,但是它本身是使用了含有HTTP管道的Web Host来负责应用程序的生命周期管理,用它来作为定时任务的话,会有很多不必要的工作负载,例如占用端口、增加了很多依赖等等。

官方意识到这个问题之后,在.NET Core 2.1版本新增了Generic Host通用主机,剥离了原来WebHost的Http管道相关的API,源码中可以发现Web Host已经基于Generic Host实现。它才是作为纯粹定时任务程序的最佳拍档。

但是由于Generic Host本身非常简单,用它运行的程序设置在注册为Windows服务启动之后会自动停止。研究很久之后才知道,想在Windows上启动服务,还是不能像Linux上那么简单——

于是尝试结合Topshelf来创建Windows服务,最终成功了。

1|1实现方法

  1. 先实现IHostLifetime接口来接管应用程序的生命周期,其实就是用空的实现来替换掉默认的ConsoleLifetime,这样就可以在之后由Topshelf框架内部去管理生命周期。
    internal class TopshelfLifetime : IHostLifetime
{
public TopshelfLifetime(IApplicationLifetime applicationLifetime, IServiceProvider services)
{
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
} private IApplicationLifetime ApplicationLifetime { get; } public Task WaitForStartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
} public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
  1. 然后实现IHostedService接口,把后台任务逻辑写到StartAsync方法中,参见官方文档《在 ASP.NET Core 中使用托管服务实现后台任务》,本文示例使用定时写入文本到一个文件来测试定时任务是否成功运行。
    internal class FileWriterService : IHostedService, IDisposable
{
private static string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"test.txt"); private Timer _timer; public Task StartAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested) return Task.FromCanceled(cancellationToken); _timer = new Timer(
(e) => WriteTimeToFile(),
null,
TimeSpan.Zero,
TimeSpan.FromSeconds(10)); return Task.CompletedTask;
} public void WriteTimeToFile()
{
if (!File.Exists(path))
{
using (var sw = File.CreateText(path))
{
sw.WriteLine(DateTime.Now);
}
}
else
{
using (var sw = File.AppendText(path))
{
sw.WriteLine(DateTime.Now);
}
}
} public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask;
} public void Dispose()
{
_timer?.Dispose();
}
}
  1. 构建Generic Host,在ConfigureServices方法中注册TopshelfLifetime,并且注册一个托管服务FileWriterService,就能完成Generic Host的简单构建,当然完整的项目应该还包含配置、日志等等。最后,使用Topshelf来接管Generic Host,创建Windows服务。
    internal class Program
{
private static void Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IHostLifetime, TopshelfLifetime>();
services.AddHostedService<FileWriterService>();
}); HostFactory.Run(x =>
{
x.SetServiceName("GenericHostWindowsServiceWithTopshelf");
x.SetDisplayName("Topshelf创建的Generic Host服务");
x.SetDescription("运行Topshelf创建的Generic Host服务"); x.Service<IHost>(s =>
{
s.ConstructUsing(() => builder.Build());
s.WhenStarted(service =>
{
service.Start();
});
s.WhenStopped(service =>
{
service.StopAsync();
});
});
});
}
}
  1. 最后发布应用程序,并安装到Windows服务。

以管理员权限开启终端,执行命令:

  dotnet publish -c release -r win-x64

  cd path-to-project/bin/release/netcoreapp2.1/win-x64/publish

  ./project-name install

  net start GenericHostWindowsServiceWithTopshelf

这样这个Windows服务就启动了!查看输出文件,可以看到定时写入成功,服务也一直没关闭~

1|2示例代码

https://github.com/ElderJames/GenericHostWindowsServiceWithTopshelf

1|3参考链接

官方文档《.NET 通用主机》

官方文档《在 ASP.NET Core 中使用托管服务实现后台任务》

__EOF__

作  者:ElderJames

.NET Core Generic Host Windows服务部署使用Topshelf的更多相关文章

  1. .NET Core Generic Host项目使用Topshelf部署为Windows服务

    1..NET Core Generic Host是什么? 在.NET Core 2.1版本加入了一种新的Host,即Generic Host(通用主机). 现在在2.1版本的Asp.Net Core中 ...

  2. .NET CORE QuartzJob定时任务+Windows/Linux部署

    前言 以前总结过一篇基于Quartz+Topshelf+.netcore实现定时任务Windows服务 https://www.cnblogs.com/gt1987/p/11806053.html.回 ...

  3. 使用.NET Core中创建Windows服务(一) - 使用官方推荐方式

    原文:Creating Windows Services In .NET Core – Part 1 – The "Microsoft" Way 作者:Dotnet Core Tu ...

  4. 利用Topshelf把.NET Core Generic Host管理的应用程序部署为Windows服务

    背景 2019第一篇文章. 此文源于前公司在迁移项目到.NET Core的过程中,希望使用Generic Host来管理定时任务程序时,没法部署到Windows服务的问题,而且官方也没给出解决方案,只 ...

  5. dotnet-warp && NSSM 部署 .net core 项目到 windows 服务

    如果你想将 .net core 项目以服务的形式部署到 windows 系统,希望本篇文章能够让你少走弯路 dotnet-warp 安装使用 dotnet-warp 是一个全局的.NET Core 工 ...

  6. .Net Core快速创建Windows服务

    1.新建.Net Core控制台程序,添加新建项Windows服务: NuGet引用 System.ServiceProcess.ServiceController,然后修改Progran.cs: c ...

  7. asp.net core 托管到windows服务,并用iis做反向代理

    使用NSSM把.Net Core部署至 Windows 服务   为什么部署至Windows Services 在很多情况下,很少会把.Net Core项目部署至Windows服务中,特别是Asp.n ...

  8. windows服务部署与卸载

    同事问到windows service的东东,现在整理一下,用c#如何创建一个windows service,以及如何调试.部署.卸载. 一.创建windows service 1. 打开VS2008 ...

  9. windows服务部署

    1.新建windows服务项目 2.编辑业务代码 我这里只写2句记录文本的测试代码 using System; using System.IO; using System.ServiceProcess ...

随机推荐

  1. Codeforces 447C - DZY Loves Sequences

    447C - DZY Loves Sequences 思路:dp 代码: #include<bits/stdc++.h> using namespace std; #define ll l ...

  2. C#,ArcGIS Engine开发入门教程

    C#,ArcGIS Engine开发入门教程 转自:http://blog.csdn.net/yanleigis/article/details/2233674  目录(?)[+] 五实现 一 加载A ...

  3. English trip -- Review Unit2 At school 在学校

    What do you need,Loki? I need an eraser What does he need? He needs a dictionary Where's my pencil? ...

  4. 20161208xlVBA工作表数据导入Access

    Sub InsertToDataBase() Dim DataPath As String Dim SQL As String Const DataName As String = "yun ...

  5. 20170706pptVBA演示文稿批量删除图片

    Public Sub StartRecursionFolder() Dim Pre As Presentation Dim FolderPath As String Dim pp As String ...

  6. 12月3日周日,关联:has_many(dependent::delete_all和destroy的区别) 注意看log; where等查询语句的用法。 layout传递参数❌

    错误❌: 1.belongs_to :job, dependent: :destroy //尝试删除一条resumen后,job没有同步删除?? 答:建立一对多的关系,如job和resume.应该在j ...

  7. 『Pandas』数据读取&DataFrame切片

    读取文件 numpy.loadtxt() import numpy as np dataset_filename = "affinity_dataset.txt" X = np.l ...

  8. spring--mvc添加用户及用户头像上传

    spring--mvc添加用户及用户头像上传 添加用户步骤: 1.用ajax获取省份信息 2.添加用户 代码:register.jsp <meta http-equiv="Conten ...

  9. spark 任务运行原理

    调优概述 在开发完Spark作业之后,就该为作业配置合适的资源了.Spark的资源参数,基本都可以在spark-submit命令中作为参数设置.很多Spark初学者,通常不知道该设置哪些必要的参数,以 ...

  10. sql截取字符串后面四位

    方法1: select substr('123456789',length('123456789')-6+1,6) from dual; 方法2: SELECT name, RIGHT(certid, ...