ما هو Generic Host في .NET - أساس DI والإعدادات والسجلات

· آخر تحديث: · · C#, .NET, Generic Host, Worker, التصميم

سجل التعديلات (3 تحديثات، آخر تحديث 3 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُضيفت روابط الاستشارة الموجودة في الأصل الياباني (consultation_services). ولم يتغيّر نصّ المقالة نفسه. قراءة النسخة السابقة لهذا التحديث (DOI: 10.5281/zenodo.22240829)
أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621357)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

小村 豪 (2026). ما هو Generic Host في .NET - أساس DI والإعدادات والسجلات. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621357 https://comcomponent.com/ar/blog/2026/03/14/000-dotnet-generic-host-what-is/

DOI (أحدث نسخة)
10.5281/zenodo.21621357
DOI (هذه النسخة)
10.5281/zenodo.22279756

عندما تبدأ كتابة تطبيق console أو worker في .NET، يكفي أوّلاً وضع معالجة قليلة في Main. غير أنه ما إن ينمو قليلاً حتى تظهر تقريباً هذه الاحتياجات.

  • قراءة appsettings.json
  • التجاوز بمتغيّرات البيئة
  • إخراج السجلات عبر ILogger
  • تجنّب أن يصير إنشاء الخدمات جداراً من new
  • تشغيل حلقة في الخلفية
  • الإنهاء النظيف عند Ctrl+C أو إيقاف الخدمة

هنا يظهر Generic Host. غير أن الاسم نفسه يختلط قليلاً.

  • ما الفرق بين Host.CreateApplicationBuilder و Host.CreateDefaultBuilder؟
  • هل IHost هو حاوية DI نفسها؟
  • ما صلته بـ BackgroundService؟
  • هل هو شيء منفصل عن WebApplicationBuilder في ASP.NET Core؟
  • هل يستحق الاستخدام في تطبيق console أيضاً؟

إذا اختلطت هذه الأسئلة بدا Generic Host «شيئاً خاصّاً بتطبيقات الويب»، أو على العكس «شيئاً يجب وضع كل شيء على host». كلا النظرتين فضفاضتان.

تفترض هذه المقالة الإحساس العملي الحالي منذ .NET 6 فصاعداً، وترتّب أوّلاً أربع نقاط.

  • ما هو Generic Host فعلاً
  • ماذا يجمع ويتولّى؟
  • علاقة Host.CreateApplicationBuilder / Host.CreateDefaultBuilder / WebApplication.CreateBuilder
  • من أين يبدأ الدخول بهدوء

الفهرس

  1. الخلاصة في جملة
    • 1.1. نثبّت المصطلحات أوّلاً
  2. جداول للنظر أوّلاً
    • 2.1. ما يحمله Generic Host
    • 2.2. الفرق بين الـ builder
    • 2.3. لماذا توجد مداخل متعدّدة؟
  3. الصورة الكاملة لـ Generic Host (رسم)
  4. ماذا يفيد Generic Host؟
    • 4.1. تجميع معالجة الإقلاع في موضع واحد
    • 4.2. DI والإعدادات والسجلات تتّصل من البداية
    • 4.3. يسهّل الإنهاء السليم والتشغيل المقيم
  5. الحدّ الأدنى من التكوين
    • 5.1. أصغر مثال على تطبيق console
    • 5.2. appsettings.json
    • 5.3. إضافة BackgroundService
  6. أنماط نمطية
    • 6.1. أدوات console قصيرة العمر
    • 6.2. worker / خدمات خلفية
    • 6.3. يوجد أيضاً تحت ASP.NET Core
  7. الحالات التي يناسبه
  8. الحالات التي لا يناسبه / يكون فيها زائداً
  9. مواضع التعثّر
  10. الخلاصة
  11. روابط مرجعية

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 16، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

1. الخلاصة في جملة

  • Generic Host أساس يجمع تشغيل تطبيق .NET وعمره.
  • داخله DI والإعدادات والسجلات و IHostedService / BackgroundService ومعالجة إيقاف التطبيق.
  • للتطبيق الجديد غير الويب، الدخول أوّلاً من Host.CreateApplicationBuilder(args) أوضح.
  • WebApplicationBuilder في ASP.NET Core ليس عالماً منفصلاً، بل مدخل وسّع فكرة الـ host نفسها نحو الويب.
  • أي أن Generic Host ليس حديث حاوية DI وحدها، بل آلية تجمع نقطة تجميع التطبيق وإدارة عمره.

باختصار، ما إن يتجاوز التطبيق قليلاً حدّ «اقرأ الوسائط واعرض مرّة وانتهِ» حتى يبدأ Generic Host بالتأثير بقوّة. وفي المقابل ليس شيئاً يُجلَب حتماً في كلّ مرّة إلى أداة صغيرة لم تبلغ ذلك بعد.

النطاق الذي يبدأ فيه Generic Host بالتأثيرلا يُجلَب Generic Host في كلّ مرّة إلى أداة صغيرة تعرض مرّة وتنتهي، ويبدأ بالتأثير بقوّة ما إن يتجاوز التطبيق ذلك قليلاً.لا يُجلَب في كلّ مرّةيبدأ بالتأثير بقوّةأداة تعرض مرّة وتنتهيGeneric Hostتطبيق يتجاوز ذلك قليلاً

الشكل 1: ما إن يتجاوز التطبيق قليلاً حدّ «اعرض مرّة وانتهِ» حتى يبدأ Generic Host بالتأثير.

1.1. نثبّت المصطلحات أوّلاً

هذه المقالة تستخدم بعد ذلك مجازات كثيرة، لذا أضع الصياغة الدقيقة أوّلاً.

المصطلح بالدقة المجاز في هذه المقالة
DI (حقن التبعيات / Dependency Injection) أسلوب يتلقّى فيه الصنف ما يحتاجه من الخارج بدل أن ينشئه بـ new. موضع تسجيل الأطراف المطلوب تمريرها هو حاوية DI (IServiceProvider)، و Generic Host يحملها من البداية التوصيل
Builder (HostApplicationBuilder) كائن لتجميع الـ host. يحمل خصائص مثل Services و Configuration و Logging، وإليها تسجّل. التطبيق لا يعمل حتى استدعاء Build() منصّة التجميع
Host (IHost) التطبيق المجمَّع نفسه الناتج عن Build(). يحمل حاوية DI والإعدادات والسجلات و hosted service، ويتولّى المسار من البدء حتى الإيقاف عبر Run() / RunAsync() الأساس
Hosted service (IHostedService / BackgroundService) وعاء معالجة يتحرّك مع بدء الـ host وإيقافه. عند إقلاع الـ host يُستدعى StartAsync، ومع BackgroundService يجري ExecuteAsync العمل المقيم
Lifetime إدارة المسار من بدء التطبيق حتى إيقافه. تستقبل إشارات مثل Ctrl+C و SIGTERM وإيقاف الخدمة، وتوحّد طريقة التوقّف العمر

أكثر ما يختلط هو Builder و Host. Builder جهة التجميع، و Host نتيجة التجميع، و Build() هو الحدّ بينهما. إن ثبت هذا، صارت مجازات لاحقة مثل «الأساس» و«الصندوق» و«المدخل» و«البوابة» تُقرأ دون حيرة عمّا تشير إليه.

الحدّ بين Builder و HostBuilder جهة التجميع، و IHost نتيجة التجميع، واستدعاء Build هو الحدّ بينهما.Build()Builder (جهة التجميع)IHost (نتيجة التجميع)من البدء حتى الإيقاف عبر Run / RunAsync

الشكل 2: Builder جهة التجميع، و IHost نتيجة التجميع، و Build() هو الحدّ بينهما.

إن كان DI جديداً عليك، فكّر هكذا فلا تخطئ. بدل كتابة سلسلة new بنفسك، تسجّل عند الإقلاع «إذا لزم هذا النوع فمرّر هذا التنفيذ»، والمستقبِل يأخذه فقط كوسيط في المنشئ. موضع ذلك التسجيل هو builder.Services.

2. جداول للنظر أوّلاً

2.1. ما يحمله Generic Host

فصل محتويات هذا الصندوق أوّلاً يسهّل الأمر كثيراً.

العنصر ما يتولّاه Generic Host ماذا يفيد
DI تجميع الخدمات من IServiceCollection يسهّل تقليل سلسلة new
Configuration جمع appsettings.json ومتغيّرات البيئة ووسائط سطر الأوامر وغيرها يسهّل التعامل مع فروق البيئات
Logging بناء الأساس لاستخدام ILogger<T> يسهّل استبدال وجهة السجلات لاحقاً
Hosted service بدء وإيقاف IHostedService / BackgroundService يسهّل فصل العمل المقيم عن نواة التطبيق
Lifetime التعامل مع البدء والإيقاف عبر IHostApplicationLifetime و IHostEnvironment وغيرهما يسهّل توحيد طريقة الإنهاء عند Ctrl+C و SIGTERM وإيقاف الخدمة

المهم هنا أن Generic Host ليس «غلاف DI مريحاً واحداً». عمليّاً، أقلّ نظرة تخطئ هي اعتباره صندوقاً يوصل محيط مدخل التطبيق معاً.

2.2. الفرق بين الـ builder

هنا أيضاً أسرع أن نرى جدولاً واحداً أوّلاً.

المدخل الاستخدام الرئيس طعم الكتابة الاختيار الأوّل
Host.CreateApplicationBuilder(args) تطبيق جديد غير ويب مثل console / worker الكتابة مباشرة إلى builder.Services / builder.Configuration / builder.Logging هذا للجديد
Host.CreateDefaultBuilder(args) كود قائم أو تكوين قديم قائم على توابع التوسعة تسلسل ConfigureServices وما شابه هذا إن وُجد أصل قائم
WebApplication.CreateBuilder(args) تطبيق ويب / API في ASP.NET Core مدخل أضاف إلى Generic Host ما يلزم الويب هذا للويب

CreateApplicationBuilder و CreateDefaultBuilder ليسا قصّة «أحدهما ميزة جديدة والآخر شيء منفصل».

كلاهما يحمل الوظائف الجوهرية نفسها والسلوك الافتراضي. الفرق أساساً أسلوب الكتابة.

للتطبيق الجديد غير الويب، الدخول اليوم من Host.CreateApplicationBuilder(args) أوضح. و WebApplication.CreateBuilder(args) يسهل ترتيبه إذا اعتبرته مدخلاً وسّع ذلك المسار نحو الويب.

علاقة المداخل الثلاثةCreateApplicationBuilder و CreateDefaultBuilder يحملان الوظائف الجوهرية نفسها والسلوك الافتراضي ويختلفان بأسلوب الكتابة فقط، و WebApplication.CreateBuilder مدخل وسّع ذلك المسار نحو الويب.مدخل وسّعه نحو الويبCreateApplicationBuilderالوظائف الجوهرية والسلوك الافتراضي نفسيهماCreateDefaultBuilderأسلوب الكتابة المباشرةأسلوب التسلسلWebApplication.CreateBuilder

الشكل 3: الـ builderان يختلفان بأسلوب الكتابة فوق الوظائف الجوهرية والسلوك الافتراضي نفسيهما، وللويب مدخل وسّع ذلك.

2.3. لماذا توجد مداخل متعدّدة؟

المداخل متعدّدة لأن جهة الويب وجهة غير الويب نمتا منفصلتين ثم التقتا.

  • كان لـ ASP.NET Core في الأصل Web Host خاص بالويب (IWebHostBuilder)، وأُعدّ Generic Host (IHostBuilder) منفصلاً لتطبيقات غير الويب.
  • بعد ذلك مالت جهة ASP.NET Core نحو Generic Host، فصار الويب وغير الويب يقومان على فكرة الـ host نفسها.
  • ثم أُضيف، إلى جانب أسلوب ربط الاستدعاءات الراجعة (ConfigureServices وغيره)، مدخل يكتب مباشرة إلى الخصائص (builder.Services وغيره). Host.CreateApplicationBuilder و WebApplication.CreateBuilder من هذا الجانب.

في التوثيق الرسمي الحالي، مسار Host.CreateApplicationBuilder (IHostApplicationBuilder) موجَّه للمشاريع الجديدة وهو افتراضي القوالب الحالية، ومسار Host.CreateDefaultBuilder (IHostBuilder) الطريقة التقليدية المتبقية للتوافق مع الكود القائم. وموثَّق أيضاً أن كليهما يحمل الوظائف الجوهرية نفسها والسلوك الافتراضي.

إن أتيت من كود عهد .NET Framework أو .NET Core 3.1 فقد تتساءل «لماذا أسلوبان للكتابة؟»، لكن الأمر أوضح إذا رأيته ليس شيئين جديد وقديم متجاورين، بل مداخل زادت أثناء الالتقاء. إن لم يكن هناك سبب لمواءمة أصل قائم، فالجديد يكفي معه Host.CreateApplicationBuilder.

خلفية تعدّد المداخلكان Web Host الخاص بالويب و Generic Host لغير الويب منفصلين، ثم التقت جهة ASP.NET Core مع Generic Host، ثم أُضيف مدخل يكتب مباشرة إلى الخصائص.Web Host (IWebHostBuilder)ASP.NET Core يلتقي مع Generic HostGeneric Host (IHostBuilder)إضافة مدخل يكتب مباشرة إلى الخصائصCreateApplicationBuilder و WebApplication.CreateBuilder

الشكل 4: Web Host و Generic Host نمتا منفصلتين ثم التقتا، وفي أثناء ذلك زاد مدخل أسلوب الكتابة المباشرة.

3. الصورة الكاملة لـ Generic Host (رسم)

إذا رسمنا الصورة الكاملة بصورة تقريبية، يكون كالتالي.

args / متغيّرات البيئة / appsettings.jsonHost.CreateApplicationBuilder(args)builder.Configurationbuilder.Servicesbuilder.LoggingIHostedService / BackgroundServicebuilder.Build()IHostRun / RunAsyncالبدء والإيقاف و Ctrl+C و SIGTERM

الشكل 5: تسجّل الإعدادات والخدمات والسجلات في الـ builder، وتشغّل IHost الناتج عن Build() عبر Run/RunAsync، فيتّصل العمر ببدء hosted service وإيقافه.

عادة تصنع الـ builder في Program.cs، وتضيف الخدمات إلى builder.Services، وتضبط builder.Configuration أو builder.Logging حسب الحاجة، ثم تستدعي Build() لتحصل على IHost، وتشغّله بـ Run() / RunAsync().

المكسب الهادئ الكبير أن كثيراً من الأشياء محمولة أصلاً عند Host.CreateApplicationBuilder(args). افتراضيّاً يدخل مثلاً ما يلي.

  • جذر المحتوى هو الدليل الحالي
  • تكوين الـ host من متغيّرات بيئة ذات بادئة DOTNET_ ووسائط سطر الأوامر
  • تكوين التطبيق من appsettings.json و appsettings.{Environment}.json و user secrets في Development ومتغيّرات البيئة ووسائط سطر الأوامر
  • السجلات: Console / Debug / EventSource / EventLog (Windows فقط)
  • في بيئة Development: التحقّق من النطاق والتحقّق من التبعيات

أي أنك لا توصّل كل شيء من الصفر بلا تفكير، بل يُوضَع من البداية «أساس يكفي للاستخدام العادي».

الافتراضات المحمولة من البدايةعند Host.CreateApplicationBuilder تكون تكوينات الـ host والتطبيق والسجلات الافتراضية محمولة أصلاً، فيُوضَع من البداية أساس يكفي للاستخدام العادي.CreateApplicationBuilder(args)تكوين الـ host (عائلة DOTNET_ والوسائط)تكوين التطبيق (appsettings.json وغيره)السجلات الافتراضية (Console وغيره)أساس يكفي للاستخدام العادي

الشكل 6: عند صنع الـ builder تكون افتراضات التكوين والسجلات محمولة، ولست توصّل من الصفر.

4. ماذا يفيد Generic Host؟

4.1. تجميع معالجة الإقلاع في موضع واحد

أهدأ أثر كبير لـ Generic Host أن مدخل التطبيق يصير أقلّ تشتّتاً.

ما إن ينمو التطبيق قليلاً حتى يزيد حول Main ما يلي.

  • قراءة ملفات الإعداد
  • الاستبدال حسب البيئة
  • تهيئة المسجّل
  • تجميع HttpClient و repository و service
  • بدء معالجة الخلفية
  • التنظيف عند إشارة الإنهاء

إن وصّلت هذا كلّه يدوياً بلا host، بدا خفيفاً أوّلاً، ثم يلتصق المدخل تدريجيّاً لاحقاً.

مع Generic Host يتّضح Program.cs كـ «موضع تجميع التبعيات». هذا الترتيب وحده يغيّر سهولة مراجعة الشيفرة كثيراً.

معالجة الإقلاع تتجمّع في موضع واحدالتوصيل اليدوي بلا host يجعل المدخل يلتصق لاحقاً، أمّا Generic Host فيجعل Program.cs موضع تجميع التبعيات بوضوح.توصيل يدوي بلا hostالمدخل يلتصق لاحقاًاستخدام Generic HostProgram.cs يصير موضع التجميعمراجعة الشيفرة أسهل

الشكل 7: التوصيل اليدوي يجعل المدخل يلتصق تدريجيّاً، والتجميع على الـ host يجعل Program.cs موضع التجميع بوضوح.

4.2. DI والإعدادات والسجلات تتّصل من البداية

مع Generic Host تركب DI والإعدادات والسجلات الأساس نفسه من البداية.

مثلاً يستطيع طرف الصنف تلقّي هذه الأشياء بشكل طبيعي.

  • ILogger<T>
  • IConfiguration
  • IHostEnvironment
  • IOptions<T>

ما يؤثّر هنا أن طريقة قراءة الإعدادات وطريقة صنع الخدمات لا تميلان إلى أسلوبين منفصلين.

إن كان الإعداد واحداً أو اثنين، تكفي قراءة IConfiguration["Section:Key"] مباشرة. غير أنه في العمل اليومي إذا نمت الإعدادات، فالأأمن جمع كل قسم في صنف عبر IOptions<T>. المقياس تقريباً تجاوز مفاتيح النصّ خمسة. عند هذا الحجم تبدأ أخطاء الكتابة بالظهور كفشل لا يُلاحَظ حتى وقت التشغيل، ويصعب تتبّع أي مفتاح يُقرأ من أين.

بالمثل، السجلات أوضح إن حقنت ILogger<T> في الأصناف التي تحتاجه، بدل صنع ILoggerFactory يدوياً في مواضع متفرّقة.

فائدة Generic Host أنه لا يفصل هذه الأمور، بل يمكن تناولها معاً كأساس للتطبيق كلّه.

كيف تُنمّى طريقة قراءة الإعداداتإن كان الإعداد واحداً أو اثنين تكفي قراءة IConfiguration مباشرة، وما إن تتجاوز مفاتيح النصّ خمسة حتى يصير جمع كل قسم في صنف عبر IOptions أأمن.إعداد واحد أو اثنانقراءة IConfiguration مباشرةمفاتيح النصّ تتجاوز خمسةالجمع في أصناف عبر IOptionsأخطاء الكتابة لا تُلاحَظ حتى وقت التشغيل

الشكل 8: مع قلّة الإعدادات تكفي القراءة المباشرة، وما إن تتجاوز المفاتيح خمسة حتى يصير الجمع عبر IOptions أأمن.

4.3. يسهّل الإنهاء السليم والتشغيل المقيم

Generic Host لا يتولّى «كيف يبدأ» فقط، بل «كيف يتوقّف» أيضاً.

عند إقلاع الـ host يُستدعى StartAsync لكل IHostedService مسجَّل. في خدمات worker يجري ExecuteAsync لـ hosted service بما فيه BackgroundService.

«الإنهاء السليم» هنا ليس قطع المعالجة فجأة، بل الإنهاء بهذا الترتيب:

  • بثّ إشارة التوقّف
  • الخروج من الحلقات والانتظار
  • تنظيف الاتّصالات والموارد

في تطبيق يعمل طويلاً هذه النقطة مهمّة جدّاً. يسهل توحيد طريقة توقّف التطبيق كلّه عند أحداث مثل Ctrl+C و SIGTERM وإيقاف الخدمة.

وإذا أراد التطبيق طلب الإنهاء من جهته، يُستخدم IHostApplicationLifetime.StopApplication(). يمكن إرسال إشارة «العمل انتهى، أريد التوقف بنظافة» في سياق الـ host.

ترتيب الإنهاء السليمعند استقبال Ctrl+C أو SIGTERM أو إيقاف الخدمة يُبثّ إشارة التوقّف، ثم يُخرَج من الحلقات والانتظار، ثم تُنظَّف الاتّصالات والموارد.StopApplication()Ctrl+C / SIGTERM / إيقاف الخدمةبثّ إشارة التوقّفالخروج من الحلقات والانتظارتنظيف الاتّصالات والمواردطلب إنهاء من جهة التطبيق

الشكل 9: الإنهاء السليم يمرّ بإشارة التوقّف ثم الخروج من الحلقة ثم التنظيف، ومن جهة التطبيق يمكن إرسال الإشارة نفسها عبر StopApplication().

5. الحدّ الأدنى من التكوين

5.1. أصغر مثال على تطبيق console

المهم أوّلاً أن استخدام Generic Host لا يعني حتماً صنع BackgroundService.

حتّى أداة console تعمل مرّة واحدة، إن احتجت DI أو إعدادات أو سجلات فـ Generic Host كافٍ تماماً.

إن أضفته لاحقاً إلى مشروع console عادي، فأوّل خطوة هي الإشارة إلى Microsoft.Extensions.Hosting.

dotnet add package Microsoft.Extensions.Hosting

أصغر مثال لـ Program.cs مثلاً كالتالي.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<JobRunner>();

using IHost host = builder.Build();

try
{
    JobRunner runner = host.Services.GetRequiredService<JobRunner>();
    await runner.RunAsync();
    return 0;
}
catch (Exception ex)
{
    ILogger logger = host.Services
        .GetRequiredService<ILoggerFactory>()
        .CreateLogger("Program");

    logger.LogError(ex, "Unhandled exception occurred during job execution.");
    return 1;
}

internal sealed class JobRunner(
    ILogger<JobRunner> logger,
    IConfiguration configuration,
    IHostEnvironment hostEnvironment)
{
    public Task RunAsync()
    {
        string message = configuration["Sample:Message"] ?? "(no message)";

        logger.LogInformation("Environment: {EnvironmentName}", hostEnvironment.EnvironmentName);
        logger.LogInformation("Message: {Message}", message);

        return Task.CompletedTask;
    }
}

عند dotnet run يظهر في وحدة التحكّم ما يلي (قيمة Message تأتي من appsettings.json الذي نضعه في 5.2 التالي).

info: JobRunner[0]
      Environment: Production
info: JobRunner[0]
      Message: hello from Generic Host

ما على يمين info: هو فئة السجلّ (هنا اسم النوع لأنّه ILogger<JobRunner>) ومعرّف الحدث. مسجّل وحدة التحكّم الافتراضي يخرج بهذا الشكل: «السطر الأوّل الفئة، والسطر الثاني المتن». كون Environment هو Production لأن القيمة الافتراضية عند عدم ضبط DOTNET_ENVIRONMENT ولا ASPNETCORE_ENVIRONMENT. للتبديل أثناء التطوير شغّل مع DOTNET_ENVIRONMENT=Development.

إن لم يكن مقيماً طويلاً، فلا حاجة للوصول حتى RunAsync(). Build() ثم حلّ الخدمات اللازمة، وإنهاء العمل بعد ذلك مباشرة. حتّى بهذا الشكل تُستفاد فائدة Generic Host تماماً.

هذه النقطة أهمّ ممّا تبدو. لا حاجة لجلب قالب Worker في كلّ مرّة حتى إلى عمل قصير العمر.

طريقة الاستخدام في عمل قصير العمرإن لم يكن مقيماً طويلاً فلا حاجة للوصول حتى RunAsync؛ يكفي Build ثم حلّ الخدمات اللازمة وإنهاء العمل بعد ذلك، وتبقى فائدة Generic Host.Build()حلّ الخدمات اللازمةإنجاز العملالإنهاء مباشرةلا حاجة للوصول حتى RunAsync()

الشكل 10: في عمل قصير العمر يكفي Build() ثم حلّ الخدمة وتشغيلها والإنهاء، وتبقى فائدة الـ host.

5.2. appsettings.json

للمثال أعلاه يكفي ملف إعداد بهذا الحدّ الأدنى.

{
  "Sample": {
    "Message": "hello from Generic Host"
  }
}

موضع تعثّر نمطي واحد. في مشروع console، إضافة appsettings.json وحدها لا تنسخه إلى مجلد الخرج. إمّا ضبط «نسخ إلى دليل الخرج» في خصائص المشروع على «نسخ إذا كان أحدث»، أو كتابة التالي في csproj.

<ItemGroup>
  <Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

معرفة العَرَض عند النسيان تسرّع التشخيص. Generic Host يقرأ appsettings.json كملف اختياري، فلا استثناء إن غاب. ببساطة لا تُؤخَذ القيمة. في المثال الأدنى يظهر Message: (no message). عندما «لا يظهر خطأ لكن الإعداد لا يعمل»، افحص أوّلاً وجود appsettings.json في مجلد الخرج.

العَرَض عندما يغيب appsettings.jsonحتى إن لم يُنسَخ appsettings.json إلى مجلد الخرج يُقرأ كملف اختياري فلا استثناء، وببساطة لا تُؤخَذ القيمة.الملف غائب عن مجلد الخرجيُقرأ كملف اختياريلا استثناءببساطة لا تُؤخَذ القيمةافحص مجلد الخرج أوّلاً

الشكل 11: غياب appsettings.json لا يرفع استثناء بل «لا تُؤخَذ القيمة» فقط، لذا افحص مجلد الخرج أوّلاً.

في هذا المثال نقرأ configuration["Sample:Message"] خاماً. إن كنت تنظر إلى قيمة أو اثنتين فهذا كافٍ.

غير أنه في العمل اليومي إذا نمت الإعدادات، فالإزاح نحو:

  • فصل كل قسم إلى صنف
  • الحقن عبر IOptions<T>
  • التحقّق عند الإقلاع

يسهّل تجنّب نثر مفاتيح النصّ.

وأيضاً، القيم الافتراضية لـ Generic Host لا تصل appsettings.json فقط، بل تصل أيضاً appsettings.{Environment}.json ومتغيّرات البيئة ووسائط سطر الأوامر، فيصير «الاستبدال أثناء التطوير فقط» و«التجاوز بمتغيّرات البيئة في الإنتاج» طبيعيّاً جدّاً.

5.3. إضافة BackgroundService

لمعالجة تعمل طويلاً، استخدام BackgroundService أوضح كثيراً.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddScoped<PollingJob>();
builder.Services.AddHostedService<PollingWorker>();

using IHost host = builder.Build();
await host.RunAsync();

internal sealed class PollingWorker(
    IServiceScopeFactory scopeFactory,
    ILogger<PollingWorker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));

        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            using IServiceScope scope = scopeFactory.CreateScope();
            PollingJob job = scope.ServiceProvider.GetRequiredService<PollingJob>();

            await job.RunAsync(stoppingToken);
            logger.LogInformation("Polling completed.");
        }
    }
}

internal sealed class PollingJob(ILogger<PollingJob> logger)
{
    public Task RunAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("Do work here.");
        return Task.CompletedTask;
    }
}

في هذا المثال نقطتان تستحقان النظر.

  1. متن BackgroundService هو ExecuteAsync
  2. إن احتجت تبعية scoped فاصنع نطاقاً عبر IServiceScopeFactory

BackgroundService نفسه بلا نطاق افتراضي. مثلاً إن أردت استخدام خدمة scoped مثل DbContext، فالشكل الآمن حلّ جهة العمل داخل النطاق كما أعلاه.

شكل استخدام scoped داخل BackgroundServiceBackgroundService نفسه بلا نطاق افتراضي، لذا الشكل الآمن حقن IServiceScopeFactory وصنع نطاق داخل ExecuteAsync وحلّ خدمة جهة العمل داخل ذلك النطاق.BackgroundService (بلا نطاق افتراضي)حقن IServiceScopeFactoryصنع نطاق داخل ExecuteAsyncحلّ العمل داخل النطاق

الشكل 12: في BackgroundService بلا نطاق افتراضي تُصنع النطاق عبر IServiceScopeFactory ويُحَلّ العمل داخله.

اختيار أداة التشغيل الدوري نفسها موضوع منفصل، غير أن PeriodicTimer هادئ جدّاً إن كنت تكتب على أساس async. هذا يتّصل أيضاً بمقالة المؤقّتات ذات الصلة.

6. أنماط نمطية

6.1. أدوات console قصيرة العمر

حتّى تطبيق ينجز عملاً مرّة وينتهي، مثل الدفعات وأدوات التحويل وأوامر الصيانة، يمكنه استخدام Generic Host بشكل طبيعي.

يناسبه مواضع كهذه.

  • قراءة ملف إعداد
  • إخراج سجلات
  • حقن HttpClient أو repository
  • إعادة رمز إنهاء

إن جلبت إلى هذا النوع فوراً BackgroundService و RunAsync()، صار ثقيلاً قليلاً واستخدمت إدارة عمر الـ host بما يفوق الحاجة.

في عمل قصير العمر يكفي حلّ JobRunner وتشغيله كما في المثال الأدنى السابق.

الاختيار في أداة console قصيرة العمرفي تطبيق ينجز عملاً مرّة وينتهي يصير جلب BackgroundService و RunAsync استخداماً زائداً لإدارة العمر، ويكفي حلّ الخدمة وتشغيلها.كافٍيميل إلى الزيادةتطبيق ينجز عملاً مرّة وينتهيحلّ JobRunner وتشغيلهBackgroundService و RunAsync()

الشكل 13: جلب BackgroundService إلى تطبيق لمرّة واحدة زائد، ويكفي حلّ الخدمة وتشغيلها.

6.2. worker / خدمات خلفية

في معالجة مثل worker مقيم والاستطلاع واستهلاك الطوابير والمراقبة والتشغيل الدوري، تركيب Generic Host و BackgroundService أوضح كثيراً.

ما يفيد خصوصاً:

  • مسار البدء والإيقاف يتوحّد في جهة الـ host
  • السجلات والإعدادات و DI متاحة من البداية
  • يسهل بثّ الإلغاء عند Ctrl+C أو إشارة التوقّف
  • يسهل فصل متن العمل المقيم عن Program.cs

ثم يسهل ربطه أيضاً بسياق Windows Service أو الحاويات. إن كنت تنمّيه كتطبيق مقيم، فـ Generic Host أساس طبيعي جدّاً.

عند التحويل إلى Windows Service، البحث عن الملفات بافتراض الدليل الحالي أقلّ أماناً من الانطلاق من IHostEnvironment.ContentRootPath. لأن «مسار أساس التطبيق» يُحسَم في سياق الـ host.

أساس worker مقيمفي worker مقيم أو تشغيل دوري تركيب Generic Host و BackgroundService أوضح، ويسهل ربطه أيضاً بسياق Windows Service أو الحاويات.worker مقيم / تشغيل دوريGeneric Host و BackgroundServiceWindows Serviceإقامة في حاويةالبحث انطلاقاً من ContentRootPath

الشكل 14: العمل المقيم أوضح مع تركيب الـ host و BackgroundService، ويسهل تنميته نحو Windows Service أو الحاويات.

6.3. يوجد أيضاً تحت ASP.NET Core

تطبيقات الويب / API تستخدم WebApplication.CreateBuilder(args)، فقد تبدو للوهلة الأولى عالماً منفصلاً عن Generic Host.

غير أن الإحساس متّصل بقوّة.

  • builder.Services
  • builder.Configuration
  • builder.Logging

تشابه طعم الكتابة لهذا السبب.

في ASP.NET Core يدخل بدء خادم HTTP أيضاً داخل عمر الـ host. أي أن فهم Generic Host يؤثّر أيضاً بمعنى أن قراءة Program.cs في جهة الويب تصير أوضح: «لماذا نلمس هنا DI والإعدادات والسجلات؟».

الويب أيضاً على الـ host نفسهتطبيق ASP.NET Core أيضاً يقوم عبر WebApplication.CreateBuilder على فكرة الـ host نفسها، وبدء خادم HTTP يدخل داخل عمر الـ host.تطبيق ASP.NET CoreWebApplication.CreateBuilderعلى فكرة الـ host نفسهابدء خادم HTTP أيضاً داخل العمر

الشكل 15: builder جهة الويب أيضاً على فكرة الـ host نفسها، وبدء خادم HTTP يدخل في العمر.

7. الحالات التي يناسبه

مواضع ينسجم فيها Generic Host بسهولة.

  • تطبيق console يستخدم إعدادات وسجلات و DI
  • worker يشبه مستهلك طابور أو مستطلع أو مراقب أو مجدول
  • تطبيق طويل التشغيل تريد تنظيفه عند Ctrl+C أو SIGTERM
  • تطبيق قد ينمو لاحقاً نحو إقامة Windows Service / حاوية
  • تطبيق تريد مواءمة أسلوب مجموعات التوسعة نفسها كما في ASP.NET Core

القاسم المشترك: «لا تريد أن يصير مدخل التطبيق وإدارة عمره عشوائيّين».

غير أن هذا وحده يصعّب رسم الخطّ، لذا أضع مقياساً أيضاً. إذا انطبق اثنان أو أكثر ممّا يلي، فغالباً أسهل لاحقاً الركوب على Generic Host من البداية.

مقياس قرار الاعتمادإن انطبق اثنان أو أكثر من بنود المقياس فاركب Generic Host من البداية، وإن لم ينطبق أيّ بند فيمكن اعتباره غير لازم.اثنان أو أكثرلا شيءعُدّ ما ينطبق من المقياساركب Generic Host من البدايةيمكن اعتبار Generic Host غير لازم

الشكل 16: إن انطبق اثنان أو أكثر فاركب من البداية، وإن لم ينطبق أيّ بند فلا حاجة لجلبه.

المقياس الخطّ العملي
عدد الإعدادات ثلاثة إعدادات أو أكثر تتغيّر حسب البيئة (جهة الاتّصال، العتبات، جهة الخرج، إلخ)
السجلات يلزم إبقاؤها في ملف أو Event Log. ليست مجرّد كتابة إلى الخرج القياسي والانتهاء
شكل التشغيل مقيم. أو يعمل بفاصل ثابت مرّة أو أكثر في اليوم
التبعيات ثلاثة أطراف أو أكثر تريد تلقّيها في المنشئ. أو طرف تريد استبداله في الاختبار
العمر عند Ctrl+C أو إيقاف الخدمة يلزم تنظيف في منتصف العمل
المستقبل احتمال التشغيل كـ Windows Service أو في حاوية

8. الحالات التي لا يناسبه / يكون فيها زائداً

في المقابل توجد مواضع لا حاجة لجعل Generic Host البطولة من البداية.

  • أداة صغيرة تقرأ الوسائط مرّة وتطبع مرّة وتنتهي
  • شيفرة تجريب خشنة تُستخدم عشرات الدقائق فقط
  • مشروع مكتبة
  • حالة تقرأ إعداداً واحداً ولا تحتاج حتى DI أو سجلات أو إدارة عمر

هنا الكتابة مباشرة في Main أقلّ قراءة وأقلّ ملفات من إقامة host. كمقياس، إن لم ينطبق أيّ بند من جدول الفصل 7، فلا حرج في اعتبار Generic Host غير لازم.

المهم أن قوّة Generic Host لا تعني أنه لازم لكل ملف تنفيذي.

9. مواضع التعثّر

أخيراً، نقاط يسهل الوقوع فيها عند أوّل تعامل مع Generic Host.

  • رؤية Generic Host كحاوية DI فقط
    • عمليّاً هو أساس يشمل البدء والإيقاف والإعدادات والسجلات و hosted service.
  • بدء تطبيق جديد من Host.CreateDefaultBuilder بالعادة
    • إن لم يكن هناك سبب لمواءمة كود قائم، فـ Host.CreateApplicationBuilder أوضح أوّلاً.
  • وضع خدمة scoped مباشرة في BackgroundService
    • hosted service بلا نطاق افتراضي. صنع نطاق عبر IServiceScopeFactory أأمن.
  • worker ينتهي بمرّة واحدة دون إبلاغ الـ host بالتوقّف
    • إن نفّذت «run once» بقالب Worker، فبدون استدعاء IHostApplicationLifetime.StopApplication() عند انتهاء العمل يستمرّ الـ host في الجري.
  • القطع بـ Environment.Exit رغم إرادة إنهاء سليم
    • إن كنت تستخدم الـ host، فـ StopApplication() أوضح حين تريد التوقّف بنظافة.
  • افتراض current directory في Windows Service
    • البحث عن الملفات أثبت إن انطلق من IHostEnvironment.ContentRootPath.
  • تطويق CLI قصير العمر بـ BackgroundService من البداية
    • لعمل لمرّة واحدة يكفي حلّ صنف خدمة عادي وتشغيله.
  • وضع مؤقّت callback جزافاً في التشغيل الدوري لـ BackgroundService
    • إن كنت تكتب تدفّقاً async فـ PeriodicTimer غالباً أسهل قراءة وأقلّ اضطراباً.

مع Generic Host، فصل «هل هو عمل قصير العمر أم عمل مقيم» أوّلاً وحده يقلّل الحيرة كثيراً.

السؤال الذي يُفصَل أوّلاًافصل أوّلاً هل العمل قصير العمر أم مقيم؛ فالقصير يكفي فيه حلّ صنف خدمة عادي وتشغيله، والمقيم يستخدم BackgroundService وإدارة عمر الـ host.قصير العمرمقيمعمل قصير العمر أم عمل مقيم؟حلّ الخدمة وتشغيلها فقطBackgroundService وإدارة العمر

الشكل 17: فصل قصير العمر عن المقيم أوّلاً وحده يقلّل الحيرة حول مدى استخدام أدوات الـ host.

10. الخلاصة

Generic Host في جملة: أساس يجمع مدخل تطبيق .NET وإدارة عمره.

نراجع النقاط التي تستحق النظر.

  1. Generic Host لا يشمل DI فقط، بل الإعدادات والسجلات ومعالجة الإيقاف و hosted service
  2. للتطبيق الجديد غير الويب، Host.CreateApplicationBuilder(args) أوضح أوّلاً
  3. في عمل قصير العمر يكفي البناء والتشغيل دون BackgroundService
  4. في عمل مقيم، BackgroundService وإدارة عمر الـ host تؤثّران بقوّة
  5. BackgroundService بلا نطاق افتراضي، فاصنع نطاقاً صريحاً للخدمات scoped
  6. WebApplicationBuilder في ASP.NET Core أيضاً، من حيث الفكرة، على المسار نفسه

Generic Host ليس أداة لطقس ثقيل. إذا نمت الإعدادات والسجلات والتبعيات والإقلاع والإيقاف ولو قليلاً، فهو أداة لإبقائها مجمّعة عند المدخل بدل تسريبها إلى الجدران.

في المقابل، إن كانت الأداة صغيرة لا تحتاج ذلك بعد، فلا حاجة لجلبه. إذا أمكن هذا التمييز، لم يعد Generic Host «شيئاً يُوضَع على نحو ما»، بل أساساً عمليّاً واضح موضع الاستخدام.

11. روابط مرجعية

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

ما هو Generic Host؟
أساس يجمع تشغيل تطبيق .NET وعمره في مكان واحد. داخله DI والإعدادات (Configuration) والسجلات و IHostedService / BackgroundService ومعالجة إيقاف التطبيق. ليس مجرّد غلاف لحاوية DI، بل آلية تجمع نقطة تجميع التطبيق وإدارة عمره. يظهر أثره عندما تبدأ الإعدادات والسجلات والتبعيات والإقلاع والإيقاف بالنمو ولو قليلاً.
أيّهما نستخدم: Host.CreateApplicationBuilder أم Host.CreateDefaultBuilder؟
للتطبيق الجديد غير الويب، الدخول من Host.CreateApplicationBuilder(args) أوضح. كلاهما يحمل الوظائف الجوهرية نفسها والسلوك الافتراضي، وليسا علاقة «ميزة جديدة مقابل شيء آخر». الفرق أسلوب الكتابة أساساً: CreateApplicationBuilder يكتب مباشرة إلى builder.Services وما شابه، و CreateDefaultBuilder يسلسل ConfigureServices وما شابه. إن كان هناك سبب لمواءمة كود قائم أو تكوين قديم قائم على توابع التوسعة فاختر CreateDefaultBuilder.
هل يستحق Generic Host الاستخدام في تطبيق console أيضاً؟
إذا احتجت DI أو إعدادات أو سجلات، فأداة console تعمل مرّة واحدة كافية لاستخدامه. لست مضطرّاً لصنع BackgroundService حتماً؛ يكفي Build() ثم حلّ الخدمات اللازمة وإنهاء العمل بعد ذلك، وتبقى فائدة Generic Host. في المقابل هو زائد على أداة صغيرة تقرأ الوسائط مرّة وتطبع مرّة وتنتهي، أو على شيفرة تجريب خشنة، فليس شيئاً يُجلَب في كلّ مرّة حتماً.
كيف نستخدم خدمة scoped داخل BackgroundService؟
ليس لـ BackgroundService نطاق افتراضي، لذا حقن خدمة scoped مباشرة في المنشئ غير آمن. احقن IServiceScopeFactory، وأنشئ نطاقاً صريحاً داخل ExecuteAsync، وحلّ خدمات جهة العمل داخل ذلك النطاق. إن أردت استخدام خدمة scoped مثل DbContext فالوعي بهذا الشكل لازم خصوصاً.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

العودة إلى المدونة