【问题标题】:ASP.NET Core—access Configuration from static classASP.NET Core——从静态类访问配置
【发布时间】: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。将更新问题。
  • 使用静态类可能是单元测试的坏习惯:stackoverflow.com/a/38107134/2803565
  • 为什么是静态类?您可以直接注入配置或创建单例

标签: asp.net-core-mvc


【解决方案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】:
  1. 在服务层创建ConfigurationHelper静态类,这样就可以在没有循环依赖的情况下在其他层使用。
public static class ConfigurationHelper
    {
        public static IConfiguration config;
        public static void Initialize(IConfiguration Configuration)
        {
            config = Configuration;
        }
    }
  1. 在 Startup 类的 ConfigureServices 方法中初始化 ConfigurationHelper。
ConfigurationHelper.Initialize(Configuration);
  1. 在任何你想要的地方使用它,包括你的静态类
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 进入一个包含静态私有成员的类。添加到启动服务可能是最好的方法,但是它将您的项目耦合在一起并且使重用变得更加困难。我倾向于在我的服务类中包含静态,它们可以被完全封装。
          猜你喜欢
          • 2020-02-02
          • 1970-01-01
          • 1970-01-01
          • 2020-08-09
          • 1970-01-01
          • 2011-07-31
          • 2018-03-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多