在一个自托管的.NET Core控制台应用程序中的Startup.cs

112

我有一个自托管的.NET Core控制台应用程序

网络上展示了一些关于ASP.NET Core的例子,但我没有Web服务器。只有一个简单的命令行应用程序。

是否可以为控制台应用程序做类似的事情?

public static void Main(string[] args)
{
    // I don't want a WebHostBuilder. Just a command line

    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();

    host.Run();
}

我想在控制台中使用类似于ASP.NET Core中的Startup.cs文件。如何实现?

你想要实现什么?一个能够提供网页的控制台应用程序吗?是的,那将是一个“自包含的asp.net core应用程序”,有一些例子可供参考,例如http://druss.co/2016/08/deploy-and-run-net-core-application-without-installed-runtime-self-contained-applications/。 - Arash Motamedi
1
@ArashMotamedi 我不想托管一个ASP.NET应用程序。我想要一个好老式的控制台应用程序,可以启动我的类库项目。我认为我会免费获得依赖注入等功能。 - Daniel
知道了。但是,是的,你的方法有点不对。请记住,所有 .net core 应用程序都由独立的库组成,你可以自由地引用这些库来进行任何类型的项目。恰好,Asp.net core 应用程序预配置了许多这些库,并公开了一个 http 端点。但是,如果你需要在控制台应用程序中进行依赖注入,只需引用适当的库即可。这里有一份指南:http://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/ - Arash Motamedi
@ArashMotamedi 非常感谢。我偶然发现了这篇文章。但是由于有关所有这些新东西的信息太多了,我非常不确定...请将其写成答案,我会标记它。 - Daniel
7个回答

127

我发现了一个灵感来自于已接受答案的解决方案的解决方法:

Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        IServiceCollection services = new ServiceCollection();
        // Startup.cs finally :)
        Startup startup = new Startup();
        startup.ConfigureServices(services);
        IServiceProvider serviceProvider = services.BuildServiceProvider();

        //configure console logging
        serviceProvider
            .GetService<ILoggerFactory>()
            .AddConsole(LogLevel.Debug);

        var logger = serviceProvider.GetService<ILoggerFactory>()
            .CreateLogger<Program>();

        logger.LogDebug("Logger is working!");

        // Get Service and call method
        var service = serviceProvider.GetService<IMyService>();
        service.MyServiceMethod();
    }
}

Startup.cs

public class Startup
{
    IConfigurationRoot Configuration { get; }

    public Startup()
    {
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json");

        Configuration = builder.Build();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddLogging();
        services.AddSingleton<IConfigurationRoot>(Configuration);
        services.AddSingleton<IMyService, MyService>();
    }
}

appsettings.json

{
    "SomeConfigItem": {
        "Token": "8201342s223u2uj328",
        "BaseUrl": "http://localhost:5000"
    }
}

MyService.cs

public class MyService : IMyService
{
    private readonly string _baseUrl;
    private readonly string _token;
    private readonly ILogger<MyService> _logger;

    public MyService(ILoggerFactory loggerFactory, IConfigurationRoot config)
    {
        var baseUrl = config["SomeConfigItem:BaseUrl"];
        var token = config["SomeConfigItem:Token"];

        _baseUrl = baseUrl;
        _token = token;
        _logger = loggerFactory.CreateLogger<MyService>();
    }

    public async Task MyServiceMethod()
    {
        _logger.LogDebug(_baseUrl);
        _logger.LogDebug(_token);
    }
}

IMyService.cs

public interface IMyService
{
    Task MyServiceMethod();
}

2
这非常有帮助。即使您的应用程序使用ASP.NET Core MVC(只要您的控制器是精简的,视图中没有应用程序逻辑),它也可以工作。我需要做的额外一件事是创建自己的Microsoft.AspNetCore.Hosting.Internal.HostingEnvironment子类,称为MyHostingEnvironment,并将其ContentRootPath = Path.GetFullPath(".")设置为; 然后在Program.cs中调用new Startup之前,执行IHostingEnvironment env = new MyHostingEnvironment(); services.AddSingleton<IHostingEnvironment,MyHostingEnvironment>();,然后将“new Startup()”更改为“new Startup(env)”。 - Demerit
非常好的答案!非常感谢您的分享。我只需要一些WebHost提供的东西,这绝对像魔法一样有效。说实话,它应该是VS中的一个模板,因为这将是最有帮助的,因为我只需要我的应用程序在迷你VPS Linux主机上运行而不需要WebHosting。 - Piotr Kula
2
也许暴露IConfiguration比暴露IConfigurationRoot更好,因为它具有更好的扩展支持。 - BuddhiP
1
在我的情况下,我需要更新startup.cs文件,添加以下内容:serviceCollection.AddLogging(builder => builder .AddConsole() .AddFilter(level => level >= LogLevel.Information) ); - Aidar Gatin
@Daniel,你是自己添加的Startup.cs吗? - Roxy'Pro

76

这个答案基于以下标准:

我想在简单的控制台应用程序中使用新的通用主机CreateDefaultBuilder,但不使用任何ASP.NET Web内容,并且还能够将启动逻辑存储在 startup.cs 中,以配置 AppConfiguration 和 Services。

因此,我花了一整个上午时间来弄清楚如何做到这一点。这是我想出来的方法...

该方法需要的唯一NuGet包是Microsoft.Extensions.Hosting(在撰写本文时,版本为3.1.7)。这是一个指向nuget包的链接。该包还必须使用CreateDefaultBuilder()才能使用,所以您可能已经添加了它。

在将扩展添加到项目后(扩展代码在答案底部),您需要将程序入口设置类似于下面的代码:

using Microsoft.Extensions.Hosting;

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

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseStartup<Startup>(); // our new method!
}

您需要添加一个Startup.cs文件,它应该如下所示:

public class Startup
{
    public IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        // Configure your services here
    }
}

然后,您可以像在典型的ASP.NET Core应用程序中一样配置您的服务(无需安装ASP.NET Core Web Hosting)。

演示项目

我创建了一个.NET Core 3.1控制台演示项目,执行各种操作,如IHostedService实现、BackgroundService实现,瞬态/单例服务。我还注入了IHttpClientFactoryIMemoryCache以提高性能。

克隆该仓库并试一试。

工作原理

我创建了一个IHostBuilder扩展方法,它简单地实现了我们都习惯使用的IHostBuilder UseStartup<TStartup>(this IHostBuilder hostBuilder)模式。

由于CreateDefaultBuilder() 添加了所有基础内容,因此没有太多需要添加的内容。我们唯一关心的是获取IConfiguration并通过ConfigureServices(IServiceCollection)创建我们的服务管道。

扩展方法源代码

/// <summary>
/// Extensions to emulate a typical "Startup.cs" pattern for <see cref="IHostBuilder"/>
/// </summary>
public static class HostBuilderExtensions
{
    private const string ConfigureServicesMethodName = "ConfigureServices";

    /// <summary>
    /// Specify the startup type to be used by the host.
    /// </summary>
    /// <typeparam name="TStartup">The type containing an optional constructor with
    /// an <see cref="IConfiguration"/> parameter. The implementation should contain a public
    /// method named ConfigureServices with <see cref="IServiceCollection"/> parameter.</typeparam>
    /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to initialize with TStartup.</param>
    /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
    public static IHostBuilder UseStartup<TStartup>(
        this IHostBuilder hostBuilder) where TStartup : class
    {
        // Invoke the ConfigureServices method on IHostBuilder...
        hostBuilder.ConfigureServices((ctx, serviceCollection) =>
        {
            // Find a method that has this signature: ConfigureServices(IServiceCollection)
            var cfgServicesMethod = typeof(TStartup).GetMethod(
                ConfigureServicesMethodName, new Type[] { typeof(IServiceCollection) });

            // Check if TStartup has a ctor that takes a IConfiguration parameter
            var hasConfigCtor = typeof(TStartup).GetConstructor(
                new Type[] { typeof(IConfiguration) }) != null;

            // create a TStartup instance based on ctor
            var startUpObj = hasConfigCtor ?
                (TStartup)Activator.CreateInstance(typeof(TStartup), ctx.Configuration) :
                (TStartup)Activator.CreateInstance(typeof(TStartup), null);

            // finally, call the ConfigureServices implemented by the TStartup object
            cfgServicesMethod?.Invoke(startUpObj, new object[] { serviceCollection });
        });

        // chain the response
        return hostBuilder;
    }
}

2
哦哦,我喜欢这个! - KyleMit
5
现在我们需要将HostBuilderExtensions的PR合并到核心代码中! - KyleMit
4
@KyleMit — 太棒了!玩这个东西真是太有趣了。我绝对不知道你可以在没有 WebHost 的情况下运行 IHost。这对未来的项目非常有用。感谢您发布这个问题。 - Andy
3
@KyleMit -- 添加了一个功能请求:https://github.com/dotnet/extensions/issues/3489 - Andy
4
作为开发者,你应该比“它不再工作”这个表述更加明确,因为通常这是客户所说的。@user1034912 -- 你能解释一下吗? - Andy
显示剩余17条评论

43

.NET Core 应用程序由独立的库和包组成,这些库和包经过精心制作,您可以在任何类型的应用程序中引用和使用它们。恰好 Asp.net core 应用程序预配置引用了许多这些库并公开了一个 http 端点。

但是如果您需要在控制台应用程序中使用依赖注入,只需引用相应的库即可。以下是指南:https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/


27

另一种方法是使用 Microsoft.Extensions.Hosting 包中的 HostBuilder

public static async Task Main(string[] args)
{
    var builder = new HostBuilder()
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.SetBasePath(Directory.GetCurrentDirectory());
            config.AddJsonFile("appsettings.json", true);
            if (args != null) config.AddCommandLine(args);
        })
        .ConfigureServices((hostingContext, services) =>
        {
            services.AddHostedService<MyHostedService>();
        })
        .ConfigureLogging((hostingContext, logging) =>
        {
            logging.AddConfiguration(hostingContext.Configuration);
            logging.AddConsole();
        });

    await builder.RunConsoleAsync();
}

@pang,我有一个关于如何使用Startup.cs中的Configure方法的问题,你有答案吗? - Manoj Prasanna

10

我知道这个帖子有点旧,但是我决定分享我的代码,因为它也可以实现 Daniel 想要的目标(在控制台应用程序中使用 DI),但不需要 Startup 类。注:请注意,此解决方案适用于 .NET Core 或 .NET Framework。

Program.cs:

public class Program
    {

        public static void Main(string[] args)
        {
            var services = new ServiceCollection();

            DependencyInjectionConfiguration.ConfigureDI(services);

            var serviceProvider = services.BuildServiceProvider();

            var receiver = serviceProvider.GetService<MyServiceInterface>();

            receiver.YourServiceMethod();
        }
    }

public static class DependencyInjectionConfiguration
    {
        public static void ConfigureDI(IServiceCollection services)
        {
            services.AddScoped<MyServiceInterface, MyService>();
            services.AddHttpClient<MyClient>(); // for example
        }
    }

5

我遇到了同样的问题,我认为这是一个不错的解决方案:

class Program
{
    static async Task 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))
}

优雅的简洁。 - JJS
新的StartUp是用来做什么的?为了简单起见,我们可以在CreateHostBuilder中配置所有服务,还是要创建一个单独的类? - Dipo
如果您的目标是在另一个文件中托管它,那么您可以简单地使用委托启动方法。public static Action<HostBuilderContext, IServiceCollection> Configure = (hostBuilderContext, services) => { } - Lee Harrison

0

是的,没错。ASP.NET Core 应用程序可以是自托管的 - 就像您的示例一样 - 或者托管在诸如 IIS 之类的 Web 服务器内部。在 .NET Core 中,所有应用程序都是控制台应用程序。


1
在像我这样的自托管场景中,如何连接Startup.cs? - Daniel
2
你的意思是我要启动一个 Web 服务器来运行我的控制台应用程序? - Daniel

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