This commit is contained in:
2026-10-03 18:04:36 +03:30
parent 92988ea155
commit 81b900b887
20 changed files with 48612 additions and 4 deletions
+454
View File
@@ -0,0 +1,454 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>رفع خطای Scoped/Singleton Mismatch در DI - فن آوران ساحر علم</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); }
tr:hover { background: var(--bg-light); }
.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; }
.alert-warning { background: #fef3c7; border-color: var(--accent); color: #92400e; }
.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-modify { background: var(--warning); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.chain-diagram { background: white; padding: 25px; border-radius: 10px; margin: 20px 0; text-align: center; }
.chain-step { display: inline-block; background: var(--danger); color: white; padding: 10px 18px; border-radius: 8px; margin: 5px; font-size: 0.88em; }
.chain-step.fixed { background: var(--success); }
.chain-step.singleton { background: var(--primary); }
.chain-step.scoped { background: var(--warning); }
.chain-arrow { display: inline-block; color: var(--accent); font-size: 1.5em; margin: 0 8px; vertical-align: middle; }
.legend { display: flex; gap: 20px; justify-content: center; margin-top: 15px; flex-wrap: wrap; }
.legend-item { display: flex; align-items: center; gap: 8px; font-size: 0.9em; }
.legend-box { width: 20px; height: 20px; border-radius: 4px; }
.diff-old { background: #fee2e2; color: #991b1b; padding: 2px 6px; border-radius: 3px; text-decoration: line-through; }
.diff-new { background: #d1fae5; color: #065f46; padding: 2px 6px; border-radius: 3px; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔗 رفع خطای Scoped/Singleton Mismatch در DI</h1>
<div class="subtitle">تحلیل و رفع عدم تطابق طول عمر سرویس‌ها در کانتینر Dependency Injection</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="#chain">زنجیره وابستگی مشکل‌ساز</a></li>
<li><a href="#solution">راه‌حل اصلاحی</a></li>
<li><a href="#step1">گام ۱: اصلاح Startup.cs</a></li>
<li><a href="#step2">گام ۲: بررسی Thread Safety</a></li>
<li><a href="#verification">تأیید رفع کامل خطا</a></li>
</ol>
</div>
<!-- Section 1: Analysis -->
<div class="section" id="analysis">
<h2>🔍 گام ۱: تحلیل دقیق ریشه خطا</h2>
<p>پیغام خطای DI به یک مشکل کلاسیک در مدیریت طول عمر سرویس‌ها اشاره می‌کند:</p>
<div class="alert alert-danger">
<strong>⚠️ پیغام خطا:</strong><br>
<code style="font-size: 0.95em;">Cannot consume scoped service 'IXDefaultAIOCRService' from singleton 'IXFileContentExtractor'</code>
</div>
<h3>قانون طلایی DI در .NET Core:</h3>
<table>
<tr>
<th>قانون</th>
<th>توضیح</th>
</tr>
<tr>
<td><strong>✅ مجاز</strong></td>
<td>Singleton می‌تواند Singleton را مصرف کند</td>
</tr>
<tr>
<td><strong>✅ مجاز</strong></td>
<td>Scoped می‌تواند Singleton را مصرف کند</td>
</tr>
<tr>
<td><strong>✅ مجاز</strong></td>
<td>Transient می‌تواند هر چیزی را مصرف کند</td>
</tr>
<tr>
<td style="background: var(--danger); color: white;"><strong>❌ ممنوع</strong></td>
<td><strong>Singleton نمی‌تواند Scoped را مصرف کند</strong> (خطای فعلی)</td>
</tr>
<tr>
<td style="background: var(--danger); color: white;"><strong>❌ ممنوع</strong></td>
<td><strong>Singleton نمی‌تواند Transient را مصرف کند</strong></td>
</tr>
</table>
<div class="alert alert-info">
<strong>💡 دلیل منطقی:</strong> اگر یک Singleton بتواند یک Scoped service را مصرف کند، آن Scoped service عملاً به یک Singleton تبدیل می‌شود (چون فقط یک بار ساخته شده و در تمام درخواست‌ها استفاده می‌شود). این مسئله باعث نشت حافظه و رفتار غیرقابل پیش‌بینی می‌شود.
</div>
</div>
<!-- Section 2: Chain -->
<div class="section" id="chain">
<h2>🔗 گام ۲: زنجیره وابستگی مشکل‌ساز</h2>
<h3>وضعیت فعلی (قبل از اصلاح):</h3>
<div class="chain-diagram">
<span class="chain-step singleton">XFileContentExtractor<br><small>(Singleton)</small></span>
<span class="chain-arrow">→</span>
<span class="chain-step singleton">XVisionFileContentExtractor<br><small>(Singleton)</small></span>
<span class="chain-arrow">→</span>
<span class="chain-step scoped">IXDefaultAIOCRService<br><small>(Scoped) ❌</small></span>
</div>
<div class="legend">
<div class="legend-item">
<div class="legend-box" style="background: var(--primary);"></div>
<span>Singleton</span>
</div>
<div class="legend-item">
<div class="legend-box" style="background: var(--warning);"></div>
<span>Scoped</span>
</div>
<div class="legend-item">
<div class="legend-box" style="background: var(--danger);"></div>
<span>خطا</span>
</div>
</div>
<h3>بررسی وابستگی‌های <code>XDefaultAIOCRService</code>:</h3>
<table>
<tr>
<th>وابستگی</th>
<th>نوع ثبت فعلی</th>
<th>آیا Stateful است؟</th>
</tr>
<tr>
<td><code>XAiApiConfiguration</code></td>
<td>Singleton</td>
<td>❌ خیر (فقط خواندنی)</td>
</tr>
<tr>
<td><code>ILogger&lt;XDefaultAIOCRService&gt;</code></td>
<td>Singleton</td>
<td>❌ خیر (Thread-safe)</td>
</tr>
<tr>
<td><code>string model</code></td>
<td>Primitive</td>
<td>❌ خیر</td>
</tr>
<tr>
<td><code>string prompt</code></td>
<td>Primitive</td>
<td>❌ خیر</td>
</tr>
</table>
<div class="alert alert-success">
<strong>✅ نتیجه‌گیری کلیدی:</strong> کلاس <code>XDefaultAIOCRService</code> هیچ وابستگی Scoped یا Stateful ندارد و کاملاً Stateless است. بنابراین می‌تواند با خیال راحت به <strong>Singleton</strong> ارتقا یابد.
</div>
</div>
<!-- Section 3: Solution -->
<div class="section" id="solution">
<h2>💡 گام ۳: راه‌حل اصلاحی</h2>
<p>تنها یک تغییر کوچک در فایل <code>Startup.cs</code> لازم است: تغییر طول عمر <code>IXDefaultAIOCRService</code> از <code>Scoped</code> به <code>Singleton</code>.</p>
<h3>زنجیره اصلاح‌شده (بعد از تغییر):</h3>
<div class="chain-diagram">
<span class="chain-step fixed">XFileContentExtractor<br><small>(Singleton)</small></span>
<span class="chain-arrow">→</span>
<span class="chain-step fixed">XVisionFileContentExtractor<br><small>(Singleton)</small></span>
<span class="chain-arrow">→</span>
<span class="chain-step fixed">IXDefaultAIOCRService<br><small>(Singleton) ✅</small></span>
</div>
<div class="alert alert-info">
<strong>💡 چرا این راه‌حل امن است؟</strong>
<ul style="padding-right: 25px; margin-top: 10px;">
<li>✅ <code>XDefaultAIOCRService</code> هیچ State ای ندارد که بین درخواست‌ها به اشتراک گذاشته شود</li>
<li>✅ <code>IChatClient</code> در هر فراخوانی <code>GetClient()</code> به صورت محلی ساخته و Dispose می‌شود</li>
<li>✅ <code>HttpClient</code> با <code>Timeout.InfiniteTimeSpan</code> thread-safe است</li>
<li>✅ <code>ILogger</code> ذاتاً thread-safe است</li>
<li>✅ <code>XAiApiConfiguration</code> فقط خواندنی و thread-safe است</li>
</ul>
</div>
</div>
<!-- Section 4: Step 1 -->
<div class="section" id="step1">
<h2>🛠️ گام ۴: اصلاح Startup.cs</h2>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Startup.cs (متد ConfigureServices)</span>
</div>
<h3>کد اصلاح‌شده:</h3>
<pre>public void ConfigureServices(IServiceCollection services)
{
// ... (ثبت‌های قبلی)
// ==========================================================
// ✅ ثبت File Content Extractors (همگی Singleton)
// ==========================================================
services.AddSingleton&lt;IXFileContentExtractor, XPdfFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XDocxFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XImageFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XAudioFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XExcelFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XVisionFileContentExtractor&gt;();
services.AddSingleton&lt;IXFileContentExtractor, XPlainTextFileContentExtractor&gt;();
// ✅ کامپوزیت اصلی (لیست بالا را در Constructor دریافت می‌کند)
services.AddSingleton&lt;IXFileContentExtractor, XFileContentExtractor&gt;();
// ==========================================================
// ✅ ثبت سرویس‌های AI
// ==========================================================
// ✅ تغییر از AddScoped به AddSingleton (رفع خطای DI)
<span class="diff-old">services.AddScoped&lt;IXDefaultAIOCRService, XDefaultAIOCRService&gt;();</span>
<span class="diff-new">services.AddSingleton&lt;IXDefaultAIOCRService, XDefaultAIOCRService&gt;();</span>
// سایر سرویس‌های AI (Scoped باقی می‌مانند چون به DataProvider وابسته‌اند)
services.AddScoped&lt;IXDefaultAiService, XDefaultAiService&gt;();
services.AddScoped&lt;IXDefaultEmbeddingService, XDefaultEmbeddingService&gt;();
services.AddScoped&lt;IXDefaultThinkingAiService, XDefaultThinkingAiService&gt;();
}</pre>
<div class="alert alert-success">
<strong>✅ نکته مهم:</strong> فقط <code>IXDefaultAIOCRService</code> به Singleton تغییر می‌کند. سایر سرویس‌های AI (<code>IXDefaultAiService</code>, <code>IXDefaultEmbeddingService</code>, <code>IXDefaultThinkingAiService</code>) به دلیل وابستگی به <code>IXAiDataProvider</code> و <code>IXFileProvider</code> که Scoped هستند، باید Scoped باقی بمانند.
</div>
</div>
<!-- Section 5: Thread Safety -->
<div class="section" id="step2">
<h2>🔒 گام ۵: بررسی Thread Safety</h2>
<p>با تبدیل <code>XDefaultAIOCRService</code> به Singleton، باید اطمینان حاصل کنیم که این کلاس Thread-safe است:</p>
<table>
<tr>
<th>بخش از کد</th>
<th>وضعیت Thread Safety</th>
<th>توضیح</th>
</tr>
<tr>
<td><code>Descriptor</code> (Property)</td>
<td><span style="color: var(--success);">✅ Safe</span></td>
<td>فقط در Constructor مقداردهی می‌شود و readonly است</td>
</tr>
<tr>
<td><code>Prompt</code> (Property)</td>
<td><span style="color: var(--success);">✅ Safe</span></td>
<td>فقط در Constructor مقداردهی می‌شود و readonly است</td>
</tr>
<tr>
<td><code>Options</code> (Property)</td>
<td><span style="color: var(--success);">✅ Safe</span></td>
<td>فقط در Constructor مقداردهی می‌شود</td>
</tr>
<tr>
<td><code>GetClient()</code></td>
<td><span style="color: var(--success);">✅ Safe</span></td>
<td>در هر فراخوانی یک <code>IChatClient</code> جدید می‌سازد (بدون State مشترک)</td>
</tr>
<tr>
<td><code>GetHttpClient()</code></td>
<td><span style="color: var(--success);">✅ Safe</span></td>
<td>در هر فراخوانی یک <code>HttpClient</code> جدید می‌سازد</td>
</tr>
<tr>
<td><code>RequestOCRAsync()</code></td>
<td><span style="color: var(--success);">✅ Safe</span></td>
<td>از <code>using var client</code> استفاده می‌کند و State محلی دارد</td>
</tr>
<tr>
<td><code>RequestOCRAsEnumerable()</code></td>
<td><span style="color: var(--success);">✅ Safe</span></td>
<td>از <code>using var client</code> استفاده می‌کند و State محلی دارد</td>
</tr>
</table>
<div class="alert alert-success">
<strong>✅ نتیجه:</strong> کلاس <code>XDefaultAIOCRService</code> کاملاً Thread-safe است و می‌تواند با اطمینان کامل به عنوان Singleton ثبت شود.
</div>
</div>
<!-- Section 6: Verification -->
<div class="section" id="verification">
<h2>✅ گام ۶: تأیید رفع کامل خطا</h2>
<h3>خلاصه تغییرات:</h3>
<table>
<tr>
<th>فایل</th>
<th>تغییر</th>
<th>دلیل</th>
</tr>
<tr>
<td><code>Startup.cs</code></td>
<td><span class="diff-old">AddScoped</span> → <span class="diff-new">AddSingleton</span><br>برای <code>IXDefaultAIOCRService</code></td>
<td>رفع خطای Scoped/Singleton Mismatch</td>
</tr>
</table>
<h3>چک‌لیست نهایی:</h3>
<div class="alert alert-success">
<ol style="padding-right: 25px;">
<li>✅ خطای <code>Cannot consume scoped service from singleton</code> رفع می‌شود</li>
<li>✅ تمام Extractor ها (Singleton) می‌توانند <code>XVisionFileContentExtractor</code> را مصرف کنند</li>
<li>✅ <code>XVisionFileContentExtractor</code> می‌تواند <code>IXDefaultAIOCRService</code> را مصرف کند</li>
<li>✅ <code>XFileContentExtractor</code> (Composite) می‌تواند تمام Extractor ها را در Constructor دریافت کند</li>
<li>✅ Thread Safety کاملاً حفظ شده است</li>
<li>✅ Performance بهبود می‌یابد (ساخت یکبار به جای ساخت در هر Request)</li>
</ol>
</div>
<h3>دستورالعمل اجرا:</h3>
<pre># ۱. فایل Startup.cs را باز کنید
# ۲. خط زیر را پیدا کنید:
services.AddScoped&lt;IXDefaultAIOCRService, XDefaultAIOCRService&gt;();
# ۳. آن را به این صورت تغییر دهید:
services.AddSingleton&lt;IXDefaultAIOCRService, XDefaultAIOCRService&gt;();
# ۴. پروژه را Rebuild کنید
dotnet build
# ۵. پروژه را اجرا کنید - خطای AggregateException دیگر ظاهر نخواهد شد</pre>
<div class="alert alert-info">
<strong>💡 نکته تکمیلی:</strong> پس از اعمال این تغییر، می‌توانید با خیال راحت endpoint <code>ExtractContent</code> را در Controller پیاده‌سازی کنید، زیرا تمام زیرساخت DI اکنون به درستی پیکربندی شده است.
</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;">
🔗 مستند فنی رفع خطای Scoped/Singleton Mismatch در xAiApi - تمامی حقوق محفوظ است
</p>
</div>
</div>
</body>
</html>