دعونا نتوقف عن الحديث عن DDD والتفكير لفترة من الوقت. أقترح التحدث عن بسيطة ، حول تنظيم إعدادات التطبيق.
بعد أن قررت أنا وزملائي التبديل إلى .NET Core ، نشأ سؤال حول كيفية تنظيم ملفات التكوين ، وكيفية إجراء التحويلات ، إلخ في البيئة الجديدة. تم العثور على الكود التالي في العديد من الأمثلة ، واستخدمه الكثير بنجاح.
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(); }
ولكن دعونا نرى كيف يعمل التكوين ، وفي أي الحالات لاستخدام هذا النهج ، والتي تثق في مطوري .NET Core. أطلب القط.
كما كان من قبل
مثل أي قصة ، هذا المقال له بداية. واحدة من المشكلات الأولى بعد التبديل إلى ASP.NET Core هي تحويل ملفات التكوين.
تذكر كيف كان من قبل مع web.config
يتكون التكوين من عدة ملفات. كان الملف الرئيسي هو web.config ، وقد تم بالفعل تطبيق التحويلات ( web.Development.config ، إلخ) على ذلك ، اعتمادًا على تكوين التجميع. في الوقت نفسه ، تم استخدام سمات xml بنشاط للبحث في قسم مستند xml وتحويله .
ولكن كما نعلم في ASP.NET Core ، تم استبدال ملف web.config بـ appsettings.json ولم تعد هناك آلية التحويل المعتادة.
ماذا تقول لنا جوجل؟نتيجة البحث عن "التحويل إلى ASP.NET Core" على google نتج عنها الكود التالي:
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(); }
في مُنشئ فئة بدء التشغيل ، نقوم بإنشاء كائن تكوين باستخدام ConfigurationBuilder . في هذه الحالة ، نشير بوضوح إلى مصادر التكوين التي نريد استخدامها.
و مثل:
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(); }
اعتمادًا على متغير البيئة ، يتم تحديد مصدر التكوين واحد أو آخر.
غالبًا ما توجد هذه الإجابات على SO وغيرها من الموارد الأقل شعبية. لكن الشعور لم يترك. أننا نخطئ. ماذا لو كنت أرغب في استخدام متغيرات البيئة أو وسيطات سطر الأوامر في التكوين؟ لماذا أحتاج إلى كتابة هذا الرمز في كل مشروع؟
بحثًا عن الحقيقة ، اضطررت إلى التعمق في التوثيق وشفرة المصدر. وأريد أن أشارك المعرفة المكتسبة في هذا المقال.
دعونا نرى كيف يعمل التكوين في. NET الأساسية.
ترتيب
يتم تمثيل التكوين في .NET Core بواسطة كائن واجهة IConfiguration .
public interface IConfiguration { string this[string key] { get; set; } IConfigurationSection GetSection(string key); IEnumerable<IConfigurationSection> GetChildren(); IChangeToken GetReloadToken(); }
- مفهرس [سلسلة المفتاح] ، والذي يسمح بالحصول على قيمة معلمة التكوين حسب المفتاح
- إرجاع GetSection (مفتاح السلسلة) قسم التكوين الذي يتوافق مع مفتاح المفتاح
- إرجاع GetChildren () مجموعة فرعية من قسم التكوين الحالي
- إرجاع GetReloadToken () مثيل IChangeToken التي يمكن استخدامها لتلقي الإعلامات عند تغيير التكوين
التكوين هو مجموعة من أزواج القيمة الرئيسية. عند القراءة من مصدر التكوين (ملف ، متغيرات البيئة) ، يتم تقليل البيانات الهرمية إلى بنية مسطحة. على سبيل المثال ، json هو كائن من النموذج
{ "Settings": { "Key": "I am options" } }
سيتم تخفيضها إلى طريقة عرض مسطحة:
Settings:Key = I am options
هنا ، المفتاح هو الإعدادات: مفتاح ، والقيمة هي خيارات أنا .
يتم استخدام موفري التكوين لملء التكوين.
مزودي التكوين
كائن الواجهة مسؤول عن قراءة البيانات من مصدر التكوين.
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 (مفتاح السلسلة ، قيمة السلسلة الخارجية) بالحصول على قيمة معلمة التكوين حسب المفتاح
- يتم تعيين (مفتاح السلسلة ، قيمة السلسلة) لتعيين قيمة معلمة التكوين
- إرجاع GetReloadToken () مثيل IChangeToken التي يمكن استخدامها لتلقي الإعلامات عند تغيير مصدر التكوين
- أسلوب Load () المسؤول عن قراءة مصدر التكوين
- يتيح لك GetChildKeys (IEnumerable <string> laterKeys ، سلسلة parentPath) الحصول على قائمة بكافة المفاتيح التي يوفرها موفر التكوين هذا
الموفرون التاليون متاحون من الصندوق:
- سلمان
- رسائل كتبها هذا المؤلف
- أكس
- متغيرات البيئة
- InMemory
- أزرق سماوي
- مزود التكوين المخصص
يتم قبول الاتفاقيات التالية لاستخدام موفري التكوين.
- تتم قراءة مصادر التكوين بالترتيب الذي تم تحديدها به.
- إذا كانت نفس المفاتيح موجودة في مصادر تكوين مختلفة (المقارنة ليست حساسة لحالة الأحرف) ، فسيتم استخدام القيمة التي تمت إضافتها في آخر الأمر.
إذا أنشأنا مثيلًا لخادم الويب باستخدام CreateDefaultBuilder ، فسيتم توصيل موفري التكوين التالي افتراضيًا:

- ChainedConfigurationProvider من خلال هذا الموفر يمكنك الحصول على القيم ومفاتيح التكوين التي تمت إضافتها بواسطة موفري التكوين الآخرين
- يستخدم JsonConfigurationProvider ملفات json كمصدر التكوين. كما ترى ، تتم إضافة ثلاثة موفرين من هذا النوع إلى قائمة الموفرين. الأول يستخدم appsettings.json كمصدر ، والثاني يستخدم appsettings. {Environment} .json . الثالث يقرأ البيانات من secrets.json . إذا قمت بإنشاء التطبيق في تكوين Release ، فلن يكون الموفر الثالث متصلاً ، لأنه لا يوصى باستخدام أسرار في بيئة الإنتاج
- EnvironmentVariablesConfigurationProvider باسترداد معلمات التكوين من متغيرات البيئة
- CommandLineConfigurationProvider يسمح بإضافة وسيطات سطر الأوامر إلى التكوين
نظرًا لأنه يتم تخزين التكوين كقاموس ، فمن الضروري ضمان تفرد المفاتيح. بشكل افتراضي ، هذا يعمل مثل هذا.
إذا كان لدى موفر CommandLineConfigurationProvider عنصر مع مفتاح المفتاح وكان لدى موفر JsonConfigurationProvider عنصر مع مفتاح المفتاح ، سيتم استبدال العنصر من JsonConfigurationProvider بالعنصر من CommandLineConfigurationProvider لأنه مسجل أخيرًا وله أولوية أعلى.
أذكر مثال من بداية المقال 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(); }
لا نحتاج إلى إنشاء IConfiguration بأنفسنا من أجل تحويل ملفات التكوين ، حيث يتم تمكين هذا افتراضيًا. هذا النهج ضروري عندما نريد تحديد عدد مصادر التكوين.
مزود التكوين المخصص
من أجل كتابة موفر التكوين الخاص بك ، تحتاج إلى تنفيذ واجهات IConfigurationProvider و IConfigurationSource . IConfigurationSource هي واجهة جديدة لم نأخذها في الاعتبار في هذه المقالة.
public interface IConfigurationSource { IConfigurationProvider Build(IConfigurationBuilder builder); }
تتكون الواجهة من طريقة Build واحدة تأخذ IConfigurationBuilder كمعلمة وتقوم بإرجاع مثيل جديد من IConfigurationProvider .
لتنفيذ موفري التكوين لدينا ، تتوفر الفصول المجردة ConfigurationProvider و FileConfigurationProvider لنا. في هذه الفئات ، يتم بالفعل تطبيق أساليب TryGet و Set و GetReloadToken و GetChildKeys ولا يزال يتم تطبيق طريقة التحميل فقط.
لنلقِ نظرة على مثال. من الضروري تطبيق قراءة التكوين من ملف yaml ، ومن الضروري أيضًا أن نتمكن من تغيير التكوين دون إعادة تشغيل تطبيقنا.
قم بإنشاء فئة YamlConfigurationProvider واجعلها وراثة FileConfigurationProvider .
public class YamlConfigurationProvider : FileConfigurationProvider { private readonly string _filePath; public YamlConfigurationProvider(FileConfigurationSource source) : base(source) { } public override void Load(Stream stream) { throw new NotImplementedException(); } }
في مقتطف الشفرة أعلاه ، يمكنك ملاحظة بعض ميزات فئة FileConfigurationProvider . يقبل المُنشئ مثيل FileConfigurationSource ، والذي يحتوي على IFileProvider . يتم استخدام IFileProvider لقراءة ملف ، والاشتراك في حدث تغيير ملف. يمكنك أيضًا ملاحظة أن طريقة التحميل تقبل تدفقًا يكون فيه ملف التكوين مفتوحًا للقراءة. هذه طريقة لفئة FileConfigurationProvider وليست في واجهة IConfigurationProvider .
أضف تطبيقًا بسيطًا يتيح لنا قراءة ملف yaml . لقراءة الملف ، سأستخدم حزمة YamlDotNet .
تطبيق 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; } } } }
لإنشاء مثيل لموفر التكوين الخاص بنا ، يجب تطبيق FileConfigurationSource .
تطبيق 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); } }
من المهم أن نلاحظ هنا أنه لتهيئة خصائص الفئة الأساسية ، يجب عليك استدعاء الأسلوب this.EnsureDefaults (البناء) .
لتسجيل موفر تكوين مخصص في التطبيق ، تحتاج إلى إضافة مثيل الموفر إلى IConfigurationBuilder . يمكنك استدعاء الأسلوب Add من IConfigurationBuilder ، لكنني سأضع على الفور منطق تهيئة YamlConfigurationProvider في طريقة التمديد .
تطبيق 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)); } }
استدعاء 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>(); }
تغيير التتبع
في تكوين api الجديد ، أصبح من الممكن إعادة قراءة مصدر التكوين عند تغييره. في نفس الوقت ، لا يتم إعادة تشغيل التطبيق.
كيف يعمل:
- موفر التكوين تراقب التغييرات مصدر التكوين
- في حالة حدوث تغيير في التكوين ، يتم إنشاء IChangeToken جديد .
- عند تغيير IChangeToken ، يتم استدعاء إعادة تحميل التكوين
دعونا نرى كيف يتم تطبيق تتبع التغيير في FileConfigurationProvider .
ChangeToken.OnChange(
يتم تمرير معلمتين إلى أسلوب OnChange للفئة ثابتة ChangeToken . المعلمة الأولى هي وظيفة تقوم بإرجاع IChangeToken جديد عندما يتغير مصدر التكوين (في هذه الحالة يكون الملف) ، وهذا هو ما يسمى منتج . المعلمة الثانية هي وظيفة رد الاتصال (أو المستهلك ) ، والتي سيتم استدعاؤها عند تغيير مصدر التكوين.
تعرف على المزيد حول فئة ChangeToken .
لا يقوم جميع مزودي التكوين بتنفيذ تتبع التغيير. هذه الآلية متاحة لأحفاد FileConfigurationProvider و AzureKeyVaultConfigurationProvider .
استنتاج
في .NET Core ، لدينا آلية سهلة ومريحة لإدارة إعدادات التطبيق. تتوفر العديد من الوظائف الإضافية خارج الصندوق ، ويتم استخدام العديد من الأشياء بشكل افتراضي.
بالطبع ، يقرر كل شخص طريقة استخدامه ، لكنني أعلم أن الناس يعرفون أدواتهم.
هذا المقال لا يغطي سوى الأساسيات. بالإضافة إلى الأساسيات ، تتوفر لنا IOptions والبرامج النصية بعد التكوين والتحقق من صحة الإعدادات والمزيد. لكن هذه قصة أخرى.
يمكنك العثور على مشروع التطبيق مع أمثلة من هذه المقالة في مستودع التخزين على جيثب .
شارك في التعليقات الذي يستخدم منهج إدارة التكوين؟
شكرا لاهتمامكم
محدث : كما اقترح AdAbsurdum بشكل صحيح ، عند العمل مع المصفوفات ، لن يتم استبدال العناصر دائمًا عند دمج التكوين من مصدرين.
النظر في مثال. عند قراءة مجموعة من appsettings.json نحصل على هذا العرض المسطح:
array:0=valueA
عند القراءة من appsettings.Development.json :
array:0=valueB array:1=value
نتيجة لذلك ، سيكون التكوين:
array:0=valueB array:1=value
ستتم إضافة جميع العناصر ذات المؤشرات الفريدة ( الصفيف: 1 في المثال) إلى الصفيف الناتج. ستخضع العناصر من مصادر التكوين المختلفة ، ولكن لها نفس الفهرس ( الصفيف: 0 في المثال) للدمج ، وسيتم استخدام العنصر الذي تمت إضافته في النهاية.