ASP.NET Core-静态类的访问配置


82

我想要一个简单的静态类来访问Configuration对象。已经从Startup类的appsettings.json文件中读取了所有配置信息。我只需要一种简单的方法即可访问它。这可能吗?

namespace MyNamespace
{
    public static class Config
    {
        public string Username => Configuration["Username"];
        public string Password => Configuration["Password"];
    }
}

应用中的其他任何位置:

string username = Config.Username;
string password = Config.Password;

1
考虑使用依赖于服务定位器反模式的依赖倒置
Nkosi

通过配置,您是指appsettings.json还是app.config?
bruno.almeida

appsettings.json。将更新问题。
birdus

1
使用静态类可能不是单元测试的好习惯:stackoverflow.com/a/38107134/2803565
S.Serpooshan

1
为什么是静态类?您可以直接注入配置或创建单例
Neville Nazerane,

Answers:


55

基于与上述相同的原理略短的版本...

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

public static IConfiguration StaticConfig { get; private set; }

要在另一个静态类中使用:

string connString = Startup.StaticConfig.GetConnectionString("DefaultConnection");

1
在不同的类库之间共享此配置值将很棘手。想象一下,您在几个项目之间共享基础架构横切共享配置/设置吗?
少年梅耶

1
我很困惑为什么这不是公认的答案。这有风险吗?看起来简单又实用。
Chaim Eliyah

3
因为这是一种反模式。您应该能够注入事物需要的所有事物,以便可以在测试期间替换它们。当它是带有的静态属性时private setter,您将如何替换它?您必须将其public仅用于测试,这是不对的。
塞杰·萨根

2
我不认为这是一种反模式,因为IConfiguration具有绑定方法,基于此原因,您可以将其绑定到类上。我不会将其存储为IConfiguration。反复将字符串解析为它们的真实类型(例如布尔值和整数)是我在微服务领域更加关注的一种反模式。更改测试值时,无需设置“ StaticConfig”。您可以MyConfigObject.MyProperty = 5轻松地在测试设置中进行设置并继续前进。
djsoteric

11

经过大量研究,此方法(在ASPNetCore 2.2中)可用于从静态类访问appsettings.json配置,但由于某些原因,appsettings.development.json无法再正确加载,但这可能是我项目中的其他问题。reloadOnChange确实起作用。作为奖励,它还具有IHostingEnvironment和IHttpContextAccessor。在这项工作奏效的同时,我最近决定改用更多的DI方法来遵循范式转换,就像其他人提到的那样。

因此,这是在静态类中访问某些DI东西(包括配置)的多种方法之一:

AppServicesHelper.cs:

public static class AppServicesHelper
{
        static IServiceProvider services = null;

        /// <summary>
        /// Provides static access to the framework's services provider
        /// </summary>
        public static IServiceProvider Services
        {
            get { return services; }
            set
            {
                if (services != null)
                {
                    throw new Exception("Can't set once a value has already been set.");
                }
                services = value;
            }
        }

        /// <summary>
        /// Provides static access to the current HttpContext
        /// </summary>
        public static HttpContext HttpContext_Current
        {
            get
            {
                IHttpContextAccessor httpContextAccessor = services.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
                return httpContextAccessor?.HttpContext;
            }
        }

        public static IHostingEnvironment HostingEnvironment
        {
            get
            {
                return services.GetService(typeof(IHostingEnvironment)) as IHostingEnvironment;
            }
        }

        /// <summary>
        /// Configuration settings from appsetting.json.
        /// </summary>
        public static MyAppSettings Config
        {
            get
            {
                //This works to get file changes.
                var s = services.GetService(typeof(IOptionsMonitor<MyAppSettings>)) as IOptionsMonitor<MyAppSettings>;
                MyAppSettings config = s.CurrentValue;

                return config;
            }
        }
    }
}

Startup.cs:

public Startup(IHostingEnvironment env)
{
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
 }

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

        services.AddHttpContextAccessor();//For HttpContext.

        // Register the IOptions object
        services.Configure<MyAppSettings>(Configuration.GetSection(nameof(MyAppSettings)));

        //Explicitly register the settings object by delegating to the IOptions object so that it can be accessed globally via AppServicesHelper.
        services.AddSingleton(resolver => resolver.GetRequiredService<IOptionsMonitor<MyAppSettings>>().CurrentValue);
 }

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
   AppServicesHelper.Services = app.ApplicationServices;
//...
}

控制器:

public class MyController: Controller
{
   public MyController()
   {
   }

   public MyAppSettings Config => AppServicesHelper.Config;

   public async Task<IActionResult> doSomething()
   {
            testModel tm = await myService.GetModel(Config.Setting_1);
            return View(tm);
   }
}

另一个类库:

public static class MyLibraryClass
{
     public static string GetMySetting_ => AppServicesHelper.Config.Setting_1; 
     public static bool IsDev => AppServicesHelper.HostingEnvironment.IsDevelopment();
}

MyAppSettings.cs是任何映射到appsettings.json中MyAppSettings部分的类:

public class MyAppSettings
{
    public string Setting_1 {get;set;}
}

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MyAppSettings": {
      "Setting_1": "something"
   }
 }

感谢@Soenhay分享了一个好的解决方案。我也遇到了同样的问题,并按照您的方法解决了该问题。
纳丁·侯赛因·索内

我还需要类似的东西来传递静态类的存储库引用。你能建议一些@Soenhay吗?
纳丁·侯赛因·索内

@NadimHossainSonet最好将它作为一个单独的问题进行询问,以获得更完整的答复。快速总结是:您可以使用与上述相同的技术。创建用于访问存储库的服务类和接口,在ConfigureServices中注册服务,然后通过services.GetService在静态类中访问它。此外,我几乎找不到罕见的DI方法,因此您可能会重新考虑。
Soenhay '20

8

我同意mcbowes,它在docs中,但是第一个示例看起来更像您所需要的...想要:

public class Program
{
    public static IConfigurationRoot Configuration { get; set; }
    public static void Main(string[] args = null)
    {
        var builder = new ConfigurationBuilder()
             .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json");

        Configuration = builder.Build();

        Console.WriteLine($"option1 = {Configuration["option1"]}");

        // Edit:
        IServiceCollection services = new ServiceCollection();
        services.AddOptions();
        services.Configure<HelloWorldOptions>(_configuration.GetSection("HelloWorld"));
        // And so on...
    }
}

5
是的,但是如何Configuration从另一个内部静态类访问此实例,而不必每次都重新构建它?
Serj Sagan

然后,您应该使用Options模式
塔布

6
@Tubbe能否提供一个示例,说明如何工作?根据我在那篇文章中所读的内容,您仍然需要在构造函数中提供选项,而在静态类中是不可能的。
CGundlach

7

尝试避免使用静态类并使用DI

namespace MyNamespace {

  public interface IConfig {
    string Username { get; }
    string Password { get; }
  }


  public class Config : IConfig {
    public Config(IConfiguration configuration) {
      _configuration = configuration;
    }
    readonly IConfiguration _configuration;
    public string Username => _configuration["Username"];
    public string Password => _configuration["Password"];
  }


}

StartUp类中的设置DI

public class Startup {
  public void ConfigureServices(IServiceCollection services) {
    //...
    services.AddTransient<IConfig, Config>(); 
    ...
  }
}

像这样使用

  public class TestUsage {
    public TestUsage(IConfig config) {
      _config = config;
    }
    readonly IConfig _config;
    public string Username => _config.Username;
    public string Password => _config.Password;
  }

3
如果要将NET Framework迁移到NET Core,则必须修改所有使用应用程序设置的类,以便注入配置值(IOptions,IConfiguration或其他)。如果您有一个庞大的项目,则编辑类将需要大量时间和测试。:-O希望看到一种无需DI且无需修改类构造函数的简单方法
JuniorMayhé18年

4
为什么有一个静态类这么糟糕?我知道字典访问是快速的,但是这让我感到有些烦恼,if(configuration["enabled"] == "True")或者if(configuration.GetValue<int>("enabled"))当我觉得if(MyStaticClass.Enabled)每秒调用很多次的方法更快,更轻便时。
Dinerdo

^完全同意。这种方法(向下滚动到“静态实例”)仍然受到许多开发人员的青睐:weblog.west-wind.com/posts/2017/dec/12/…–
djsoteric

4

您可以使用Signleton模式从任何地方访问您的配置

    public class ConnectionStrings
    {
        private ConnectionStrings()
        {
        }
        // property with getter only will not work.
        public static ConnectionStrings Instance { get; protected set; } = new ConnectionStrings();

        public string DatabaseConnection { get; set; }
    }

在你的创业班

    public class Startup
    {
        private readonly IConfiguration configuration;

        public Startup(IConfiguration configuration)
        {
            this.configuration = configuration;
            configuration.GetSection("ConnectionStrings").Bind(ConnectionStrings.Instance);
        }

        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app)
        {
        }
    }

很好的例子。您能显示如何从appsettings.json中检索值以及如何在SomeController.cs中使用它吗
软件很有趣

就像连接字符串一样,定义另一个单例类,将其命名为AppSettings并将特定节绑定到您的单例类,最后从任何地方调用它,但请注意,在appsettings.json中进行的任何修改都不会影响您的单例类,直到重新启动应用程序为止。 。
艾哈迈德Aljaff

酷,谢谢您-我绝对喜欢走整个依赖项注入路线,但是在逐渐迁移较旧的项目的同时,这将有助于我朝这个方向发展!
dasch88 '20

2

已经说过了,但我要说一下。

我相信.Net Core希望开发人员通过Dependency Inject获得价值。这是我从研究中注意到的,但我也在推测。作为开发人员,我们需要遵循此范式转换,以便正确使用.Net Core。

选项模式是一个很好的替代静态配置。在您的情况下,它将如下所示:

appsettings.json

{
  "Username": "MyUsername",
  "Password": "Password1234"
}

SystemUser.cs

public class SystemUser 
{
  public string Username { get; set; } = "";
  public string Password { get; set; } = "";
}

启动文件

services.Configure<SystemUser>(Configuration);

并使用SystemUser类,请执行以下操作。

TestController.cs

public class TestController : Controller 
{
  private readonly SystemUser systemUser;

  public TestController(IOptionsMonitor<SystemUser> systemUserOptions)
  {
    this.systemUser = systemUserOptions.CurrentValue;
  }

  public void SomeMethod() 
  {
    var username = this.systemUser.Username; // "MyUsername"
    var password = this.systemUser.Password; // "Password1234"
  }
}

即使我们没有使用静态类,我也认为这是适合您需求的最佳选择。否则,您可能必须在Startup类内使用静态属性,这是一个令人恐惧的解决方案imo。


1
静态类不是面向对象编程的最佳实践,它有点繁重。您的意思是说有状态的静态类。但是,即使那是相当沉重的手。这种形式的DI可以说是一种组件设计概念,而不是OOP。调用者必须了解内部依赖项并不是真正符合OOP理想的精神。权衡开发人员喜欢它的是精细的单元测试。我们中的许多人都具有极其贫乏的OOP设计,与OOP相比,它更需要通过状态传递命令性功能。
约书亚·恩菲尔德

如果我依赖诸如大众运输这样的库,并且在配置Bus时,我将如何使用选项模式?我想使用选项模式,而不是从appsettings中访问硬编码字符串?
kuldeep

1

我个人很喜欢此链接中使用的方法

本质上,它只是将静态字段添加到您的options类。

 public class WeblogConfiguration
 {
    public static WeblogConfiguration Current;

    public WeblogConfiguration()
    {
        Current = this;
    }
} 

然后可以在任何静态类中执行以下操作:

WeblogConfiguration.Current

简单而直接


0

如果您使用环境变量作为配置,则可以直接访问环境变量,而不是通过配置对象。

using System;

namespace My.Example
{
    public static class GetPaths
    {
        private static readonly string MyPATH = 
            Environment.GetEnvironmentVariable("PATH");

        private static readonly string MySpecialPath =
            Environment.GetEnvironmentVariable("PREFIX_SpecialPath");
        ...
    }
}

0

我认为您可以使用扩展功能,像这样

public static string ConfigToSomeThing(this IConfiguration config, int value)
        {
            return config[value.ToString()] ?? "";
        }

然后在任何地方,只需注入IConfiguration并使用扩展方法

_systemConfiguration.ConfigToSomeThing(123);

0

我只是在下面的课程中创建的:


    /// <summary>
    /// 
    /// </summary>
    public static class ConfigurationManager
    {
        /// <summary>
        /// 
        /// </summary>
        public sealed class ConfigurationManagerAppSettings
        {
            /// <summary>
            /// 
            /// </summary>
            internal ConfigurationManagerAppSettings() { }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="key"></param>
            /// <returns></returns>
            public string this[string key] => (TheConfiguration ?? throw new Exception("Set ConfigurationManager.TheConfiguration in Startup.cs")).GetSection($"AppSettings:{key}").Value;
        }

        /// <summary>
        /// 
        /// </summary>
        public static IConfiguration? TheConfiguration { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public static readonly ConfigurationManagerAppSettings AppSettings = new ConfigurationManagerAppSettings();
    }

及以下代码:

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

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services) {
            ConfigurationManager.TheConfiguration = Configuration;


-1

这是一种从NET.Core页面获取配置值的方法,而不必静态引用这些值,但是仍然可以将其传递给从非静态类调用的其他静态函数。

在您的非静态类的顶部,添加以下内容:

private readonly IConfiguration _configuration;

然后在构造函数中引入现有配置作为该函数的输入: IConfiguration configuration

然后将配置分配给构造函数中的只读变量: _configuration = configuration;

这是一个看起来像的例子:

public class IndexModel : PageModel
{
    private readonly IConfiguration _configuration;

    public IndexModel(IConfiguration configuration)
    {
        _configuration = configuration;
    }
}

之后,您可以通过引用_configuration引用该类中任何函数的配置,甚至可以将其传递给您从其他类调用的其他静态函数:

public async Task OnGetAsync()
{
    AnotherClass.SomeFunction(_configuration);
}

然后,在调用的静态类中,我可以使用配置值:

public static string SomeFunction(IConfiguration configuration)
{
    string SomeValue = configuration.GetSection("SomeSectionOfConfig")["SomeValue"];
}

我有一个类,该类调用一些用于查看和修改数据的存储过程,并使用此方法从appsettings.json传递参数值。


问题是关于如何“从静态类访问配置”的,所以我认为在这里告诉我们如何“无需静态引用这些配置”如何获取配置不是很有用
Colin,

-4

考虑使用此处的ASP.NET Core配置说明。

您可以创建一个类来存储您的配置设置,然后访问值,如下所示:

_config.UserName

在启动-ConfigureServices中:

services.Configure<Config>(Configuration.GetSections("General"));

然后只需将您的对象注入所需的位置即可:

IOptions<Config> config

您如何将对象注入静态类?
mavi

您将配置注入实例,而不是使用静态类。我没有建议尝试将配置注入静态。
mcbowes

3
这个问题专门针对在static class
Serj Sagan

2
@SerjSagan即使问题专门询问静态类,他们也可能无法理解从.NET到.NET Core的模式转变。因此,静态类不是最佳答案,因此选项模式应该是有效答案。
christo8989

-5

IConfiguration在项目内的任何位置均可注入。但是在静态类的情况下,我正在使用的选项可能只是方法而已... var Configuration = new ConfigurationBuilder() .AddUserSecrets<Startup>() .Build(); 而且,您可以添加必需的部分,例如在上面的代码块中,我添加了'UserSecrets'。


您不能将DI转换为静态,所以这是错误的答案。
Billb

嗨,Billb,这是我上面提到的。我们不能将DI转换为静态类,这就是上面代码块的原因。没有发生DI但允许访问配置的地方,例如代码块中的“ UserSecrets”
irejwanul

好吧,您不能将DI转换为静态类,但可以将DI转换为包含静态私有成员的类。添加到启动服务可能是最好的方法,但是它将您的项目耦合在一起,使重用更加困难。我倾向于在服务类中包括静态变量,这些静态变量可以被完全封装。
iGanja
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.