众所周知, 在 Asp.Net Core 中, 通过 UserStartup()
方法来注册 Startup 类, 在 Startup 类中可通过 ConfigureServices
方法来注册服务依赖, 通过 Configure
方法来配置请求管道, 比如注册中间件等. 这样一来:
- Program 类变得很干净, 只需要注册
WebHost
示例并进行运行即可 - 注册依赖, 还是配置管道, 统一在Startup文件配置, 不会影响业务代码. 这样如果相似的服务很多, 也比较容易剥离和抽象
ConsoleApplication 中的依赖注入
然而, 在控制台程序中, 默认的注册方式是这样的:
public static async Task Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
// 配置文件设置
})
.ConfigureServices((hostingContext, services) =>
{
// 依赖注入设置
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration);
logging.AddConsole();
});
await builder.RunConsoleAsync();
}
所有的逻辑都写在Program
中, 而且如果没有额外处理的话, 甚至全部写在一个匿名方法中. 这显得很不美观.
另外, 尤其当习惯了 Asp.Net Core 的这种依赖注入方式之后, 总觉得有些别扭, 总想着搞个类似的 Startup
类来处理依赖注入的代码, 甚至是程序入口的代码 —— 尤其是作为后台进程运行的服务的时候.
目前, .NetCore 应该没有官方的扩展方法来实现. 不过既然是扩展方法, 事实上也不是太难, 将 ConfigureServices
稍微拆一下, 得到这样一个简易版:
// using Microsoft.Extensions.Hosting
static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
// 这里做了许多默认的配置, 具体可以看注释
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((ctx, services)
=> (new Startup(ctx.Configuration)).ConfigureServices(services))
.UseConsoleLifetime();
return host;
}
这里仅仅是将 IServerCollection services
传了过去, 而如果有需要, 第一个参数也是可以传过去使用的. 而这种办法, Startup 类大概如下:
public class Startup
{
public Startup(IConfiguration configuration)
{
}
public void ConfigureServices(IServiceCollection services)
{
}
}
再进一步, 可以将这一步, 按照类似 Asp.Net Core 的样子抽象出来. 这样的话, 就可以做成类库来使用. 整个使用体验跟做 ASP 的体验就基本无二了.
首先, 新建一个扩展方法类, 实现 UseStartup
方法:
public static class HostBuilderExtensions
{
// 约定ConfigureServices方法名
private const string ConfigureServicesMethodName = "ConfigureServices";
public static IHostBuilder UseStartup<TStartup>(
this IHostBuilder hostBuilder) where TStartup : class
{
hostBuilder.ConfigureServices((ctx, serviceCollection) =>
{
// 找到以 IServiceCollection 为参数的 ConfigureServices 方法
var cfgServicesMethod = typeof(TStartup).GetMethod(
ConfigureServicesMethodName, new Type[] { typeof(IServiceCollection) });
// 找到构造方法
var hasConfigCtor = typeof(TStartup).GetConstructor(
new Type[] { typeof(IConfiguration) }) != null;
//创建Startup的实例
var startUpObj = hasConfigCtor ?
(TStartup)Activator.CreateInstance(typeof(TStartup), ctx.Configuration) :
(TStartup)Activator.CreateInstance(typeof(TStartup), null);
// 调用 ConfigureServices 方法
cfgServicesMethod?.Invoke(startUpObj, new object[] { serviceCollection });
});
return hostBuilder;
}
}
然后, 在 Program 中的CreateHostBuilder
方法代码就变成了这样:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).UseStartup<Startup>();
以上各种实现, 都可以在 Startup 中进行服务注册:
public void ConfigureServices(IServiceCollection services)
{
// 注册options, memorycache 等内置服务
services.AddOptions();
services.AddMemoryCache();
// 注册自定义生命周期服务
services.AddScope<IMyService, MyService>();
// 注册 HostService
services.AddHostedService<MyBackgroundService>();
}
关于 HostServcie , 即实现了
IHostService
或继承BackgroundService
的服务, 这些服务会在程序启动后执行其 Start/Execute 方法, 这是实现后台线程/长线程的一种方法.
参考资料: https://github.com/sonicmouse/Host.CreateDefaultBuilder.Example