如何在.NET Core控制台应用程序中设置依赖注入容器?

5

我创建了一个新的.NET Core控制台应用,并安装了以下包:

  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.EnvironmentVariables
  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Options
  • Microsoft.Extensions.Options.ConfigurationExtensions

我为配置创建了一个appsettings.json文件。

{
  "app": {
    "foo": "bar" 
  }
}

我想将这些值映射到一个类

internal class AppOptions
{
    public string Foo { get; set; }
}

我还想在配置期间验证选项,因此我添加了一个验证类

internal class AppOptionsValidator : IValidateOptions<AppOptions>
{
    public ValidateOptionsResult Validate(string name, AppOptions options)
    {
        IList<string> validationFailures = new List<string>();

        if (string.IsNullOrEmpty(options.Foo))
            validationFailures.Add("Foo is required.");

        return validationFailures.Any()
        ? ValidateOptionsResult.Fail(validationFailures)
        : ValidateOptionsResult.Success;
    }
}

我想搭建DI容器并创建测试场景。
    static void Main(string[] args)
    {
        ConfigureServices();

        Console.ReadLine();
    }

    private static void ConfigureServices()
    {
        IServiceCollection serviceCollection = new ServiceCollection();
        IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();

        // Setup configuration service

        IConfiguration configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", true, true)
            .AddEnvironmentVariables()
            .Build();

        serviceCollection.AddSingleton(configuration);

        // Setup options

        IConfiguration configurationFromDI = serviceProvider.GetService<IConfiguration>(); // This is just for testing purposes

        IConfigurationSection myConfigurationSection = configurationFromDI.GetSection("app");

        serviceCollection.AddSingleton<IValidateOptions<AppOptions>, AppOptionsValidator>();
        serviceCollection.Configure<AppOptions>(myConfigurationSection);

        // Try to read the current options

        IOptions<AppOptions> appOptions = serviceProvider.GetService<IOptions<AppOptions>>();

        Console.WriteLine(appOptions.Value.Foo);
    }

遗憾的是,变量configurationFromDI为null。因此,变量configuration未被添加到DI容器中。

如何正确设置控制台应用程序的依赖注入?


在添加服务之后加入 serviceCollection.BuildServiceProvider(); - TheGeneral
4
不需要写所有这些代码。由于您已经使用了这么多扩展,最好(而且更容易)使用HostBuilder,就像ASP.NET Core应用程序一样,并使用其ConfigureServicesConfigureAppConfiguration方法。 - Panagiotis Kanavos
这个回答解决了你的问题吗?在自托管的.NET Core控制台应用程序中的Startup.cs - Michael Freidgeim
2个回答

6

BuildServiceProvider 方法的调用应该在所有服务注册之后进行。

但是,没有必要编写所有这些代码。由于您已经使用了很多扩展,因此最好(也更容易)使用 通用主机,就像 ASP.NET Core 应用程序一样,并使用其 ConfigureServicesConfigureAppConfiguration 方法:

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

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureHostConfiguration(configuration =>
            {
                configuration....;
            });
            .ConfigureServices((hostContext, services) =>
            {
                var myConfigurationSection = configuration.GetSection("app");

                services.AddSingleton<IValidateOptions<AppOptions>, AppOptionsValidator>();
                services.Configure<AppOptions>(myConfigurationSection);

            });
}

通过 HostBuilderContext.Configuration 属性可配置。

CreateDefaultBuilder 设置当前文件夹,配置环境变量以及使用 appsettings.json 文件,因此无需显式添加。

Appsettings.json 的复制设置

在 Web 应用程序模板中,appsettings.json 文件会自动添加,并将 Build Action 属性设置为 Content ,并将 Copy to Output 操作设置为 如果较新则复制

控制台应用程序中没有这些文件。手动添加新的 appsettings.json 文件时,其 Build ActionNoneCopyNever 。当调试应用程序时,当前目录为 bin\Debug 。使用默认设置,appsettings.json 将不会被复制到 bin / Debug 中。

必须将 Build Action 更改为 Content ,并将 Copy 设置为 如果较新则复制始终复制


谢谢! :) 我尝试了你的代码示例 https://pastebin.com/bDvh9vuK,但不幸的是它无法从appsettings.json文件中读取。我总是得到选项验证失败的错误。你知道缺少什么吗?`ConfigureHostConfiguration`需要什么? - user13755987
没有什么缺少的。我在十几个CLI工具中使用它,所以它不仅仅是从文档中复制的代码。 appsettings.json 是否被复制到输出文件夹中?当您添加新的JSON文件时,其“Build Action”设置为“None”,而“Copy”设置为“Never”。当您调试应用程序时,当前目录是bin/Debug,因此您的应用程序在那里看不到任何json文件。您需要将“Copy”设置为“Always”或“Copy if newer”。您需要将“Build Action”设置为“Content”,以便在发布或打包期间包含该文件。 - Panagiotis Kanavos
啊,我不知道那个 :) 既然在 ConfigureAppConfigurationConfigureHostConfiguration 中没有任何事情要做,我就把它们删掉了。代码似乎仍然可以正常工作。你介意看一下更新后的版本吗?https://pastebin.com/VYsfSbEe - user13755987

1

控制台项目中的DI

您可以在任何可执行的.NET Core应用程序中设置DI,并通过扩展IHostBuilder在Startup类中配置服务(就像web项目一样):

public static class HostBuilderExtensions
{
    private const string ConfigureServicesMethodName = "ConfigureServices";

    public static IHostBuilder UseStartup<TStartup>(
        this IHostBuilder hostBuilder) where TStartup : class
    {
        hostBuilder.ConfigureServices((ctx, serviceCollection) =>
        {
            var cfgServicesMethod = typeof(TStartup).GetMethod(
                ConfigureServicesMethodName, new Type[] { typeof(IServiceCollection) });

            var hasConfigCtor = typeof(TStartup).GetConstructor(
                new Type[] { typeof(IConfiguration) }) != null;

            var startUpObj = hasConfigCtor ?
                (TStartup)Activator.CreateInstance(typeof(TStartup), ctx.Configuration) :
                (TStartup)Activator.CreateInstance(typeof(TStartup), null);

            cfgServicesMethod?.Invoke(startUpObj, new object[] { serviceCollection });
        });

        return hostBuilder;
    }
}

现在,您有一个扩展方法UseStartup<>(),可以在Program.cs中调用(最后一行):
public class Program
{
    public static void Main(string[] args)
    {
        // for console app
        CreateHostBuilder(args).Build().Run(); 
      
        // For winforms app (commented)
        // Application.SetHighDpiMode(HighDpiMode.SystemAware);
        // Application.EnableVisualStyles();
        // Application.SetCompatibleTextRenderingDefault(false);
        // var host = CreateHostBuilder(args).Build();
        // Application.Run(host.Services.GetRequiredService<MainForm>());
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            
            .ConfigureServices((hostContext, services) =>
            {
                config.AddJsonFile("appsettings.json", optional: false);
                config.AddEnvironmentVariables();
                // any other configurations
            })
            .UseStartup<MyStartup>();
}

最后,添加你自己的Startup类(这里是MyStartup.cs),并从构造函数中注入IConfiguration
public class MyStartup
{
    public IConfiguration Configuration { get; }

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

    public void ConfigureServices(IServiceCollection services)
    {
        // services.AddBlahBlahBlah()
    }
}

P.S:你得到了null,因为你在注册 IConfiguration 之前调用了 .BuildServiceProvider
映射 appsettings.json:
要将 appsettings.json 的值映射到类型上,请定义一个空接口,比如 IConfigSection(只是为了泛型约束的原因):
public interface IConfigSection
{
}

然后,按照以下方式扩展IConfiguration接口:

public static class ConfigurationExtensions
{
    public static TConfig GetConfigSection<TConfig>(this IConfiguration configuration) where TConfig : IConfigSection, new()
    {
        var instance = new TConfig();
        var typeName = typeof(TConfig).Name;
        configuration.GetSection(typeName).Bind(instance);

        return instance;
    }
}

扩展方法 GetConfigSection<>() 会为您进行映射。只需定义实现 IConfigSectionconfig类

public class AppConfigSection : IConfigSection
{
    public bool IsLocal { get; set; }
    public bool UseSqliteForLocal { get; set; }
    public bool UseSqliteForServer { get; set; }
}

以下是您的appsettings.json应该如何看起来(类名和属性名应该匹配):
{

.......

"AppConfigSection": {
    "IsLocal": false,
    "UseSqliteForServer": false,
    "UseSqliteForLocal": false
  },

.....

}

最后,按照以下方式检索您的设置并将其映射到您的ConfigSections中:
// configuration is the injected IConfiguration
AppConfigSection appConfig = configuration.GetConfigSection<AppConfigSection>();

我认为你在 Program.cs 文件中的以下部分: .ConfigureServices((hostContext, services) => 应该改为: .ConfigureHostConfiguration((config) => - Robb Sadler
@RobbSadler 这取决于你使用的框架,是 .net-core 还是 dotnet。 - Efe

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