ASP.NET Core——从静态类访问配置

Posted

技术标签:

【中文标题】ASP.NET Core——从静态类访问配置【英文标题】:ASP.NET Core—access Configuration from static class 【发布时间】:2018-02-03 18:04:04 【问题描述】:

我想要一个访问配置对象的简单静态类。所有配置信息都已从 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;

【问题讨论】:

考虑使用依赖倒置作为服务定位器反模式 配置是指 appsettings.json 还是 app.config? appsettings.json。将更新问题。 使用静态类可能是单元测试的坏习惯:***.com/a/38107134/2803565 为什么是静态类?您可以直接注入配置或创建单例 【参考方案1】:

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

public Startup(IConfiguration configuration)

    Configuration = configuration;
    StaticConfig = configuration;


public static IConfiguration StaticConfig  get; private set; 

在另一个静态类中使用:

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

【讨论】:

在不同的类库之间共享这个配置值会很棘手。想象一下,您在多个项目之间有基础架构横切共享配置/设置? 我很困惑为什么这不是公认的答案。这有什么风险吗?看起来简单实用。 因为它是一种反模式。您应该能够注入事物所需的所有东西,以便在测试期间替换它们。当它是带有private setter 的静态属性时,您将如何替换它?您必须将其设为 public 只是为了进行测试,这是不对的。 我不认为这是一种反模式,因为 IConfiguration 具有绑定方法,允许您将其绑定到一个类,出于这个原因等等。不过,我不会将其存储为 IConfiguration 。反复将字符串解析为它们的真实类型(如布尔值和整数)是我在微服务世界中更关心的一种反模式。当您更改测试值时,您不必设置“StaticConfig”。您可以在测试设置中轻松设置 MyConfigObject.MyProperty = 5 并继续前进。 我相信只要您不在解决方案中使用单独的项目,此解决方案就可以工作。通常,我在解决方案控制器项目、服务项目和repo项目中至少创建3个项目,并将服务项目的引用添加到控制器项目中以使用控制层中的服务。基于此,我无法将控制器项目的引用添加到服务项目以使用 Startup.StaticConfig in-service 项目。它产生循环依赖【参考方案2】:

经过大量研究,这适用于(在 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 方法似乎很少见,因此您可能会重新审视它。【参考方案3】:

我同意 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...
    

【讨论】:

是的,但是如何从另一个内部静态类访问Configuration 的这个实例,而不必每次都重新构建它? 那么你应该使用Options pattern。 @Tubbe 你能提供一个例子来说明它是如何工作的吗?根据我在那篇文章中读到的内容,您仍然需要在构造函数中提供选项,而这在静态类中是不可能的。【参考方案4】:

尽量避免使用静态类并使用 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;
  

【讨论】:

如果要将 NET Framework 迁移到 NET Core,则必须修改所有使用应用程序设置的类才能注入配置值(IOptions、IConfiguration 或其他)。如果您有一个庞大的项目,编辑类将花费大量时间和测试。 :-O 希望看到一种更简单的方法,无需 DI 和修改类构造函数 为什么有一个静态类这么糟糕?我知道字典访问速度很快,但是当我觉得 if(MyStaticClass.Enabled) 对于每秒调用多次的方法来说更快更轻时,我不得不使用 if(configuration["enabled"] == "True")if(configuration.GetValue&lt;int&gt;("enabled")) 让我有点恼火。 ^ 完全同意。这种方法(向下滚动到“静态实例”)仍然受到许多开发人员的青睐:weblog.west-wind.com/posts/2017/dec/12/… 尝试AppDomain.CurrentDomain.AssemblyResolve静态事件,通过设置文件路径定义查找dll路径【参考方案5】:

您可以使用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 中的任何修改都不会影响您的单例上课,直到您重新启动应用程序... 很酷,谢谢——我当然更喜欢使用整个依赖注入路线,但是在逐步迁移旧项目时,这将帮助我朝着这个方向前进! 这个问题是指静态类,而不是非静态类。【参考方案6】:
    在服务层创建ConfigurationHelper静态类,这样就可以在没有循环依赖的情况下在其他层使用。
public static class ConfigurationHelper
    
        public static IConfiguration config;
        public static void Initialize(IConfiguration Configuration)
        
            config = Configuration;
        
    
    在 Startup 类的 ConfigureServices 方法中初始化 ConfigurationHelper。
ConfigurationHelper.Initialize(Configuration);
    在任何你想要的地方使用它,包括你的静态类
e.g: ConfigurationHelper.config.GetSection("AWS:Accesskey").Value;

【讨论】:

这是 ASP.NET Core 3.1+ 的完美解决方案。我在 .NET 5 上对其进行了测试,效果非常好。感谢分享@pedram 你在 program.cs ConfigurationHelper.Initialize(builder.Configuration) 中节省了我的时间 .net 6;【参考方案7】:

这已经说过了,但我要说出来。

我相信 .Net Core 希望开发人员通过依赖注入获得价值。这是我从研究中注意到的,但我也在推测。作为开发人员,我们需要遵循这种范式转变才能很好地使用 .Net Core。

Options Pattern 是静态配置的不错替代品。在你的情况下,它看起来像这样:

appsettings.json


  "Username": "MyUsername",
  "Password": "Password1234"

SystemUser.cs

public class SystemUser 

  public string Username  get; set;  = "";
  public string Password  get; set;  = "";

Startup.cs

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 类中使用静态属性,这是一个可怕的解决方案。

【讨论】:

静态类不是面向对象编程的最佳实践有点笨拙。你的意思是说 stateful 静态类。但即便如此,也是相当沉重的。这种形式的 DI 可以说更像是一种组件设计概念,而不是 OOP 概念。调用者必须了解内部依赖关系并不真正符合 OOP 理想的精神。开发人员喜欢它的权衡是细粒度的单元测试。我们中的许多人都有极其贫乏的 OOP 设计,它比 OOP 更接近于传递状态的命令式函数。 如果我依赖于公共交通等库并且在配置 Bus 时,我想使用选项模式而不是从 appsettings 访问硬编码字符串,我将如何使用选项模式? 如何解决 AppDomain.CurrentDomain.AssemblyResolve 静态事件【参考方案8】:

我个人喜欢这个link使用的方法

本质上它只是向您的选项类添加一个静态字段。

 public class WeblogConfiguration
 
    public static WeblogConfiguration Current;

    public WeblogConfiguration()
    
        Current = this;
    
 

然后在任何静态类中你都可以这样做:

WeblogConfiguration.Current

简单直接

【讨论】:

【参考方案9】:

If you are using environment variables as your configuration,您可以直接访问环境变量,而不是通过配置对象。

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");
        ...
    

【讨论】:

【参考方案10】:

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

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

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

_systemConfiguration.ConfigToSomeThing(123);

【讨论】:

【参考方案11】:

我刚刚创建了下面的类:


    /// <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;

【讨论】:

【参考方案12】:

这是一种从 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 传递参数值。

【讨论】:

问题是关于如何“从静态类访问配置”,所以我认为在这里告诉我们如何“无需静态引用这些配置”来获取配置并不是很有用【参考方案13】:

考虑使用说明 here 进行 ASP.NET Core 配置。

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

_config.UserName

在启动 - ConfigureServices:

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

然后只需将您的对象注入您需要的任何位置:

IOptions<Config> config

【讨论】:

你打算如何在静态类中注入对象? 您将配置注入实例而不是使用静态类。我没有建议尝试将配置注入静态。 这个问题专门关于在static class中使用它 @SerjSagan 尽管这个问题专门询问静态类,但他们可能不明白从 .NET 到 .NET Core 的范式转变。因此,静态类不是最佳答案,因此选项模式应该是有效答案。【参考方案14】:

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

【讨论】:

你不能 DI 变成一个静态的,所以这是错误的答案。 嗨,billb,这就是我上面提到的。我们不能 DI 进入静态类,这就是上面代码块的原因。没有 DI 发生但允许访问配置,例如代码块中的“UserSecrets” 好吧,你不能 DI 进入一个静态类,但是你可以 DI 进入一个包含静态私有成员的类。添加到启动服务可能是最好的方法,但是它将您的项目耦合在一起并且使重用变得更加困难。我倾向于在我的服务类中包含静态,它们可以被完全封装。

以上是关于ASP.NET Core——从静态类访问配置的主要内容,如果未能解决你的问题,请参考以下文章

来自静态类的 ASP.NET Core Web API 日志记录

一个文件搞定Asp.net core 3.1动态页面转静态页面

ASP.NET Core - 从 WebApi 提供静态内容 [重复]

使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API

asp.net core 系列之静态文件

ASP.NET Core依赖注入高级玩法——如何注入多个服务实现类