.NET Core把控制台应用做成跨平台服务

参考资料
https://docs.microsoft.com/zh-cn/dotnet/core/extensions/generic-host

本篇博客会介绍如何把一个控制台应用做成跨平台的服务,并介绍如何在该服务中读取配置文件。

故事的起因

今天早上在关注的曾经满满干货,现在却充满了培训班网课广告和理财割韭菜广告的若干个.NET Core技术公众号中翻出了一篇文章《把 Console 部署成 Windows 服务,四种方式总有一款适合你!》,这篇文章来自一个不错的公众号“一线码农聊技术”。文章中第三种方式“使用微软新内置的 Hosting”让我感觉非常清真,这种方式也没有用第三方库,完全依靠框架解决。

最近公司有一个项目,需要从数据中台中拉取数据到我们的数据库中。我同事负责这个项目。他采用的方式是用一个控制台应用,把拉取数据的方法在Program类的Main方法中调用。我隐隐感觉这种方式不太清真。当时我也不了解什么是Windows服务,什么是Linux服务,所以也没有帮他想出更清真的方式。今天看完上面这篇文章之后,我认为可以把这个控制台做成一个系统服务来实现这个功能。

但由于我就职的公司是一家技术比较朴素的外包公司,公司大概率是不会给我们时间来重构项目。但既然我了解到了实现这个功能更加清真的方式,我就要尝试一下。而且我喜欢参照实际开发中的障碍来学习。比如上面我提到的这个项目,公司给我同事的测试环境是Windows Server,但正式环境是CentOS。虽然不知道是怎样想的,但这意味着如果后面我有机会在公司项目中用上这项技术,我无法完全参考上面那篇文章的内容,而是需要把这个服务做成一个跨平台服务。

Host泛型主机

上面提到的这篇文章给我的提示就是Hosting。因为VS太好用了,我在今年上半年开始学习开发以来,一直依赖VS2019自动生成的项目模板,所以几乎没有了解过Program类中的Main方法和Host这些东西,我完全不清楚它们是什么。看完上面这篇文章,我开始在微软的文档中搜索Host,发现了这篇文章:https://docs.microsoft.com/zh-cn/dotnet/core/extensions/generic-host

可能是因为文章内容升级为了与.NET 5相匹配的内容,所以文章暂时没有汉化,也没有机翻。我们可以强行看英文。

Generic Host在其它的文档中有时被译为泛型主机,有时被译为通用主机。Generic Host可以被用于其他类型的.NET应用,例如控制台应用。一个host是封装了应用程序资源的对象。例如:

  • Dependency injection (DI)
  • Logging
  • Configuration
  • IHostedService implementations

看完这一部分,我了解到原来依赖注入,日志,配置,Service这些东西,都是创建一个host的时候来完成的。之前一直听说的“ASP.NET Core Web应用也是一个控制台应用”,今天才仿佛有点理解这句话。

准备host与配置文件

创建一个控制台应用,命名为ServiceDemo。

安装这两个NuGet包:

.NET Core把控制台应用做成跨平台服务

由于我还打算在这个跨平台服务中使用配置框架来读取配置,所以我在项目中添加两个json文件:

.NET Core把控制台应用做成跨平台服务

appsettings.json的内容为:

{
  "MyConfiguration": {
    "Key1": "Value1",
    "Key2": "Value2"
  }
}

appsettings.Development.json的内容为:

{
  "MyConfiguration": {
    "Key1": "Dev Value1",
    "Key2": "Dev Value2"
  }
}

我们准备一个模型来承载我们的配置。新建一个MyConfiguration类:

.NET Core把控制台应用做成跨平台服务

可以看到我们把两个属性的set访问器都置为private,不允许后续再手动给它们赋值。同时给两个属性都赋了默认值。

然后到Program类中配置我们的host:

.NET Core把控制台应用做成跨平台服务

可以看到我们在下面定义了一个CreateHostBuilder方法,就跟ASP.NET Web应用自己的模板一样。当然这个方法也可以不叫这个名字,记得在Main方法中调用这个方法即可。

这个方法的返回类型是IHostBuilder。首先它调用Host类的方法CreateDefaultBuilder,创建并且配置了一个HostBuilder对象。这个方法具体做了什么,可以参考:https://docs.microsoft.com/zh-cn/dotnet/core/extensions/generic-host#default-builder-settings

然后我们紧接着调用ConfigureAppConfiguration方法,来为我们这个应用进行配置。这个方法的参数是一个Action委托,委托的两个参数分别是HostBuilderContext,也就是HostBuilder上下文,还有IConfigurationBuilder:

.NET Core把控制台应用做成跨平台服务

我们在这个方法中先清空了所有的配置文件源(非必须,视情况而定),然后添加了我们的两个配置文件。配置文件均配置为可选,并且支持热重载。

ASP.NET Core 已经帮我们加载appsettings.json和appsettings.{Environment}.json这些配置文件了。但有时候我们可能想自己提供配置文件,比如叫emailsettings.json和emailsettings.{Environment}.json,就可以用上面代码中这种方式。

同时我们看到IHostBuilder还有一个ConfigureService方法,它的作用就跟Startup的ConfigureService是一样的,一会我们会用到它来注册我们的服务。

然后继续看Program类,它的Main方法调用我们下面定义的CreateHostBuilder方法,返回值是一个HostBuilder。紧接着调用IHostBuilder的Build方法,创建了一个IHost实例。然后调用Run或者RunAsync方法,运行起这个host实例。

创建服务

我们创建一个服务类,取名MyService,继承抽象类BackgroundService,必须实现抽象类的抽象方法ExecuteAsync。BackgroundService这个抽象类实现了IHostedService接口,我们顺手实现IHostedService接口的StartAsync和StopAsync方法,当然也可以不实现。

看一下BackgroundService:

.NET Core把控制台应用做成跨平台服务

目前我们的服务:

.NET Core把控制台应用做成跨平台服务

我们实现一个构造函数,通过构造函数注入IConfiguration,获取到我们的配置:

.NET Core把控制台应用做成跨平台服务

我们先实现一下StartAsync和StopAsync:

.NET Core把控制台应用做成跨平台服务

这样在这个服务运行前和停止后,都会输出这两条语句。

然后实现ExecuteAsync方法:

.NET Core把控制台应用做成跨平台服务

内容解释已经在注释里了。

现在当然还不能运行,需要注册一下这个服务,注册为HostedService。BackgroundService就是实现的IHostedService接口。

注册服务

如何注册这个服务?如果是Web应用,肯定去Startup中的ConfigureServices方法中注册。现在我们虽然没有Startup,但前面提到过了,可以直接调用ConfigureServices方法。回到Program类中:

.NET Core把控制台应用做成跨平台服务

现在直接运行项目,就会发现这个服务可以被直接执行了。

运行项目

直接运行的话,会发现输出的Key1和Key2是我们的默认值V1和V2,因为我们编译完之后的文件里并没有我们自己的appsettings.json文件。

需要右键appsettings.json和appsettings.Development.json,选择属性,把“复制到输出目录”这一项选为“始终复制”:

.NET Core把控制台应用做成跨平台服务

这样再编译时,这两个文件就会被复制到生成目录中。此时再运行:

.NET Core把控制台应用做成跨平台服务

结果还是不太对,因为在注册配置文件时,也就是AddJsonFile时,后添加的应该覆盖前面添加的:

.NET Core把控制台应用做成跨平台服务

也就是说我们先添加的appsettings.json,又添加的appsettings.Development.json,但读取到的却是先添加的appsettings.json中的值。

如何解决?右键项目属性,添加一下环境变量:

.NET Core把控制台应用做成跨平台服务

这时候会有一个launchSettings文件,在Properties目录中:

.NET Core把控制台应用做成跨平台服务

我们的环境就是Development了,此时再运行:

.NET Core把控制台应用做成跨平台服务

验证跨平台性

为了验证服务的跨平台性,我们可以使用windwos的linux子系统,又叫WSL。我已经提前安装好了。进入子系统,cd到项目目录下。注意,本机的C盘在子系统中就是/mnt/c:

.NET Core把控制台应用做成跨平台服务

dotnet run后发现,运行是成功的。说明该服务在Linux内核的系统中也是可以运行的。

总结

在实现这个Demo时,我也遇到了一些阻力。首先是.NET 5准备发布以后,很多文档都针对.NET 5的新特性进行了重写或者修改,基本都是纯英文,还没有汉化。虽然汉化之后读起来也是味同嚼蜡。其次,文档点到即止,对于高手来说是点到了,对于新手来说还完全没有点到。但跟其它语言和框架相比,.NET的文档已经相当完善了。感谢为这些付出的人们,感谢为社区付出的开发者们,感谢微软。

目前我对.NET国内社区的认识是:.NET Core的学习资料,无论免费的还是付费的,从0基础到入门的有很多,从进阶到高手的也有很多,但从入门到进阶的基本没有。我推断从入门到进阶这个过程是要在工作中慢慢渡过的。但我现在的公司对待技术的马马虎虎态度显然不能让我实现这个过渡,各位大佬们如果有在上海的,能在这个寒冷的冬天,同时也是就业的寒冬,给小弟提供一个工作机会,小弟感激不尽!!

.NET Core把控制台应用做成跨平台服务

上一篇:使用 Iceberg on Kubernetes 打造新一代云原生数据湖


下一篇:关于php伪协议