📑 فهرست مطالب
🔍 گام ۱: تحلیل نواقص کد فعلی
در نسخه فعلی XFileContentExtractor، از مکانیزم Reflection (Assembly.LoadFrom و Activator.CreateInstance) برای یافتن و ساخت نمونههای Extractor استفاده شده است. این رویکرد دارای نواقص جدی زیر است:
❌ عدم پشتیبانی از وابستگیها (DI Bypass)
کلاسهایی مانند XVisionFileContentExtractor یا XAudioFileContentExtractor دارای وابستگیهایی مانند IXDefaultAIOCRService یا XAiApiConfiguration هستند. Activator.CreateInstance نمیتواند این وابستگیها را حل کند و باعث خطای MissingMethodException میشود.
❌ مشکل قفلشدگی فایل (File Locking)
استفاده از Assembly.LoadFrom در حلقه روی فایلهای DLL میتواند باعث قفل شدن فایلها در محیطهای هاستینگ (مانند IIS) و جلوگیری از بهروزرسانی یا Deploy شود.
❌ خطر حلقه بینهایت (Circular Dependency)
اگر XFileContentExtractor خودش به عنوان IXFileContentExtractor در DI ثبت شود، ممکن است در لیست بازگردانده شود و باعث فراخوانی بازگشتی بینهایت در متدهای CanExtract شود.
IEnumerable<T> در تزریق وابستگی است.
🛠️ گام ۲: بازنویسی کلاس XFileContentExtractor
به جای اسکن دستی DLL ها، از کانتینر DI میخواهیم تمام پیادهسازیهای ثبتشدهی IXFileContentExtractor را به ما تزریق کند. سپس خودِ کامپوزیت را از لیست فیلتر میکنیم.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using xAiApi.Interfaces.Extractors;
using xAiModels.Models;
using xCommons.Extensions;
using xExceptions.Constants;
namespace xAiApi.Providers.Extractors
{
/// <summary>
/// Composite extractor that delegates to appropriate extractor
/// based on MIME type using Dependency Injection ...
/// </summary>
public class XFileContentExtractor : IXFileContentExtractor
{
private readonly IList<IXFileContentExtractor> extractors;
/// <summary>
/// Constructor: Inject all registered IXFileContentExtractor instances ...
/// </summary>
public XFileContentExtractor(IEnumerable<IXFileContentExtractor> availableExtractors)
{
// فیلتر کردن خودِ این کلاس برای جلوگیری از حلقه بینهایت (Circular Dependency)
extractors = availableExtractors
.Where(e => e.GetType() != typeof(XFileContentExtractor))
.ToList();
}
/// <summary>
/// Check if any registered extractor supports the specified MIME type ...
/// </summary>
public bool CanExtract(string mimeType)
{
return extractors.Any(e => e.CanExtract(mimeType));
}
/// <summary>
/// Extract text content from file stream ...
/// </summary>
public async Task<string> ExtractAsync(
Stream fileStream,
string mimeType,
CancellationToken cancellationToken = default
)
{
var extractor = extractors.FirstOrDefault(e => e.CanExtract(mimeType));
if (extractor.IsNull())
{
XException.NotAllowed.Throw($"Unsupported file type: {mimeType}");
}
return await extractor.ExtractAsync(
fileStream: fileStream,
mimeType: mimeType,
cancellationToken: cancellationToken
);
}
/// <summary>
/// Extract content from stream as Rich Result ...
/// </summary>
public async Task<XFileExtractionResult> ExtractRichAsync(
Stream fileStream,
string fileName,
string mimeType,
CancellationToken cancellationToken = default
)
{
var extractor = extractors.FirstOrDefault(e => e.CanExtract(mimeType));
if (extractor.IsNull())
{
XException.NotAllowed.Throw($"Unsupported file type: {mimeType}");
}
return await extractor.ExtractRichAsync(
fileStream: fileStream,
fileName: fileName,
mimeType: mimeType,
cancellationToken: cancellationToken
);
}
}
}
🔌 گام ۳: ثبت صحیح سرویسها در Startup.cs
برای اینکه تزریق IEnumerable<IXFileContentExtractor> کار کند، باید تمام Extractor های خاص را به صورت Singleton (چون State-less هستند) در کانتینر DI ثبت کنیم. کانتینر .NET Core به طور خودکار آنها را در یک لیست جمعآوری میکند.
public void ConfigureServices(IServiceCollection services)
{
// ... (ثبتهای قبلی سرویسها)
// ==========================================================
// ✅ ثبت File Content Extractors (الگوی Composite)
// ==========================================================
// ۱. ثبت Extractor های پایه
services.AddSingleton<IXFileContentExtractor, XPlainTextFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XDocxFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XExcelFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XPdfFileContentExtractor>();
// ۲. ثبت Extractor های پیشرفته (تصویر و صوت)
// نکته: XImageFileContentExtractor و XAudioFileContentExtractor باید در پروژه موجود باشند
services.AddSingleton<IXFileContentExtractor, XImageFileContentExtractor>();
services.AddSingleton<IXFileContentExtractor, XAudioFileContentExtractor>();
// ۳. ثبت Vision Extractor (که وابستگی به IXDefaultAIOCRService دارد)
// این خط به طور خودکار وابستگیهای XVisionFileContentExtractor را از DI حل میکند
services.AddSingleton<IXFileContentExtractor, XVisionFileContentExtractor>();
// ۴. ثبت کامپوزیت اصلی (این کلاس لیست بالا را در Constructor دریافت میکند)
services.AddSingleton<IXFileContentExtractor, XFileContentExtractor>();
// ... (ثبت سرویسهای AI و سایر موارد)
services.AddScoped<IXDefaultAiService, XDefaultAiService>();
services.AddScoped<IXDefaultAIOCRService, XDefaultAIOCRService>();
services.AddScoped<IXDefaultEmbeddingService, XDefaultEmbeddingService>();
services.AddScoped<IXDefaultThinkingAiService, XDefaultThinkingAiService>();
}
در .NET Core، وقتی
IEnumerable<T> را Inject میکنید، تمام ثبتهای T (شامل خودِ XFileContentExtractor اگر قبل از فیلتر کردن باشد) را برمیگرداند. به همین دلیل در Constructor کلاس XFileContentExtractor، خط .Where(e => e.GetType() != typeof(XFileContentExtractor)) اضافه شده است تا از حلقه بینهایت جلوگیری شود.
✨ گام ۴: مزایای رویکرد جدید
| ویژگی | رویکرد قدیمی (Reflection) | رویکرد جدید (DI) |
|---|---|---|
| حل وابستگیها (Dependencies) | ❌ شکست میخورد | ✅ به طور خودکار حل میشود |
| عملکرد (Performance) | ⚠️ کند (اسکن DLL در هر بار) | ✅ بسیار سریع (Resolved at startup) |
| قابلیت تست (Unit Testing) | ❌ بسیار دشوار (Mocking سخت) | ✅ آسان (تزریق لیست Mock) |
| پایداری در محیط Production | ❌ خطر File Locking | ✅ کاملاً پایدار و استاندارد |
| افزودن Extractor جدید | خودکار (اما با ریسک) | فقط افزودن یک خط AddSingleton در Startup.cs |
با این تغییر، معماری پروژه شما کاملاً با اصول SOLID (به ویژه Dependency Inversion) و الگوهای استاندارد .NET Core همسو میشود. کلاس
XVisionFileContentExtractor که به IXDefaultAIOCRService وابسته است، اکنون بدون هیچ خطایی مقداردهی اولیه خواهد شد.