📑 فهرست مطالب
🔍 گام ۱: تحلیل دقیق ریشه خطا
پیغام خطای DI به یک مشکل کلاسیک در مدیریت طول عمر سرویسها اشاره میکند:
⚠️ پیغام خطا:
Cannot consume scoped service 'IXDefaultAIOCRService' from singleton 'IXFileContentExtractor'
قانون طلایی DI در .NET Core:
| قانون | توضیح |
|---|---|
| ✅ مجاز | Singleton میتواند Singleton را مصرف کند |
| ✅ مجاز | Scoped میتواند Singleton را مصرف کند |
| ✅ مجاز | Transient میتواند هر چیزی را مصرف کند |
| ❌ ممنوع | Singleton نمیتواند Scoped را مصرف کند (خطای فعلی) |
| ❌ ممنوع | Singleton نمیتواند Transient را مصرف کند |
💡 دلیل منطقی: اگر یک Singleton بتواند یک Scoped service را مصرف کند، آن Scoped service عملاً به یک Singleton تبدیل میشود (چون فقط یک بار ساخته شده و در تمام درخواستها استفاده میشود). این مسئله باعث نشت حافظه و رفتار غیرقابل پیشبینی میشود.
🔗 گام ۲: زنجیره وابستگی مشکلساز
وضعیت فعلی (قبل از اصلاح):
XFileContentExtractor
(Singleton) → XVisionFileContentExtractor
(Singleton) → IXDefaultAIOCRService
(Scoped) ❌
(Singleton) → XVisionFileContentExtractor
(Singleton) → IXDefaultAIOCRService
(Scoped) ❌
Singleton
Scoped
خطا
بررسی وابستگیهای XDefaultAIOCRService:
| وابستگی | نوع ثبت فعلی | آیا Stateful است؟ |
|---|---|---|
XAiApiConfiguration |
Singleton | ❌ خیر (فقط خواندنی) |
ILogger<XDefaultAIOCRService> |
Singleton | ❌ خیر (Thread-safe) |
string model |
Primitive | ❌ خیر |
string prompt |
Primitive | ❌ خیر |
✅ نتیجهگیری کلیدی: کلاس
XDefaultAIOCRService هیچ وابستگی Scoped یا Stateful ندارد و کاملاً Stateless است. بنابراین میتواند با خیال راحت به Singleton ارتقا یابد.
💡 گام ۳: راهحل اصلاحی
تنها یک تغییر کوچک در فایل Startup.cs لازم است: تغییر طول عمر IXDefaultAIOCRService از Scoped به Singleton.
زنجیره اصلاحشده (بعد از تغییر):
XFileContentExtractor
(Singleton) → XVisionFileContentExtractor
(Singleton) → IXDefaultAIOCRService
(Singleton) ✅
(Singleton) → XVisionFileContentExtractor
(Singleton) → IXDefaultAIOCRService
(Singleton) ✅
💡 چرا این راهحل امن است؟
- ✅
XDefaultAIOCRServiceهیچ State ای ندارد که بین درخواستها به اشتراک گذاشته شود - ✅
IChatClientدر هر فراخوانیGetClient()به صورت محلی ساخته و Dispose میشود - ✅
HttpClientباTimeout.InfiniteTimeSpanthread-safe است - ✅
ILoggerذاتاً thread-safe است - ✅
XAiApiConfigurationفقط خواندنی و thread-safe است
🛠️ گام ۴: اصلاح Startup.cs
MODIFY
xAiApi/Startup.cs (متد ConfigureServices)
کد اصلاحشده:
public void ConfigureServices(IServiceCollection services)
{
// ... (ثبتهای قبلی)
// ==========================================================
// ✅ ثبت File Content Extractors (همگی Singleton)
// ==========================================================
services.AddSingleton<IXFileContentExtractor, XPdfFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XDocxFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XImageFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XAudioFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XExcelFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XVisionFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XPlainTextFileContentExtractor>();
// ✅ کامپوزیت اصلی (لیست بالا را در Constructor دریافت میکند)
services.AddSingleton<IXFileContentExtractor, XFileContentExtractor>();
// ==========================================================
// ✅ ثبت سرویسهای AI
// ==========================================================
// ✅ تغییر از AddScoped به AddSingleton (رفع خطای DI)
services.AddScoped<IXDefaultAIOCRService, XDefaultAIOCRService>();
services.AddSingleton<IXDefaultAIOCRService, XDefaultAIOCRService>();
// سایر سرویسهای AI (Scoped باقی میمانند چون به DataProvider وابستهاند)
services.AddScoped<IXDefaultAiService, XDefaultAiService>();
services.AddScoped<IXDefaultEmbeddingService, XDefaultEmbeddingService>();
services.AddScoped<IXDefaultThinkingAiService, XDefaultThinkingAiService>();
}
✅ نکته مهم: فقط
IXDefaultAIOCRService به Singleton تغییر میکند. سایر سرویسهای AI (IXDefaultAiService, IXDefaultEmbeddingService, IXDefaultThinkingAiService) به دلیل وابستگی به IXAiDataProvider و IXFileProvider که Scoped هستند، باید Scoped باقی بمانند.
🔒 گام ۵: بررسی Thread Safety
با تبدیل XDefaultAIOCRService به Singleton، باید اطمینان حاصل کنیم که این کلاس Thread-safe است:
| بخش از کد | وضعیت Thread Safety | توضیح |
|---|---|---|
Descriptor (Property) |
✅ Safe | فقط در Constructor مقداردهی میشود و readonly است |
Prompt (Property) |
✅ Safe | فقط در Constructor مقداردهی میشود و readonly است |
Options (Property) |
✅ Safe | فقط در Constructor مقداردهی میشود |
GetClient() |
✅ Safe | در هر فراخوانی یک IChatClient جدید میسازد (بدون State مشترک) |
GetHttpClient() |
✅ Safe | در هر فراخوانی یک HttpClient جدید میسازد |
RequestOCRAsync() |
✅ Safe | از using var client استفاده میکند و State محلی دارد |
RequestOCRAsEnumerable() |
✅ Safe | از using var client استفاده میکند و State محلی دارد |
✅ نتیجه: کلاس
XDefaultAIOCRService کاملاً Thread-safe است و میتواند با اطمینان کامل به عنوان Singleton ثبت شود.
✅ گام ۶: تأیید رفع کامل خطا
خلاصه تغییرات:
| فایل | تغییر | دلیل |
|---|---|---|
Startup.cs |
AddScoped → AddSingleton برای IXDefaultAIOCRService |
رفع خطای Scoped/Singleton Mismatch |
چکلیست نهایی:
- ✅ خطای
Cannot consume scoped service from singletonرفع میشود - ✅ تمام Extractor ها (Singleton) میتوانند
XVisionFileContentExtractorرا مصرف کنند - ✅
XVisionFileContentExtractorمیتواندIXDefaultAIOCRServiceرا مصرف کند - ✅
XFileContentExtractor(Composite) میتواند تمام Extractor ها را در Constructor دریافت کند - ✅ Thread Safety کاملاً حفظ شده است
- ✅ Performance بهبود مییابد (ساخت یکبار به جای ساخت در هر Request)
دستورالعمل اجرا:
# ۱. فایل Startup.cs را باز کنید # ۲. خط زیر را پیدا کنید: services.AddScoped<IXDefaultAIOCRService, XDefaultAIOCRService>(); # ۳. آن را به این صورت تغییر دهید: services.AddSingleton<IXDefaultAIOCRService, XDefaultAIOCRService>(); # ۴. پروژه را Rebuild کنید dotnet build # ۵. پروژه را اجرا کنید - خطای AggregateException دیگر ظاهر نخواهد شد
💡 نکته تکمیلی: پس از اعمال این تغییر، میتوانید با خیال راحت endpoint
ExtractContent را در Controller پیادهسازی کنید، زیرا تمام زیرساخت DI اکنون به درستی پیکربندی شده است.