ASP.NET Core - доступ до конфігурації зі статичного класу


82

Я хочу простий статичний клас, який отримує доступ до об'єкта Configuration. Вся інформація про конфігурацію вже прочитана з файлу 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
чому статичний клас? Ви можете безпосередньо ввести конфігурацію або створити
синглтон

Відповіді:


55

Трохи коротша версія, заснована на тому ж принципі, що і вище ...

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

public static IConfiguration StaticConfig { get; private set; }

Для використання в іншому статичному класі:

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

1
поділитися цими значеннями конфігурації між різними бібліотеками класів буде складно. Уявіть, що у вас є інфраструктура для спільного використання конфігурацій / налаштувань між декількома проектами?
Junior Mayhé

1
Мене бентежить, чому це не прийнята відповідь. Чи є в цьому певний ризик? Виглядає просто і функціонально.
Хаїм Елія

3
Тому що це анти-шаблон. Ви повинні мати змогу вводити все необхідне, щоб замінити їх під час тестування. Коли це статична властивість з a private setter, як ви збираєтесь це замінити? Вам доведеться зробити це publicлише для тестування, що неправильно.
Serj Sagan

2
Я не відчуваю, що це анти-шаблон, тому що IConfiguration має методи прив'язки, які дозволяють прив'язувати його до класу з цієї причини та багато іншого. Однак я б не зберігав його як IConfiguration. Багаторазовий синтаксичний аналіз рядків на їх реальні типи, такі як булеві значення та ints - це анти-шаблон, яким я більше займаюся у світі мікросервісів. Не потрібно встановлювати "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 - це будь-який клас, який відображається у розділі MyAppSettings у appsettings.json:

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

8

Я згоден з mcbowes, це в документах , але перший приклад більше схожий на те, що вам потрібно ... хочете:

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з іншого внутрішнього статичного класу, не відновлюючи його кожного разу?
Сердж Саган,

Тоді слід використовувати 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"];
  }


}

Налаштування DI в класі StartUp

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 та модифікації конструкторів класів
Junior Mayhé

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,

2

Це вже було сказано, але я збираюся це сказати.

Я вважаю .Net Core хоче, щоб розробники отримували значення за допомогою Dependency Inject. Це те, що я помітив у своїх дослідженнях, але я також трохи припускаю. Як розробники, нам потрібно слідувати цій зміні парадигми, щоб добре використовувати .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, що є страшним рішенням imo.


1
Статичні класи не є найкращою практикою, оскільки об’єктно-орієнтоване програмування трохи важке. Ви хочете сказати , з збереженням стану класів статичних. Але навіть це досить важко. DI у цій формі, можливо, більше концепція дизайну компонентів, ніж концепція ООП. Люди, які телефонують, повинні знати внутрішні залежності, насправді не відповідають ідею ООП. Компроміс, який розробники люблять, - це детальне тестування модулів. Багато з нас мають надзвичайно анемічну конструкцію ООП, яка ближча до імперативних функцій, що проходять навколо штату, ніж ООП.
Джошуа Енфілд,

як я буду використовувати шаблон параметрів, якщо я залежа від такої бібліотеки, як масовий транспорт, і під час налаштування шини я хочу використовувати шаблон параметрів замість доступу до жорстко закодованого рядка з налаштувань додатків?
kuldeep

1

Особисто мені подобається метод, використаний у цьому посиланні

По суті, це просто додавання статичного поля до вашого класу параметрів.

 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, використовуючи цей підхід.


Питання в тому, як "отримати доступ до конфігурації зі статичного класу", тому я не думаю, що дуже корисно розповідати нам тут, як отримати конфігурації "без необхідності посилання на них статично"
Колін

-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

Конфігурація IC є ін’єкційною в будь-якому місці проекту. Але у випадку зі статичним класом я використовую опцію, яка, можливо, підходить ... var Configuration = new ConfigurationBuilder() .AddUserSecrets<Startup>() .Build(); І, ви можете додати потрібний розділ, наприклад, у цьому блоці коду вище, я додав 'UserSecrets'.


Ви не можете потрапити в статику, тому це неправильна відповідь.
billb

Привіт billb, це те, про що я згадав вище. Ми не можемо перейти до статичного класу, і тому код вище. Там, де не відбувається DI, але дозволяє отримати доступ до конфігурації, наприклад, "UserSecrets" у блоці коду
irejwanul

Ну, ви не можете DI в статичний клас, АЛЕ ви можете DI в клас, який містить статичні приватні члени. Додавання до послуги запуску - це, мабуть, найкращий підхід, але це поєднує ваші проекти разом і ускладнює повторне використання. Я, як правило, включаю статику в свої сервісні класи, де їх можна повністю інкапсулювати.
iGanja
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.