Files
2026-10-03 18:04:36 +03:30

385 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>بازطراحی XFileContentExtractor با استفاده از تزریق وابستگی - فن آوران ساحر علم</title>
<style>
:root {
--primary: #1e3a8a;
--secondary: #3b82f6;
--accent: #f59e0b;
--success: #10b981;
--danger: #ef4444;
--warning: #f97316;
--bg-light: #f8fafc;
--bg-code: #1e293b;
--text-dark: #0f172a;
--text-muted: #64748b;
--border: #e2e8f0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Tahoma', 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 100%);
color: var(--text-dark);
line-height: 1.8;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: white;
padding: 40px;
text-align: center;
}
.header h1 { font-size: 2.1em; margin-bottom: 10px; }
.header .subtitle { font-size: 1.1em; opacity: 0.95; }
.meta-bar {
display: flex;
justify-content: space-between;
background: var(--bg-light);
padding: 15px 30px;
border-bottom: 2px solid var(--border);
flex-wrap: wrap;
gap: 15px;
}
.meta-item { display: flex; align-items: center; gap: 8px; font-size: 0.9em; color: var(--text-muted); }
.meta-item strong { color: var(--primary); }
.content { padding: 40px; }
.section {
margin-bottom: 35px;
padding: 25px;
background: var(--bg-light);
border-radius: 12px;
border-right: 5px solid var(--secondary);
}
.section h2 {
color: var(--primary);
font-size: 1.5em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
.section h3 { color: var(--secondary); font-size: 1.2em; margin: 20px 0 12px; }
pre {
background: var(--bg-code);
color: #e2e8f0;
padding: 18px;
border-radius: 8px;
overflow-x: auto;
direction: ltr;
text-align: left;
font-family: 'Consolas', monospace;
font-size: 0.85em;
margin: 15px 0;
border-right: 4px solid var(--accent);
}
code {
background: #fef3c7;
color: #92400e;
padding: 2px 8px;
border-radius: 4px;
font-family: 'Consolas', monospace;
font-size: 0.9em;
direction: ltr;
display: inline-block;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
background: white;
border-radius: 8px;
overflow: hidden;
}
th { background: var(--primary); color: white; padding: 12px; text-align: right; }
td { padding: 12px; border-bottom: 1px solid var(--border); }
.alert { padding: 15px 20px; border-radius: 8px; margin: 15px 0; border-right: 4px solid; }
.alert-info { background: #dbeafe; border-color: var(--secondary); color: #1e40af; }
.alert-success { background: #d1fae5; border-color: var(--success); color: #065f46; }
.alert-danger { background: #fee2e2; border-color: var(--danger); color: #991b1b; }
.footer { background: var(--primary); color: white; padding: 25px; text-align: center; }
.toc { background: white; padding: 20px; border-radius: 10px; margin-bottom: 25px; border: 2px solid var(--border); }
.toc h3 { color: var(--primary); margin-bottom: 15px; }
.toc ol { padding-right: 25px; }
.toc li { padding: 6px 0; }
.toc a { color: var(--secondary); text-decoration: none; }
.file-change { background: #f0f9ff; border-right: 4px solid var(--secondary); padding: 15px; margin: 10px 0; border-radius: 8px; }
.file-change .path { font-family: 'Consolas', monospace; color: var(--primary); font-weight: bold; direction: ltr; display: inline-block; }
.badge-new { background: var(--success); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.badge-modify { background: var(--warning); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.arch-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin: 20px 0; }
.arch-card { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); border-top: 4px solid var(--secondary); }
.arch-card h4 { color: var(--primary); margin-bottom: 12px; }
.arch-card ul { list-style: none; padding-right: 0; }
.arch-card li { padding: 6px 0; padding-right: 20px; position: relative; }
.arch-card li::before { content: '▸'; position: absolute; right: 0; color: var(--accent); font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔄 بازطراحی XFileContentExtractor بر پایه DI</h1>
<div class="subtitle">حذف Reflection و جایگزینی با تزریق وابستگی استاندارد برای مدیریت Extractor ها</div>
</div>
<div class="meta-bar">
<div class="meta-item">👨‍💻 <strong>توسعه‌دهنده:</strong> هادی خزاعی اصل</div>
<div class="meta-item">🏢 <strong>شرکت:</strong> فن آوران ساحر علم</div>
<div class="meta-item">📅 <strong>تاریخ:</strong> شنبه ۱۲ مهر ۱۴۰۵</div>
<div class="meta-item">📦 <strong>پروژه:</strong> xAiApi</div>
</div>
<div class="content">
<div class="toc">
<h3>📑 فهرست مطالب</h3>
<ol>
<li><a href="#analysis">تحلیل نواقص کد فعلی</a></li>
<li><a href="#solution">گام ۱: بازنویسی کلاس XFileContentExtractor</a></li>
<li><a href="#di-registration">گام ۲: ثبت سرویس‌ها در Startup.cs</a></li>
<li><a href="#benefits">مزایای رویکرد جدید</a></li>
</ol>
</div>
<!-- Section 1: Analysis -->
<div class="section" id="analysis">
<h2>🔍 گام ۱: تحلیل نواقص کد فعلی</h2>
<p>در نسخه فعلی <code>XFileContentExtractor</code>، از مکانیزم <strong>Reflection</strong> (<code>Assembly.LoadFrom</code> و <code>Activator.CreateInstance</code>) برای یافتن و ساخت نمونه‌های Extractor استفاده شده است. این رویکرد دارای نواقص جدی زیر است:</p>
<div class="arch-grid">
<div class="arch-card">
<h4>❌ عدم پشتیبانی از وابستگی‌ها (DI Bypass)</h4>
<p>کلاس‌هایی مانند <code>XVisionFileContentExtractor</code> یا <code>XAudioFileContentExtractor</code> دارای وابستگی‌هایی مانند <code>IXDefaultAIOCRService</code> یا <code>XAiApiConfiguration</code> هستند. <code>Activator.CreateInstance</code> نمی‌تواند این وابستگی‌ها را حل کند و باعث خطای <code>MissingMethodException</code> می‌شود.</p>
</div>
<div class="arch-card">
<h4>❌ مشکل قفل‌شدگی فایل (File Locking)</h4>
<p>استفاده از <code>Assembly.LoadFrom</code> در حلقه روی فایل‌های DLL می‌تواند باعث قفل شدن فایل‌ها در محیط‌های هاستینگ (مانند IIS) و جلوگیری از به‌روزرسانی یا Deploy شود.</p>
</div>
<div class="arch-card">
<h4>❌ خطر حلقه بی‌نهایت (Circular Dependency)</h4>
<p>اگر <code>XFileContentExtractor</code> خودش به عنوان <code>IXFileContentExtractor</code> در DI ثبت شود، ممکن است در لیست بازگردانده شود و باعث فراخوانی بازگشتی بی‌نهایت در متدهای <code>CanExtract</code> شود.</p>
</div>
</div>
<div class="alert alert-danger">
<strong>⚠️ نتیجه‌گیری:</strong> استفاده از Reflection برای ساخت اشیایی که وابستگی دارند، یک Anti-Pattern در .NET Core است. راه‌حل استاندارد، استفاده از قابلیت <code>IEnumerable&lt;T&gt;</code> در تزریق وابستگی است.
</div>
</div>
<!-- Section 2: Solution -->
<div class="section" id="solution">
<h2>🛠️ گام ۲: بازنویسی کلاس XFileContentExtractor</h2>
<p>به جای اسکن دستی DLL ها، از کانتینر DI می‌خواهیم تمام پیاده‌سازی‌های ثبت‌شده‌ی <code>IXFileContentExtractor</code> را به ما تزریق کند. سپس خودِ کامپوزیت را از لیست فیلتر می‌کنیم.</p>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Providers/Extractors/XFileContentExtractor.cs</span>
</div>
<pre>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
{
/// &lt;summary&gt;
/// Composite extractor that delegates to appropriate extractor
/// based on MIME type using Dependency Injection ...
/// &lt;/summary&gt;
public class XFileContentExtractor : IXFileContentExtractor
{
private readonly IList&lt;IXFileContentExtractor&gt; extractors;
/// &lt;summary&gt;
/// Constructor: Inject all registered IXFileContentExtractor instances ...
/// &lt;/summary&gt;
public XFileContentExtractor(IEnumerable&lt;IXFileContentExtractor&gt; availableExtractors)
{
// فیلتر کردن خودِ این کلاس برای جلوگیری از حلقه بی‌نهایت (Circular Dependency)
extractors = availableExtractors
.Where(e => e.GetType() != typeof(XFileContentExtractor))
.ToList();
}
/// &lt;summary&gt;
/// Check if any registered extractor supports the specified MIME type ...
/// &lt;/summary&gt;
public bool CanExtract(string mimeType)
{
return extractors.Any(e => e.CanExtract(mimeType));
}
/// &lt;summary&gt;
/// Extract text content from file stream ...
/// &lt;/summary&gt;
public async Task&lt;string&gt; 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
);
}
/// &lt;summary&gt;
/// Extract content from stream as Rich Result ...
/// &lt;/summary&gt;
public async Task&lt;XFileExtractionResult&gt; 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
);
}
}
}</pre>
</div>
<!-- Section 3: DI Registration -->
<div class="section" id="di-registration">
<h2>🔌 گام ۳: ثبت صحیح سرویس‌ها در Startup.cs</h2>
<p>برای اینکه تزریق <code>IEnumerable&lt;IXFileContentExtractor&gt;</code> کار کند، باید تمام Extractor های خاص را به صورت <code>Singleton</code> (چون State-less هستند) در کانتینر DI ثبت کنیم. کانتینر .NET Core به طور خودکار آن‌ها را در یک لیست جمع‌آوری می‌کند.</p>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Startup.cs (متد ConfigureServices)</span>
</div>
<pre>public void ConfigureServices(IServiceCollection services)
{
// ... (ثبت‌های قبلی سرویس‌ها)
// ==========================================================
// ✅ ثبت File Content Extractors (الگوی Composite)
// ==========================================================
// ۱. ثبت Extractor های پایه
services.AddSingleton&lt;IXFileContentExtractor, XPlainTextFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XDocxFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XExcelFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XPdfFileContentExtractor&gt;();
// ۲. ثبت Extractor های پیشرفته (تصویر و صوت)
// نکته: XImageFileContentExtractor و XAudioFileContentExtractor باید در پروژه موجود باشند
services.AddSingleton&lt;IXFileContentExtractor, XImageFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XAudioFileContentExtractor&gt;();
// ۳. ثبت Vision Extractor (که وابستگی به IXDefaultAIOCRService دارد)
// این خط به طور خودکار وابستگی‌های XVisionFileContentExtractor را از DI حل می‌کند
services.AddSingleton&lt;IXFileContentExtractor, XVisionFileContentExtractor&gt;();
// ۴. ثبت کامپوزیت اصلی (این کلاس لیست بالا را در Constructor دریافت می‌کند)
services.AddSingleton&lt;IXFileContentExtractor, XFileContentExtractor&gt;();
// ... (ثبت سرویس‌های AI و سایر موارد)
services.AddScoped&lt;IXDefaultAiService, XDefaultAiService&gt;();
services.AddScoped&lt;IXDefaultAIOCRService, XDefaultAIOCRService&gt;();
services.AddScoped&lt;IXDefaultEmbeddingService, XDefaultEmbeddingService&gt;();
services.AddScoped&lt;IXDefaultThinkingAiService, XDefaultThinkingAiService&gt;();
}</pre>
<div class="alert alert-info">
<strong>💡 نکته حیاتی درباره ترتیب ثبت:</strong><br>
در .NET Core، وقتی <code>IEnumerable&lt;T&gt;</code> را Inject می‌کنید، تمام ثبت‌های <code>T</code> (شامل خودِ <code>XFileContentExtractor</code> اگر قبل از فیلتر کردن باشد) را برمی‌گرداند. به همین دلیل در Constructor کلاس <code>XFileContentExtractor</code>، خط <code>.Where(e => e.GetType() != typeof(XFileContentExtractor))</code> اضافه شده است تا از حلقه بی‌نهایت جلوگیری شود.
</div>
</div>
<!-- Section 4: Benefits -->
<div class="section" id="benefits">
<h2>✨ گام ۴: مزایای رویکرد جدید</h2>
<table>
<tr>
<th>ویژگی</th>
<th>رویکرد قدیمی (Reflection)</th>
<th>رویکرد جدید (DI)</th>
</tr>
<tr>
<td>حل وابستگی‌ها (Dependencies)</td>
<td><span style="color: var(--danger);">❌ شکست می‌خورد</span></td>
<td><span style="color: var(--success);">✅ به طور خودکار حل می‌شود</span></td>
</tr>
<tr>
<td>عملکرد (Performance)</td>
<td><span style="color: var(--warning);">⚠️ کند (اسکن DLL در هر بار)</span></td>
<td><span style="color: var(--success);">✅ بسیار سریع (Resolved at startup)</span></td>
</tr>
<tr>
<td>قابلیت تست (Unit Testing)</td>
<td><span style="color: var(--danger);">❌ بسیار دشوار (Mocking سخت)</span></td>
<td><span style="color: var(--success);">✅ آسان (تزریق لیست Mock)</span></td>
</tr>
<tr>
<td>پایداری در محیط Production</td>
<td><span style="color: var(--danger);">❌ خطر File Locking</span></td>
<td><span style="color: var(--success);">✅ کاملاً پایدار و استاندارد</span></td>
</tr>
<tr>
<td>افزودن Extractor جدید</td>
<td>خودکار (اما با ریسک)</td>
<td>فقط افزودن یک خط <code>AddSingleton</code> در <code>Startup.cs</code></td>
</tr>
</table>
<div class="alert alert-success">
<strong>✅ نتیجه‌گیری نهایی:</strong><br>
با این تغییر، معماری پروژه شما کاملاً با اصول <strong>SOLID</strong> (به ویژه Dependency Inversion) و الگوهای استاندارد .NET Core همسو می‌شود. کلاس <code>XVisionFileContentExtractor</code> که به <code>IXDefaultAIOCRService</code> وابسته است، اکنون بدون هیچ خطایی مقداردهی اولیه خواهد شد.
</div>
</div>
</div>
<div class="footer">
<p><strong>👨‍💻 توسعه‌دهنده:</strong> هادی خزاعی اصل</p>
<p><strong>🏢 شرکت:</strong> فن آوران ساحر علم</p>
<p><strong>📅 تاریخ:</strong> شنبه ۱۲ مهر ۱۴۰۵</p>
<p style="margin-top: 15px; opacity: 0.8; font-size: 0.9em;">
🔄 مستند فنی بازطراحی XFileContentExtractor بر پایه Dependency Injection - تمامی حقوق محفوظ است
</p>
</div>
</div>
</body>
</html>