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

454 lines
23 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>رفع خطای 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>