如何使用通用主机构建器运行.NET Core控制台应用程序

58

我正在尝试使用HostBuilder模式来运行控制台应用程序(而不是Windows服务)。意图是保持流程与WebApi尽可能相似,以保持开发实践的一致性。我已经看到了使用HostedService或BackGroundService的示例,其中它们想要将其作为Windows服务运行。但是,如果我想要运行一个简单的控制台应用程序,我该在哪里指定我的入口类和方法?从hostbuilder.Build()中,我可以看到Run()和RunAsync()方法。但我无法弄清它将执行什么?

我已经看到了其他例子,你可以创建servicecollection,然后使用serviceprovider.GetService().SomeMethod()来启动进程。但这有点偏离我们想要做的事情。所以请建议如何指定启动过程。我们正在使用3.1 .Net Core。

class Program
{
    static async void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        await host.RunAsync();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostBuilderContext, serviceCollection) => new Startup(hostBuilderContext.Configuration).ConfigureServices(serviceCollection))
        .UseSerilog()
        ; 

}

3
使用 dotnet new worker 命令创建一个后台应用程序。它采用主机构建器模式,并可以直接运行。 - abdusco
谢谢您的建议。我尝试了一下,它使用了HostedService。那么,即使我不将控制台应用程序作为Windows服务运行,使用HostedService是唯一的方法吗? - askids
不,您不必使用托管服务。您可以使用工作器项目模板作为设置 DI 容器的参考。一旦您拥有了主机,就可以使用它来解析服务并在 Main 方法中调用它们,无需指定入口点。或者直接使用托管服务,它不需要成为 Windows/Systemd 服务即可运行。 - abdusco
这个回答解决了你的问题吗?在自托管的.NET Core控制台应用程序中的Startup.cs - Michael Freidgeim
2个回答

90

编辑:.NET 6的更新如下↓
.NET 7并没有太多变化。


我建议你首先使用默认的worker模板。它预装了必要的软件包。如果你已经有一个项目,只需安装Microsoft.Extensions.Hosting软件包即可。
dotnet new worker -n MyCli

然后打开 Program.cs 并构建主机。如果您不想使用托管服务路线,请删除 Worker 托管服务。

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                // remove the hosted service
                // services.AddHostedService<Worker>();

                // register your services here.
            });
}

构建你的逻辑:

internal class MyService
{
    // you can also inject other services
    private ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void DoSomething()
    {
        _logger.LogInformation("Doing something");
    }
}

然后在.ConfigureServices方法中注册该类

Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.AddTransient<MyService>();
    });

现在你可以在Main方法中解决并调用它:
public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    var myService = host.Services.GetRequiredService<MyService>();
    myService.DoSomething();
}

.NET 6 更新

在 .NET 6 中,样板代码显著减少。我们可以将我们的 Program.cs 重写为:

using Microsoft.Extensions.Hosting; // Requires NuGet package

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services => { services.AddTransient<MyService>(); })
    .Build();

var my = host.Services.GetRequiredService<MyService>();
await my.ExecuteAsync();

class MyService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public async Task ExecuteAsync(CancellationToken stoppingToken = default)
    {
        _logger.LogInformation("Doing something");
    }
}

3
注意:需要使用“Microsoft.Extensions.Hosting”。可能需要一个NuGet包。 - Heinrich Ulbricht
1
@HeinrichUlbricht 是的。但 worker 模板已经预安装在该软件包中。 - abdusco
2
有人可能需要添加配置-请参阅如何在ASP .NET Core中初始化主机之前读取配置设置? - Michael Freidgeim
您可以始终解决作用域服务,它们只是在作用域之外变成单例。https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#scoped-service-as-singleton。或者,如果您不匹配生命周期(将作用域服务注入到单例中),则会出现俘获依赖项的情况。 - abdusco
@JohnLong 如果MyService是被测试的系统,你不应该模拟它本身。你应该模拟或伪造它的依赖项。 - Marc L.
显示剩余2条评论

9

基本上:

  • 实例化主机构建器并配置服务等内容。

  • 创建一个包含程序方法的类,并将该类注册为服务。

  • 构建主机,创建作用域,获取类的实例,调用自己的方法。

我的程序是在ProgramAsync类中的方法MainAsync

    class Program
    {
        static void Main(string[] args)
        {
            using (IHost host = Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration(cfg =>
                {
                    cfg.AddJsonFile("appsettings.json");
                })
                .ConfigureServices((context, services) =>
                {
                    services.AddDbContext<EquitiesDbContext>(options => { options.UseSqlServer(context.Configuration.GetConnectionString("Equities")); });
                    services.AddScoped<ProgramAsync>();
                })
                .ConfigureLogging((context, cfg) =>
                {
                    cfg.ClearProviders();
                    cfg.AddConfiguration(context.Configuration.GetSection("Logging"));
                    cfg.AddConsole();
                })
                .Build()
                )
            {
                using( IServiceScope scope = host.Services.CreateScope() )
                {
                    ProgramAsync p = scope.ServiceProvider.GetRequiredService<ProgramAsync>();
                    p.MainAsync().Wait();
                }
            }
            Console.WriteLine("Done.");
        }
    }


4
声明 Program.Main(..)async Task Main(..) 是合法的,并允许您使用 await p.MainAsync(); - Marc L.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接