Mari kita menunda pembicaraan tentang DDD dan refleksi sejenak. Saya mengusulkan untuk berbicara tentang yang sederhana, tentang pengaturan pengaturan aplikasi.
Setelah kolega saya dan saya memutuskan untuk beralih ke .NET Core, muncul pertanyaan tentang bagaimana mengatur file konfigurasi, bagaimana melakukan transformasi, dll. Di lingkungan baru. Kode berikut ditemukan dalam banyak contoh, dan banyak yang berhasil menggunakannya.
public IConfiguration Configuration { get; set; } public IHostingEnvironment Environment { get; set; } public Startup(IConfiguration configuration, IHostingEnvironment environment) { Environment = environment; Configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json") .Build(); }
Tapi mari kita lihat bagaimana konfigurasi bekerja, dan dalam kasus apa menggunakan pendekatan ini, dan di mana untuk mempercayai pengembang .NET Core. Saya minta kucing.
Seperti sebelumnya
Seperti kisah apa pun, artikel ini memiliki awal. Salah satu masalah pertama setelah beralih ke ASP.NET Core adalah transformasi file konfigurasi.
Ingat bagaimana sebelumnya dengan web.config
Konfigurasi terdiri dari beberapa file. File utama adalah web.config , dan transformasi ( web.Development.config , dll.) Sudah diterapkan, tergantung pada konfigurasi perakitan. Pada saat yang sama, atribut xml- secara aktif digunakan untuk mencari dan mengubah bagian dokumen xml- .
Tapi seperti yang kita tahu di ASP.NET Core, file web.config telah digantikan oleh appsettings.json dan tidak ada lagi mekanisme transformasi yang biasa.
Apa yang diceritakan google kepada kami?Hasil pencarian untuk "Mengubah ke dalam ASP.NET Core" di google menghasilkan kode berikut:
public IConfiguration Configuration { get; set; } public IHostingEnvironment Environment { get; set; } public Startup(IConfiguration configuration, IHostingEnvironment environment) { Environment = environment; Configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json") .Build(); }
Di konstruktor dari kelas Startup , kami membuat objek konfigurasi menggunakan ConfigurationBuilder . Dalam hal ini, kami secara eksplisit menunjukkan sumber konfigurasi yang ingin kami gunakan.
Dan semacamnya:
public IConfiguration Configuration { get; set; } public IHostingEnvironment Environment { get; set; } public Startup(IConfiguration configuration, IHostingEnvironment environment) { Environment = environment; Configuration = new ConfigurationBuilder() .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json") .Build(); }
Bergantung pada variabel lingkungan, satu atau beberapa sumber konfigurasi dipilih.
Jawaban ini sering ditemukan di SO dan sumber daya lain yang kurang populer. Namun perasaan itu tidak pergi. bahwa kita salah. Bagaimana jika saya ingin menggunakan variabel lingkungan atau argumen baris perintah dalam konfigurasi? Mengapa saya perlu menulis kode ini di setiap proyek?
Untuk mencari kebenaran, saya harus mempelajari dokumentasi dan kode sumber. Dan saya ingin berbagi ilmu yang didapat dalam artikel ini.
Mari kita lihat bagaimana konfigurasi bekerja di .NET Core.
Konfigurasi
Konfigurasi dalam .NET Core diwakili oleh objek antarmuka IConfiguration .
public interface IConfiguration { string this[string key] { get; set; } IConfigurationSection GetSection(string key); IEnumerable<IConfigurationSection> GetChildren(); IChangeToken GetReloadToken(); }
- [string key] indexer, yang memungkinkan untuk mendapatkan nilai parameter konfigurasi dengan kunci
- GetSection (kunci string) mengembalikan bagian konfigurasi yang sesuai dengan kunci tombol
- GetChildren () mengembalikan set subbagian dari bagian konfigurasi saat ini
- GetReloadToken () mengembalikan instance IChangeToken yang dapat digunakan untuk menerima notifikasi ketika konfigurasi berubah
Konfigurasi adalah kumpulan pasangan nilai kunci. Saat membaca dari sumber konfigurasi (file, variabel lingkungan), data hierarkis direduksi menjadi struktur datar. Misalnya, json adalah objek dari formulir
{ "Settings": { "Key": "I am options" } }
akan direduksi menjadi tampilan datar:
Settings:Key = I am options
Di sini, kuncinya adalah Pengaturan: Kunci , dan nilainya adalah pilihan saya .
Penyedia konfigurasi digunakan untuk mengisi konfigurasi.
Penyedia konfigurasi
Objek antarmuka bertanggung jawab untuk membaca data dari sumber konfigurasi.
IConfigurationProvider :
public interface IConfigurationProvider { bool TryGet(string key, out string value); void Set(string key, string value); IChangeToken GetReloadToken(); void Load(); IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath); }
- TryGet (kunci string, nilai string keluar) memungkinkan untuk mendapatkan nilai parameter konfigurasi dengan kunci
- Set (kunci string, nilai string) digunakan untuk mengatur nilai parameter konfigurasi
- GetReloadToken () mengembalikan instance IChangeToken yang dapat digunakan untuk menerima notifikasi ketika sumber konfigurasi berubah
- Load () metode yang bertanggung jawab untuk membaca sumber konfigurasi
- GetChildKeys (IEnumerable <string> beforeKeys, string parentPath) memungkinkan Anda untuk mendapatkan daftar semua kunci yang disediakan penyedia konfigurasi ini
Penyedia berikut tersedia dari kotak:
- Json
- Ini
- Xml
- Variabel Lingkungan
- Ingatan
- Azure
- Penyedia konfigurasi khusus
Konvensi berikut untuk menggunakan penyedia konfigurasi diterima.
- Sumber konfigurasi dibaca dalam urutan yang ditentukan.
- Jika kunci yang sama ada di sumber konfigurasi yang berbeda (perbandingannya tidak peka huruf besar-kecil), maka nilai yang ditambahkan terakhir digunakan.
Jika kami membuat instance dari server web menggunakan CreateDefaultBuilder , maka penyedia konfigurasi berikut terhubung secara default:

- ChainedConfigurationProvider melalui penyedia ini Anda bisa mendapatkan nilai dan kunci konfigurasi yang telah ditambahkan oleh penyedia konfigurasi lainnya
- JsonConfigurationProvider menggunakan file json sebagai sumber konfigurasi. Seperti yang Anda lihat, tiga penyedia jenis ini ditambahkan ke daftar penyedia. Yang pertama menggunakan appsettings.json sebagai sumber, yang kedua menggunakan appsettings. {Lingkungan} .json . Yang ketiga membaca data dari secrets.json . Jika Anda membangun aplikasi dalam konfigurasi Release , penyedia ketiga tidak akan terhubung, karena tidak disarankan untuk menggunakan rahasia di lingkungan Produksi
- EnvironmentVariablesConfigurationProvider mengambil parameter konfigurasi dari variabel lingkungan
- CommandLineConfigurationProvider memungkinkan menambahkan argumen baris perintah ke konfigurasi
Karena konfigurasi disimpan sebagai kamus, penting untuk memastikan keunikan tombol. Secara default, ini berfungsi seperti ini.
Jika penyedia CommandLineConfigurationProvider memiliki elemen dengan kunci tombol dan penyedia JsonConfigurationProvider memiliki elemen dengan kunci, elemen dari JsonConfigurationProvider akan digantikan oleh elemen dari CommandLineConfigurationProvider karena terdaftar terakhir dan memiliki prioritas lebih tinggi.
Ingat contoh dari awal artikel public IConfiguration Configuration { get; set; } public IHostingEnvironment Environment { get; set; } public Startup(IConfiguration configuration, IHostingEnvironment environment) { Environment = environment; Configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json") .Build(); }
Kita tidak perlu membuat IConfiguration sendiri untuk mengubah file konfigurasi, karena ini diaktifkan secara default. Pendekatan ini diperlukan ketika kita ingin membatasi jumlah sumber konfigurasi.
Penyedia konfigurasi khusus
Untuk menulis penyedia konfigurasi Anda, Anda perlu mengimplementasikan antarmuka IConfigurationProvider dan IConfigurationSource . IConfigurationSource adalah antarmuka baru yang belum kami pertimbangkan dalam artikel ini.
public interface IConfigurationSource { IConfigurationProvider Build(IConfigurationBuilder builder); }
Antarmuka terdiri dari metode Build tunggal yang menggunakan IConfigurationBuilder sebagai parameter dan mengembalikan instance baru IConfigurationProvider .
Untuk mengimplementasikan penyedia konfigurasi kami, kelas abstrak ConfigurationProvider dan FileConfigurationProvider tersedia untuk kami. Di kelas-kelas ini, logika metode TryGet , Set , GetReloadToken , GetChildKeys sudah diimplementasikan dan tetap hanya menerapkan metode Load .
Mari kita lihat sebuah contoh. Kita perlu menerapkan membaca konfigurasi dari file yaml , dan kita juga perlu mengubah konfigurasi tanpa memulai ulang aplikasi kita.
Buat kelas YamlConfigurationProvider dan jadikan sebagai pewaris FileConfigurationProvider .
public class YamlConfigurationProvider : FileConfigurationProvider { private readonly string _filePath; public YamlConfigurationProvider(FileConfigurationSource source) : base(source) { } public override void Load(Stream stream) { throw new NotImplementedException(); } }
Dalam cuplikan kode di atas, Anda dapat melihat beberapa fitur dari kelas FileConfigurationProvider . Konstruktor menerima turunan dari FileConfigurationSource , yang berisi IFileProvider . IFileProvider digunakan untuk membaca file, dan berlangganan acara perubahan file. Anda juga dapat melihat bahwa metode Load menerima Stream di mana file konfigurasi terbuka untuk dibaca. Ini adalah metode kelas FileConfigurationProvider dan tidak ada dalam antarmuka IConfigurationProvider .
Tambahkan implementasi sederhana yang memungkinkan kita membaca file yaml . Untuk membaca file, saya akan menggunakan paket YamlDotNet .
Implementasi YamlConfigurationProvider public class YamlConfigurationProvider : FileConfigurationProvider { private readonly string _filePath; public YamlConfigurationProvider(FileConfigurationSource source) : base(source) { } public override void Load(Stream stream) { if (stream.CanSeek) { stream.Seek(0L, SeekOrigin.Begin); using (StreamReader streamReader = new StreamReader(stream)) { var fileContent = streamReader.ReadToEnd(); var yamlObject = new DeserializerBuilder() .Build() .Deserialize(new StringReader(fileContent)) as IDictionary<object, object>; Data = new Dictionary<string, string>(); foreach (var pair in yamlObject) { FillData(String.Empty, pair); } } } } private void FillData(string prefix, KeyValuePair<object, object> pair) { var key = String.IsNullOrEmpty(prefix) ? pair.Key.ToString() : $"{prefix}:{pair.Key}"; switch (pair.Value) { case string value: Data.Add(key, value); break; case IDictionary<object, object> section: { foreach (var sectionPair in section) FillData(pair.Key.ToString(), sectionPair); break; } } } }
Untuk membuat turunan dari penyedia konfigurasi kami, Anda harus mengimplementasikan FileConfigurationSource .
Implementasi YamlConfigurationSource public class YamlConfigurationSource : FileConfigurationSource { public YamlConfigurationSource(string fileName) { Path = fileName; ReloadOnChange = true; } public override IConfigurationProvider Build(IConfigurationBuilder builder) { this.EnsureDefaults(builder); return new YamlConfigurationProvider(this); } }
Penting untuk dicatat di sini bahwa untuk menginisialisasi properti kelas dasar, Anda harus memanggil metode this.EnsureDefaults (builder) .
Untuk mendaftarkan penyedia konfigurasi khusus dalam aplikasi, Anda perlu menambahkan instance penyedia ke IConfigurationBuilder . Anda dapat memanggil metode Tambahkan dari IConfigurationBuilder , tetapi saya akan segera mengeluarkan logika inisialisasi YamlConfigurationProvider dalam metode ekstensi .
Terapkan YamlConfigurationExtensions public static class YamlConfigurationExtensions { public static IConfigurationBuilder AddYaml( this IConfigurationBuilder builder, string filePath) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath)); return builder .Add(new YamlConfigurationSource(filePath)); } }
Panggil Metode AddYaml public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, builder) => { builder.AddYaml("appsettings.yaml"); }) .UseStartup<Startup>(); }
Ubah pelacakan
Dalam konfigurasi api yang baru, dimungkinkan untuk membaca kembali sumber konfigurasi ketika diubah. Pada saat yang sama, aplikasi tidak me-restart.
Cara kerjanya:
- Penyedia Konfigurasi memonitor perubahan sumber konfigurasi
- Jika perubahan konfigurasi terjadi, IChangeToken baru dibuat .
- Ketika IChangeToken diubah , reload konfigurasi dipanggil
Mari kita lihat bagaimana pelacakan perubahan diterapkan di FileConfigurationProvider .
ChangeToken.OnChange(
Dua parameter dilewatkan ke metode OnChange dari kelas statis ChangeToken . Parameter pertama adalah fungsi yang mengembalikan IChangeToken baru ketika sumber konfigurasi (dalam hal ini file) berubah, ini adalah apa yang disebut produser . Parameter kedua adalah fungsi callback (atau konsumen ), yang akan dipanggil ketika sumber konfigurasi diubah.
Pelajari lebih lanjut tentang kelas ChangeToken .
Tidak semua penyedia konfigurasi menerapkan pelacakan perubahan. Mekanisme ini tersedia untuk keturunan FileConfigurationProvider dan AzureKeyVaultConfigurationProvider .
Kesimpulan
Di .NET Core, kami memiliki mekanisme yang mudah dan nyaman untuk mengelola pengaturan aplikasi. Banyak add-on yang tersedia di luar kotak, banyak hal digunakan secara default.
Tentu saja, setiap orang memutuskan cara mana yang akan menggunakannya, tetapi saya yakin bahwa orang tahu alat mereka.
Artikel ini hanya membahas dasar-dasarnya. Selain dasar-dasar, IOptions, skrip pasca-konfigurasi, validasi pengaturan, dan banyak lagi tersedia untuk kami. Tapi itu cerita lain.
Anda dapat menemukan proyek aplikasi dengan contoh-contoh dari artikel ini di repositori di Github .
Bagikan komentar yang menggunakan pendekatan manajemen konfigurasi mana?
Terima kasih atas perhatian anda
upd .: Seperti yang disarankan AdAbsurdum , ketika bekerja dengan array, elemen tidak akan selalu diganti ketika menggabungkan konfigurasi dari dua sumber.
Pertimbangkan sebuah contoh. Saat membaca array dari appsettings.json kita mendapatkan tampilan datar ini:
array:0=valueA
Saat membaca dari appsettings.Development.json :
array:0=valueB array:1=value
Akibatnya, konfigurasi akan menjadi:
array:0=valueB array:1=value
Semua elemen dengan indeks unik ( array: 1 pada contoh) akan ditambahkan ke array yang dihasilkan. Elemen dari sumber konfigurasi yang berbeda, tetapi memiliki indeks yang sama ( array: 0 dalam contoh) akan mengalami penggabungan, dan elemen yang ditambahkan terakhir akan digunakan.