محتويات الدورة

ASP.NET Core / الأساسي: HTTP وتركيب التطبيق / الدرس 6

حقن الاعتماد وأعماره

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

وقت القراءة التقريبي: 4 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • تمييز عقود singleton وscoped وtransient.
  • تجنب الاعتماد الأسير واستخدام مزود عام خفي.
  • فحص مشاركة النطاق والتخلص من موارده في الذاكرة.

يحدد العمر المشاركة والملكية

تشترك singleton طوال عمر الحاوية، وscoped داخل النطاق، وتنشأ transient وفق قواعد الحل. للطلب العادي نطاق، ولا يكتسب العامل الخلفي النطاق نفسه تلقائيا. تؤثر الاختيارات في الحالة وأمان الخيوط والاحتفاظ والتخلص.

أظهر الاعتماد بمعامل المعالج أوالمنشئ، لا بمزود عام يحل أي خدمة لراحة مؤقتة. قد يمد التقاط singleton لاعتماد scoped عمره ويرفضه فحص النطاق. تفيد خيارات التحقق ولا تستنتج كل قاعدة عمر في التطبيق.

يشترك النطاق فينسخةscoped واحدة

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

لا تخزن بيانات طلب متغيرة في singleton لتجنب تمريرها. قد يستعمل كود داخل الطلب نفسه scoped بالتزامن، فلا تعني النطاقية أمان الخيوط. احفظ HttpContext وحالة المستخدم ضمن العمر الصحيح.

csharp فحص هوية خدمة ضمن نطاق صريح
using System;
using Microsoft.Extensions.DependencyInjection;

var collection = new ServiceCollection();
collection.AddScoped<Marker>();
using var provider = collection.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true });
using var first = provider.CreateScope();
using var second = provider.CreateScope();
var a = first.ServiceProvider.GetRequiredService<Marker>();
var b = first.ServiceProvider.GetRequiredService<Marker>();
var c = second.ServiceProvider.GetRequiredService<Marker>();
Console.WriteLine($"sameScope={ReferenceEquals(a, b)} differentScopes={ReferenceEquals(a, c)}");

internal sealed class Marker { }
الناتج المتوقع
sameScope=True differentScopes=False

يعيد الحلان في النطاق الأول Marker نفسها، ويعطي النطاق الثاني كائنا مستقلا. لا يشغّل المثال مضيف HTTP أو خدمة تطبيق مهيأة.

تتخلص الحاوية منمملوكها عندالحدالصحيح

يحل البرنامج موردا scoped قابلا للتخلص ويلاحظ تحريره مرة عند التخلص من النطاق. لا تتخلص الخدمة من اعتماد مستعار من DI لانتهاء طريقتها. ويحتاج مورد ينشئه مصنع خارج ملكية الحاوية مالكا صريحا؛ كما يحتاج التخلص غير المتزامن مسارا مناسبا.

تختلف مسؤولية singleton مسجلة كنسخة جاهزة من نسخة أنشأتها الحاوية وفق القواعد الموثقة. راجع الفرق قبل افتراض تحرير كل شيء تلقائيا. لا تلتقط transient قابلة للتخلص بحل من الجذر طويل العمر لقصر الشيفرة فقط.

csharp ملاحظة التخلص الذي يملكه النطاق
using System;
using Microsoft.Extensions.DependencyInjection;

var collection = new ServiceCollection();
collection.AddScoped<OwnedResource>();
using var provider = collection.BuildServiceProvider();
using (var scope = provider.CreateScope())
{
    _ = scope.ServiceProvider.GetRequiredService<OwnedResource>();
    Console.WriteLine($"inside={OwnedResource.Disposals}");
}
Console.WriteLine($"after={OwnedResource.Disposals}");

internal sealed class OwnedResource : IDisposable
{
    public static int Disposals { get; private set; }
    public void Dispose() => Disposals++;
}
الناتج المتوقع
inside=0
after=1

يبقى المورد حيا داخل النطاق، ثم يجري التخلص منه قبل السطر الأخير. يفحص المثال عمر كائن اصطناعي فقط.

يحتاج العملالخلفي نطاقاومالكا للاكتمال

قد ينشئ العامل المستضاف singleton نطاقا لوحدة عمل محدودة وينتظر مستخدميها قبل التخلص. لا تحقن DbContext طلبية في singleton ولا تحفظ HttpContext بعد الطلب. يوضح المصنع الإنشاء ولا يلغي مسؤولية التنظيف والفشل.

تجنب BuildServiceProvider أثناء التسجيل؛ فقد يخلق حاوية مستقلة ويكرر رسم singleton. فضل المصانع والخيارات المدعومة والمزود المركب. ضع اختلاف تسجيل البيئة عند أصل التركيب، مع اعتماد المجال على عقود ضيقة.

اختبار افتراضالعمر لا نجاحالحل فقط

راجع هوية الخدمة وحالتها والمستدعين المتزامنين والملكية والإلغاء والتخلص عند الفشل. قد ينجح الحل مرة مع احتفاظ زائد أو استخدام بعد النطاق. تكشف أدوات البناء والنطاق بعض الرسم ولا تستنتج عمر الأعمال كله.

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

الأخطاء الشائعة وإصلاحها

يلتقط singleton اعتمادا scoped.

لماذا يحدث؟
يحتفظ مالك أطول عمرا باعتماد أقصر من نطاقه المقصود.
كيف تصلحه؟
استخدم حدا مبررا للنطاق أو المصنع لكل عملية.
كيف تتجنب تكراره؟
فعّل فحص النطاق المناسب واختبر عقد الملكية.

تعد الخدمة آمنة للخيوط لأنها scoped.

لماذا يحدث؟
اختلط نطاق المشاركة بضبط الوصول المتزامن.
كيف تصلحه؟
راجع جميع المستخدمين المتزامنين والوصول إلى الحالة.
كيف تتجنب تكراره؟
أبق حالة الطلب المتغيرة محدودة وصريحة.

ينشأ مزود إضافي أثناء تسجيل التطبيق.

لماذا يحدث؟
يخلق التركيب رسوما مكررة للخدمات وملكية مواردها.
كيف تصلحه؟
استخدم مزود التطبيق المقصود ومصانع التسجيل المدعومة.
كيف تتجنب تكراره؟
راجع إنشاء كل مزود كقرار يتعلق بدورة الحياة.

اختبر فهمك

1. تحتاج مهمة خلفية إلى DbContext واحدة لكل دفعة. ما نمط العمر الذي ينبغي مراجعته بدلا من حقنها في عامل singleton؟

اعرض الحل المشروح

أنشئ نطاقا أو استخدم مصنع سياق مناسبا لكل دفعة محدودة، وحل السياق أو اصنعه هناك، وانتظر العمل ثم تخلص من المورد والنطاق المملوكين. لا تشارك سياقا واحدا بين عمليات متوازية. صرح بالإلغاء والتنظيف. يعتمد التصميم على EF والمزود؛ ولا يثبت مثال DI وحده صحة وحدة عمل قاعدة.

2. لماذا لا ينبغي للخدمة استدعاء Dispose على كل اعتماد تتلقاه؟

اعرض الحل المشروح

لا يعني تلقي المرجع ملكية عمره. قد تشاركه DI داخل نطاق أو عبر التطبيق، فيكسر التخلص المبكر مستخدمين آخرين. اتبع ملكية الحاوية أو المصنع الموثقة. قد يحتاج ما أنشأته الخدمة صراحة using أو await using، بينما يبقى المستعار عادة مسؤولية مالكه.

أهم ما تعلمته

  • تحدد الأعمار المشاركة والتعرض للتزامن ومسؤولية التخلص.
  • لا يجعل النطاق الحالة آمنة للخيوط تلقائيا.
  • على العمل الخلفي امتلاك الموارد التي يستخدمها وانتظار اكتمال استخدامها.

المراجع الرسمية