Startup.cs 在自托管 .NET Core 控制台应用程序中
Startup.cs in a self-hosted .NET Core Console Application
我有一个自托管的 .NET Core 控制台应用程序.
网络显示 ASP.NET Core 的示例,但我没有网络服务器。只是一个简单的命令行应用程序。
是否可以为控制台应用程序做这样的事情?
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 核心中使用 Startup.cs,但在控制台上。
我该怎么做?
是的,是的。 ASP.NET 核心应用程序可以自托管(如您的示例)或托管在 Web 服务器(例如 IIS)中。在 .NET Core 中,所有应用都是控制台应用。
所有 .NET Core
应用程序均由精心设计的独立库和包组成,您可以在任何类型的应用程序中自由引用和使用它们。碰巧 Asp.net core
应用程序被预配置为引用大量这些库并公开一个 http 端点。
但是如果您的控制台应用程序需要依赖注入,只需引用适当的库即可。这是一个指南:https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/
所以我想到了这个解决方案,灵感来自已接受的答案:
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);
}
}
我MyService.cs
public interface IMyService
{
Task MyServiceMethod();
}
另一种方法是使用 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();
}
我知道这个线程有点旧,但我还是决定分享我的代码,因为它也实现了 Daniel 想要的结果(控制台应用程序中的 DI),但没有启动 class。 Ps.: 请注意,此解决方案对 .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
}
}
此答案基于以下标准:
I'd like to use the new Generic Host CreateDefaultBuilder
without any of the ASP.NET web stuff, in a simple console app, but also be able to squirrel away the startup logic in startup.cs
in order to configure AppConfiguration
and Services
所以我花了一上午的时间来弄清楚如何才能做到这一点。这就是我想出的...
此方法唯一需要的 nuget 包是 Microsoft.Extensions.Hosting
(在撰写本文时它的版本是 3.1.7
)。这里有一个link到nuget package。此包也是使用 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 核心应用程序中那样配置您的服务(无需安装 ASP.NET 核心虚拟主机)。
演示项目
我整理了一个 .NET Core 3.1 控制台 demo project doing all kinds of things,例如 IHostedService
实现、BackgroundService
实现、transient/singleton 服务。我还注入了 IHttpClientFactory
和 IMemoryCache
以备不时之需。
克隆该存储库并试一试。
工作原理
我创建了一个 IHostBuilder
扩展方法,它简单地实现了我们都习惯的 IHostBuilder UseStartup<TStartup>(this IHostBuilder hostBuilder)
模式。
自 CreateDefaultBuilder()
adds in all the basics 以来,没有什么可以添加的了。我们唯一关心的是获取 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;
}
}
我遇到了同样的问题,我认为这是一个很好的解决方案:
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))
}
我有一个自托管的 .NET Core 控制台应用程序.
网络显示 ASP.NET Core 的示例,但我没有网络服务器。只是一个简单的命令行应用程序。
是否可以为控制台应用程序做这样的事情?
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 核心中使用 Startup.cs,但在控制台上。
我该怎么做?
是的,是的。 ASP.NET 核心应用程序可以自托管(如您的示例)或托管在 Web 服务器(例如 IIS)中。在 .NET Core 中,所有应用都是控制台应用。
所有 .NET Core
应用程序均由精心设计的独立库和包组成,您可以在任何类型的应用程序中自由引用和使用它们。碰巧 Asp.net core
应用程序被预配置为引用大量这些库并公开一个 http 端点。
但是如果您的控制台应用程序需要依赖注入,只需引用适当的库即可。这是一个指南:https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/
所以我想到了这个解决方案,灵感来自已接受的答案:
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);
}
}
我MyService.cs
public interface IMyService
{
Task MyServiceMethod();
}
另一种方法是使用 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();
}
我知道这个线程有点旧,但我还是决定分享我的代码,因为它也实现了 Daniel 想要的结果(控制台应用程序中的 DI),但没有启动 class。 Ps.: 请注意,此解决方案对 .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
}
}
此答案基于以下标准:
I'd like to use the new Generic Host
CreateDefaultBuilder
without any of the ASP.NET web stuff, in a simple console app, but also be able to squirrel away the startup logic instartup.cs
in order to configureAppConfiguration
and Services
所以我花了一上午的时间来弄清楚如何才能做到这一点。这就是我想出的...
此方法唯一需要的 nuget 包是 Microsoft.Extensions.Hosting
(在撰写本文时它的版本是 3.1.7
)。这里有一个link到nuget package。此包也是使用 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 核心应用程序中那样配置您的服务(无需安装 ASP.NET 核心虚拟主机)。
演示项目
我整理了一个 .NET Core 3.1 控制台 demo project doing all kinds of things,例如 IHostedService
实现、BackgroundService
实现、transient/singleton 服务。我还注入了 IHttpClientFactory
和 IMemoryCache
以备不时之需。
克隆该存储库并试一试。
工作原理
我创建了一个 IHostBuilder
扩展方法,它简单地实现了我们都习惯的 IHostBuilder UseStartup<TStartup>(this IHostBuilder hostBuilder)
模式。
自 CreateDefaultBuilder()
adds in all the basics 以来,没有什么可以添加的了。我们唯一关心的是获取 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;
}
}
我遇到了同样的问题,我认为这是一个很好的解决方案:
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))
}