<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Emiliano Montesdeoca</title><link>https://emimontesdeoca.github.io/ar/</link><description>Microsoft MVP in Developer Technologies. Cloud Solutions Team Lead. Speaker, blogger, and community advocate.</description><language>ar</language><managingEditor>emimontesdeoca@outlook.es (Emiliano Montesdeoca)</managingEditor><webMaster>emimontesdeoca@outlook.es (Emiliano Montesdeoca)</webMaster><lastBuildDate>Mon, 01 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://emimontesdeoca.github.io/ar/index.xml" rel="self" type="application/rss+xml"/><item><title>Blazor من الصفر: الفصل 3 — مكوّنات قابلة للتوسّع</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-chapter-3/</link><pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-chapter-3/</guid><description>الفصل الثالث من سلسلة Blazor من الصفر. نتعمّق في المكوّنات: المعاملات، التركيب، RenderFragment، وبنية المجلدات التي تحافظ على واجهة مرتبة مع نمو التطبيق.</description><content:encoded>&lt;p>مرحبًا بك في الفصل 3 من &lt;strong>Blazor من الصفر&lt;/strong>. إذا فاتك &lt;a href="../blazor-from-scratch-chapter-2">الفصل 2&lt;/a>، ابدأ به أولًا حتى يكون مشروعك الأساسي جاهزًا.&lt;/p>
&lt;p>في الفصل 2 جعلنا التطبيق يعمل. في هذا الفصل سنجعل الكود &lt;strong>قابلاً للصيانة&lt;/strong>.&lt;/p>
&lt;p>مشاريع Blazor تصبح فوضوية بسرعة عندما تتحول كل صفحة إلى ملف &lt;code>.razor&lt;/code> ضخم. المكوّنات هي الحل: اتساق أعلى، إعادة استخدام أفضل، وحدود أوضح بين أجزاء الواجهة.&lt;/p>
&lt;hr>
&lt;h2 id="ما-هو-مكون-blazor-فعليا">ما هو مكوّن Blazor فعليًا؟&lt;/h2>
&lt;p>المكوّن هو ملف &lt;code>.razor&lt;/code> يستطيع:&lt;/p>
&lt;ul>
&lt;li>عرض الـ markup&lt;/li>
&lt;li>حفظ حالة محلية&lt;/li>
&lt;li>استقبال مدخلات عبر Parameters&lt;/li>
&lt;li>إرسال أحداث إلى المكوّن الأب&lt;/li>
&lt;li>عرض محتوى فرعي&lt;/li>
&lt;/ul>
&lt;p>أثناء التشغيل، يتعامل Blazor مع كل مكوّن كآلة حالة صغيرة. عند تغيّر الحالة يعيد التصيير ويطبّق DOM diff.&lt;/p>
&lt;p>لذلك الهدف هو تصميم مدخلات واضحة وسلوك متوقّع.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-1-ابدأ-بمكون-صغير-ومحدد">الخطوة 1: ابدأ بمكوّن صغير ومحدد&lt;/h2>
&lt;p>أنشئ &lt;code>Components/Common/SectionHeader.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;header class=&amp;#34;section-header&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h2&amp;gt;@Title&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @if (!string.IsNullOrWhiteSpace(Subtitle))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;@Subtitle&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Title { get; set; } = string.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string? Subtitle { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>استخدمه في &lt;code>Components/Pages/Home.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt;Blazor من الصفر&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;SectionHeader
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Title=&amp;#34;Blazor من الصفر&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Subtitle=&amp;#34;الفصل 3 عن المكوّنات.&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>المثال بسيط، لكن الفكرة محورية: يجب أن يُفهم المكوّن من خلال معاملاته.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-2-اجعل-السلوك-صريحا-بالمعاملات">الخطوة 2: اجعل السلوك صريحًا بالمعاملات&lt;/h2>
&lt;p>لننشئ زرًا قابلاً لإعادة الاستخدام.&lt;/p>
&lt;p>&lt;code>Components/Common/AppButton.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;app-button @VariantCssClass&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;OnClick&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>Text
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Text { get; set; } &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Button&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Variant { get; set; } &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;primary&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public EventCallback OnClick { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string VariantCssClass &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> Variant&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToLowerInvariant() &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;secondary&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;app-button--secondary&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;danger&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;app-button--danger&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;app-button--primary&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الاستخدام:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private int _savedCount;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void Save()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _savedCount++;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;AppButton Text=&amp;#34;Save&amp;#34; Variant=&amp;#34;primary&amp;#34; OnClick=&amp;#34;Save&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;تم الحفظ @_savedCount مرة.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>المهم هنا هو تصميم العقد:&lt;/p>
&lt;ul>
&lt;li>معاملات قليلة وواضحة&lt;/li>
&lt;li>أسماء صريحة بدل سلوك &amp;ldquo;سحري&amp;rdquo;&lt;/li>
&lt;li>قيم افتراضية آمنة&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="الخطوة-3-استخدم-renderfragment-للتركيب">الخطوة 3: استخدم &lt;code>RenderFragment&lt;/code> للتركيب&lt;/h2>
&lt;p>&lt;code>RenderFragment&lt;/code> يسمح للمكوّن الأب بتمرير أجزاء واجهة إلى المكوّن الابن.&lt;/p>
&lt;p>أنشئ &lt;code>Components/Common/Card.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;article class=&amp;#34;card&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;header class=&amp;#34;card__header&amp;#34;&amp;gt;@Title&amp;lt;/header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section class=&amp;#34;card__body&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @ChildContent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/article&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public string Title { get; set; } = string.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public RenderFragment? ChildContent { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الاستخدام:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;Card Title=&amp;#34;Roadmap&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;المكوّنات&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;Data binding&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;Routing&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/Card&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا النمط يساعدك على بناء صفحات متناسقة بدون تكرار نفس wrapper markup.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-4-فضل-التركيب-على-الصفحات-العملاقة">الخطوة 4: فضّل التركيب على الصفحات العملاقة&lt;/h2>
&lt;p>عندما تكبر الصفحة، قسّمها حسب المسؤولية:&lt;/p>
&lt;ul>
&lt;li>&lt;code>ProfileSummary&lt;/code> لكتلة التعريف&lt;/li>
&lt;li>&lt;code>ProfileStats&lt;/code> للمؤشرات&lt;/li>
&lt;li>&lt;code>ProfileActivityList&lt;/code> للنشاط الأخير&lt;/li>
&lt;/ul>
&lt;p>بهذا تصبح الصفحة طبقة تنسيق، وليست مكانًا لكل التفاصيل.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-5-وازن-بين-الـ-markup-والمنطق">الخطوة 5: وازن بين الـ markup والمنطق&lt;/h2>
&lt;p>في المكوّنات البسيطة، &lt;code>@code&lt;/code> داخل الملف كافٍ.&lt;/p>
&lt;p>في المكوّنات الكبيرة، انقل المنطق إلى code-behind:&lt;/p>
&lt;ul>
&lt;li>&lt;code>UserCard.razor&lt;/code>&lt;/li>
&lt;li>&lt;code>UserCard.razor.cs&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>هذا يجعل كلًا من الواجهة ومنطق C# أسهل قراءة.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-6-بنية-مجلدات-عملية">الخطوة 6: بنية مجلدات عملية&lt;/h2>
&lt;p>بنية فعالة مع النمو:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Components/Pages/&lt;/code> -&amp;gt; صفحات قابلة للتوجيه&lt;/li>
&lt;li>&lt;code>Components/Layout/&lt;/code> -&amp;gt; هيكل التطبيق والتنقّل&lt;/li>
&lt;li>&lt;code>Components/Common/&lt;/code> -&amp;gt; عناصر عامة مشتركة&lt;/li>
&lt;li>&lt;code>Components/Features/&amp;lt;FeatureName&amp;gt;/&lt;/code> -&amp;gt; مكوّنات خاصة بكل ميزة&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="أخطاء-شائعة-في-بدايات-مشاريع-blazor">أخطاء شائعة في بدايات مشاريع Blazor&lt;/h2>
&lt;ul>
&lt;li>تمرير عدد كبير من المعاملات بدل نموذج مخصص&lt;/li>
&lt;li>وضع قواعد العمل داخل مكوّنات الصفحات&lt;/li>
&lt;li>إنشاء &amp;ldquo;مكوّن إلهي&amp;rdquo; بعدد هائل من الأسطر&lt;/li>
&lt;li>تكرار markup بدل استخراج مكوّنات صغيرة قابلة لإعادة الاستخدام&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="الفصل-القادم">الفصل القادم&lt;/h2>
&lt;p>في الفصل 4 سنركّز على &lt;strong>Data binding والأحداث&lt;/strong>: &lt;code>@bind&lt;/code>، التعامل مع الأحداث، مفاضلات two-way binding، وأنماط تبقي الحالة متوقعة.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>Blazor من الصفر: الفصل 2 — أول تطبيق Blazor لك</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-chapter-2/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-chapter-2/</guid><description>الفصل 2 من سلسلة Blazor من الصفر. ننشئ أول تطبيق، نشغله محلياً، ونراجع الملفات الأساسية لفهم بنية المشروع من البداية.</description><content:encoded>&lt;p>مرحباً بك في الفصل 2 من &lt;strong>Blazor من الصفر&lt;/strong>. إذا فاتك &lt;a href="../blazor-from-scratch-chapter-1">الفصل 1&lt;/a>، اقرأه أولاً.&lt;/p>
&lt;p>في هذا الفصل سننشئ تطبيق Blazor جديداً، نشغله محلياً، ونفهم دور الملفات المهمة.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-1-إنشاء-المشروع">الخطوة 1: إنشاء المشروع&lt;/h2>
&lt;p>من الطرفية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new blazor -o BlazorFromScratch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd BlazorFromScratch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا ينشئ تطبيق Blazor Web App على .NET 9 بالهيكل الافتراضي.&lt;/p>
&lt;p>إذا طلبت منك الأداة اختيار نموذج التفاعلية، ابدأ بـ &lt;strong>Interactive Server&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-2-التشغيل-محليا">الخطوة 2: التشغيل محلياً&lt;/h2>
&lt;p>شغّل التطبيق:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet watch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ثم افتح الرابط الذي يظهر في الطرفية (غالباً &lt;code>https://localhost:5xxx&lt;/code>). يجب أن ترى الصفحات الافتراضية Home وCounter وWeather.&lt;/p>
&lt;p>إذا عمل ذلك، فبيئة التطوير جاهزة.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-3-فهم-هيكل-المشروع">الخطوة 3: فهم هيكل المشروع&lt;/h2>
&lt;p>هذه أهم الملفات والمجلدات التي تحتاج معرفتها أولاً:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Program.cs&lt;/code> — تسجيل الخدمات وضبط HTTP pipeline.&lt;/li>
&lt;li>&lt;code>Components/App.razor&lt;/code> — المكوّن الجذري للتطبيق.&lt;/li>
&lt;li>&lt;code>Components/Routes.razor&lt;/code> — تعريف المسارات.&lt;/li>
&lt;li>&lt;code>Components/Pages/&lt;/code> — الصفحات القابلة للتوجيه مثل Home وCounter.&lt;/li>
&lt;li>&lt;code>Components/Layout/&lt;/code> — التخطيط المشترك (&lt;code>MainLayout.razor&lt;/code> والقائمة).&lt;/li>
&lt;li>&lt;code>wwwroot/&lt;/code> — الملفات الثابتة (CSS، الصور، favicon).&lt;/li>
&lt;li>&lt;code>appsettings.json&lt;/code> — قيم الإعدادات.&lt;/li>
&lt;/ul>
&lt;p>لا تحتاج لفهم كل ملف الآن. يكفي في هذه المرحلة معرفة مكان الواجهة ومكان إعداد التشغيل.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-4-نفذ-أول-تعديل">الخطوة 4: نفّذ أول تعديل&lt;/h2>
&lt;p>افتح &lt;code>Components/Pages/Home.razor&lt;/code> واستبدل المحتوى بـ:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt;Blazor من الصفر&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Blazor من الصفر&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;الفصل 2 يعمل محلياً.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>احفظ الملف ثم حدّث المتصفح. مع &lt;code>dotnet watch&lt;/code> سيظهر التغيير مباشرة.&lt;/p>
&lt;p>هذا التعديل البسيط يؤكد الدورة الكاملة: تعديل -&amp;gt; بناء -&amp;gt; عرض.&lt;/p>
&lt;hr>
&lt;h2 id="الخطوة-5-اقرأ-programcs-مرة-واحدة">الخطوة 5: اقرأ Program.cs مرة واحدة&lt;/h2>
&lt;p>لا تحتاج لحفظه، لكن تعرّف على الأسطر الأساسية:&lt;/p>
&lt;ul>
&lt;li>&lt;code>AddRazorComponents()&lt;/code> لتفعيل Razor components.&lt;/li>
&lt;li>&lt;code>AddInteractiveServerComponents()&lt;/code> لتفعيل المكوّنات التفاعلية على الخادم.&lt;/li>
&lt;li>&lt;code>MapRazorComponents&amp;lt;App&amp;gt;()&lt;/code> لربط المكوّن الجذري بنقاط النهاية.&lt;/li>
&lt;/ul>
&lt;p>هذه الأسطر توضّح كيف يبدأ التطبيق وما قدرات العرض المفعّلة.&lt;/p>
&lt;hr>
&lt;h2 id="مشاكل-شائعة">مشاكل شائعة&lt;/h2>
&lt;p>إذا فشل التشغيل، فغالباً السبب أحد التالي:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>إصدار SDK قديم&lt;/strong>: نفّذ &lt;code>dotnet --version&lt;/code> وتأكد من .NET 9.&lt;/li>
&lt;li>&lt;strong>تحذير شهادة HTTPS&lt;/strong>: نفّذ &lt;code>dotnet dev-certs https --trust&lt;/code>.&lt;/li>
&lt;li>&lt;strong>تعارض المنفذ&lt;/strong>: أوقف عمليات &lt;code>dotnet&lt;/code> القديمة ثم أعد التشغيل.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="القادم">القادم&lt;/h2>
&lt;p>في الفصل 3 سنتعمّق في &lt;strong>المكوّنات&lt;/strong>: المعاملات، التركيب، وكيفية تنظيم واجهة قابلة لإعادة الاستخدام.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>Blazor من الصفر: الفصل الأول — ما هو Blazor؟</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-chapter-1/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-chapter-1/</guid><description>الفصل الأول من سلسلة Blazor من الصفر. نستعرض ما هو Blazor فعلاً، ومن أين جاء، ونماذج التصيير المختلفة المتاحة اليوم، وكيف يقارن بأطر عمل JavaScript.</description><content:encoded>&lt;p>مرحباً بك في الفصل الأول من سلسلة &lt;strong>Blazor من الصفر&lt;/strong>. إذا فاتك &lt;a href="../blazor-from-scratch-intro">منشور المقدمة&lt;/a> حيث شرحت الغرض من هذه السلسلة والجمهور المستهدف، ابدأ بقراءته أولاً — فهو قصير.&lt;/p>
&lt;p>في هذا الفصل سنجيب على السؤال الجوهري: &lt;em>ما هو Blazor؟&lt;/em> يبدو هذا بسيطاً، لكن الإجابة ذات طبقات متعددة — لا سيما أن &amp;ldquo;Blazor&amp;rdquo; اكتسب معاني متباينة قليلاً عبر السنوات. بنهاية هذا المنشور ستعرف ما هو Blazor، وكيف يندرج ضمن منظومة .NET، وما هي نماذج التصيير المختلفة، ولماذا قد تختاره بدلاً من إطار عمل JavaScript — أو لا تختاره.&lt;/p>
&lt;hr>
&lt;h2 id="نبذة-تاريخية-مختصرة">نبذة تاريخية مختصرة&lt;/h2>
&lt;p>بدأ Blazor كمشروع تجريبي لـ Steve Sanderson في Microsoft حوالي عام 2017. كانت الفكرة استفزازية: تشغيل C# في المتصفح باستخدام WebAssembly، مما يُلغي الحاجة إلى JavaScript كلياً. كان ذلك دليلاً على المفهوم، والاسم كان مزجاً مقصوداً بين &lt;strong>Bla&lt;/strong>zer وRa&lt;strong>zor&lt;/strong> — محرك القوالب الذي بُني عليه Blazor في نهاية المطاف.&lt;/p>
&lt;p>أثار التجربةُ حماساً كافياً جعل Microsoft تأخذها بجدية. شُحن Blazor كجزء من ASP.NET Core 3.0 في سبتمبر 2019 — أولاً كـ &lt;strong>Blazor Server&lt;/strong>: نموذج يُشغّل كود C# على الخادم ويستخدم اتصال SignalR في الوقت الحقيقي لإرسال تحديثات واجهة المستخدم إلى المتصفح. تبعه &lt;strong>Blazor WebAssembly&lt;/strong> كجزء من ASP.NET Core 3.1 في مايو 2020، مُجلِباً تنفيذاً حقيقياً على جانب العميل للإطار.&lt;/p>
&lt;p>صقّل .NET 6 و7 تجربة المطورين. ثم جاء .NET 8، الذي صدر في نوفمبر 2023، ليُعيد التفكير جذرياً في نموذج التصيير بما أسمته Microsoft &lt;em>واجهة مستخدم ويب متكاملة&lt;/em>. باتت التصيير الثابت من جانب الخادم، والتصيير المتدفق، والخادم التفاعلي، وWebAssembly التفاعلي، ووضع Auto الجديد تتعايش جميعها تحت سقف واحد في مشروع واحد. أسهم .NET 9 في البناء على هذا الأساس وتصحيح العقبات.&lt;/p>
&lt;p>هذا هو المكان الذي نقف فيه اليوم.&lt;/p>
&lt;hr>
&lt;h2 id="ما-هو-blazor-فعلا">ما هو Blazor فعلاً&lt;/h2>
&lt;p>في جوهره، Blazor هو &lt;strong>إطار عمل لواجهة المستخدم مبني على المكونات لـ .NET&lt;/strong>. تبني واجهتك من مكونات — وحدات مستقلة من C# وترميز HTML قادرة على الاحتفاظ بالحالة والاستجابة للأحداث وتأليف هياكل أكبر. إذا عملت مع React أو Vue فسيبدو هذا النموذج الذهني مألوفاً. الفارق الرئيسي أنك تكتب C# بدلاً من JavaScript.&lt;/p>
&lt;p>تُكتب المكونات في Blazor في ملفات &lt;code>.razor&lt;/code>. يمزج ملف &lt;code>.razor&lt;/code> بين ترميز HTML وكود C# باستخدام صيغة Razor التي ربما تعرفها من عروض ASP.NET MVC. هكذا يبدو مكوّن بسيط:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/counter&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;عدّاد&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;العدد الحالي: @currentCount&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button @onclick=&amp;#34;IncrementCount&amp;#34;&amp;gt;انقر هنا&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private int currentCount = 0;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void IncrementCount()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentCount++;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لا أثر لـ JavaScript. يربط توجيه &lt;code>@onclick&lt;/code> النقر على الزر بطريقة C#. يُصيّر التعبير &lt;code>@currentCount&lt;/code> القيمة الحالية. حين تتغير الحالة يُحدد Blazor ما يجب تحديثه في DOM.&lt;/p>
&lt;p>هذا هو الجوهر. كل ما سواه — التوجيه، وحقن التبعيات، والنماذج، واستدعاءات HTTP — مبني فوق نموذج المكونات هذا.&lt;/p>
&lt;hr>
&lt;h2 id="نماذج-التصيير">نماذج التصيير&lt;/h2>
&lt;p>هنا يتمركز كثير من الالتباس، فلنكن دقيقين. &amp;ldquo;Blazor&amp;rdquo; اليوم يُشير إلى عائلة من أوضاع التصيير، لا إلى نموذج نشر وحيد. فهم الفارق مهم لأنه يؤثر في الأداء ومتطلبات البنية التحتية وما يستطيع مكوناتك وما لا تستطيع فعله.&lt;/p>
&lt;h3 id="ssr-الثابت">SSR الثابت&lt;/h3>
&lt;p>أبسط الأوضاع. تُصيَّر مكونات Razor على الخادم إلى HTML ويُرسل ذلك HTML إلى المتصفح. لا اتصال دائم، لا WebAssembly، ولا تفاعلية من جانب العميل افتراضياً. هذا جوهرياً ما تفعله Razor Pages، لكن باستخدام نموذج المكونات.&lt;/p>
&lt;p>استخدمه للصفحات الغنية بالمحتوى، وصفحات الهبوط، وكل ما لا يحتاج إلى تفاعلية حية.&lt;/p>
&lt;h3 id="الخادم-التفاعلي">الخادم التفاعلي&lt;/h3>
&lt;p>يعمل كود المكون على الخادم. تُقام اتصال WebSocket عبر SignalR بين المتصفح والخادم. حين تنقر على زر أو تكتب في حقل إدخال، ينتقل الحدث عبر WebSocket إلى الخادم، يُنفَّذ كود C#، يُحسب الفرق، وتُرسل تحديثات DOM إلى المتصفح.&lt;/p>
&lt;p>&lt;strong>المزايا:&lt;/strong> وصول كامل لموارد الخادم (قواعد البيانات، نظام الملفات، الأسرار). تحميل أولي سريع. حجم تنزيل صغير. يعمل في متصفحات بلا دعم لـ WebAssembly.&lt;/p>
&lt;p>&lt;strong>العيوب:&lt;/strong> كل تفاعل مستخدم يستلزم رحلة ذهاباً وإياباً للخادم، مما يضيف زمن استجابة. تتوسع موارد الخادم مع عدد الاتصالات النشطة. تتدهور التطبيق عند انقطاع الاتصال.&lt;/p>
&lt;p>استخدمه لتطبيقات الأعمال والأدوات الداخلية والتطبيقات التي يُقدَّم فيها الوصول للبيانات على الخادم على حساب قدرة العمل دون اتصال.&lt;/p>
&lt;h3 id="webassembly-التفاعلي">WebAssembly التفاعلي&lt;/h3>
&lt;p>يُنزَّل وقت تشغيل .NET إلى المتصفح ويعمل تطبيقك بالكامل على جانب العميل. بعد التنزيل الأولي يعمل التطبيق بشكل كامل دون اتصال وكل تفاعل فوري — لا رحلات للخادم لمنطق الواجهة.&lt;/p>
&lt;p>&lt;strong>المزايا:&lt;/strong> تنفيذ حقيقي من جانب العميل. يعمل دون اتصال. تقليل الحمل على الخادم بعد تحميل التطبيق.&lt;/p>
&lt;p>&lt;strong>العيوب:&lt;/strong> تنزيل أولي أكبر (وقت تشغيل .NET + تطبيقك). تحميل أبطأ عند أول زيارة. تحتاج إلى API للوصول إلى البيانات من جانب الخادم.&lt;/p>
&lt;p>استخدمه للتطبيقات القادرة على العمل دون اتصال، والأدوات التي تحتاج استجابة فورية، والسيناريوهات التي لا تحتاج فيها الواجهة لمعالجة من الخادم.&lt;/p>
&lt;h3 id="وضع-auto">وضع Auto&lt;/h3>
&lt;p>مُقدَّم في .NET 8. يبدأ التطبيق في وضع الخادم التفاعلي (تحميل أولي سريع، دون انتظار تنزيل WebAssembly). بمجرد تنزيل ملفات WebAssembly في الخلفية تتحول الزيارات اللاحقة تلقائياً إلى وضع WebAssembly.&lt;/p>
&lt;p>يمنحك هذا التحميل الأولي السريع لوضع الخادم والتنفيذ الكامل من جانب العميل لـ WebAssembly في نهاية المطاف. إنه أكثر النماذج تعقيداً في الاستيعاب، لكنه قيمة افتراضية عملية لكثير من التطبيقات.&lt;/p>
&lt;h3 id="مزج-الأوضاع-في-التطبيق-الواحد">مزج الأوضاع في التطبيق الواحد&lt;/h3>
&lt;p>في .NET 8 وما بعده لست مُقيَّداً بوضع تصيير واحد لكامل التطبيق. يمكن أن تكون صفحة الهبوط التسويقية SSR ثابتاً؛ ولوحة التحكم المُصادَق عليها خادماً تفاعلياً؛ وأداة تصور البيانات WebAssembly تفاعلياً. يتولى الإطار إدارة الانتقالات.&lt;/p>
&lt;p>في هذه السلسلة سنبدأ بالخادم التفاعلي لأنه الأسهل تشغيلاً ويتميز بنموذج ذهني أكثر مباشرة. سنستكشف الأوضاع الأخرى مع التقدم.&lt;/p>
&lt;hr>
&lt;h2 id="كيف-يقارن-blazor-بأطر-عمل-javascript">كيف يقارن Blazor بأطر عمل JavaScript&lt;/h2>
&lt;p>إذا بنيت مع React أو Angular أو Vue فهذه على الأرجح المقارنة التي تستفسر عنها.&lt;/p>
&lt;p>&lt;strong>أوجه الشبه حقيقية.&lt;/strong> نموذج مكونات Blazor مشابه عمداً لـ React. لديك props (Parameters في Blazor)، وحالة محلية (حقول في كتلة &lt;code>@code&lt;/code>)، وخطافات دورة الحياة، ونمط الاتصال ذاته: البيانات للأسفل والأحداث للأعلى. إذا عرفت React ستشعر بالألفة في غضون ساعات.&lt;/p>
&lt;p>&lt;strong>الفارق الرئيسي هو اللغة.&lt;/strong> في Blazor تكتب C#. يمكن مشاركة منطق الأعمال وقواعد التحقق ونماذج البيانات بين واجهتك في Blazor وخلفيتك في ASP.NET Core. لن تحتاج بعد الآن لتكرار صنف &lt;code>User&lt;/code> في TypeScript وهو موجود لديك في C#.&lt;/p>
&lt;p>&lt;strong>الفجوة في النظام البيئي حقيقية لكنها تضيق.&lt;/strong> النظام البيئي لـ npm ضخم. النظام البيئي لـ NuGet لمكونات الواجهة أصغر، رغم أنه نما بشكل ملحوظ. إذا احتجت مكتبة رسوم بيانية محددة أو أداة سحب وإفلات فلا يزال JavaScript يوفر خيارات أكثر. لكن لمعظم تطبيقات الأعمال، ما هو متاح في عالم .NET يزيد على الكفاية.&lt;/p>
&lt;p>&lt;strong>التشغيل البيني مع JavaScript موجود حين تحتاجه.&lt;/strong> يتيح لك Blazor استدعاء JavaScript من C# وC# من JavaScript. لواجهات برمجة المتصفح التي تفتقر لغلاف .NET، أو حين تريد استخدام مكتبة JS موجودة، فالتشغيل البيني متاح. يُضيف طبقة لكنه ليس مؤلماً.&lt;/p>
&lt;p>&lt;strong>الإجابة الصادقة:&lt;/strong> إذا كان فريقك بالكامل من مطوري JavaScript ويبنون منتجاً عاماً يُعدّ فيه SEO والأداء ونظام npm البيئي أموراً جوهرية، التزم بـ JavaScript. إذا كان فريقك من مطوري .NET، أو إذا كنت تبني تطبيقات داخلية أو خاصة بالأعمال، أو إذا كانت مشاركة الكود بين الواجهتين الأمامية والخلفية مهمة لك، فـ Blazor خيار مقنع.&lt;/p>
&lt;hr>
&lt;h2 id="ما-تحتاجه-للمتابعة">ما تحتاجه للمتابعة&lt;/h2>
&lt;p>لبقية هذه السلسلة ستحتاج إلى:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>.NET 9 SDK&lt;/strong> — حمّله من &lt;a href="https://dotnet.microsoft.com/download">dot.net&lt;/a>&lt;/li>
&lt;li>&lt;strong>بيئة تطوير&lt;/strong> — Visual Studio 2022 (الإصدار المجتمعي مجاني)، أو VS Code مع امتداد C# Dev Kit، أو JetBrains Rider&lt;/li>
&lt;li>إلمام أساسي بـ C# — الأصناف والخصائص والواجهات وasync/await&lt;/li>
&lt;/ul>
&lt;p>هذا كل شيء. لا Node، لا npm، لا webpack.&lt;/p>
&lt;hr>
&lt;h2 id="الفصل-القادم">الفصل القادم&lt;/h2>
&lt;p>في الفصل الثاني سننشئ تطبيق Blazor الأول لك، ونستعرض هيكل المشروع، ونتأكد من قدرتك على تشغيله محلياً. ستخرج منه بتطبيق يعمل وصورة واضحة عما يفعله كل ملف.&lt;/p>
&lt;p>نلتقي هناك.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>Blazor من الصفر: سلسلة جديدة</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-intro/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-from-scratch-intro/</guid><description>انطلاق سلسلة مطوّلة نستعرض فيها Blazor من الأساس — بلا اختصارات، بلا غموض، فقط شرح واضح وكود حقيقي.</description><content:encoded>&lt;p>أكتب عن Blazor منذ فترة — دورات حياة المكوّنات، CSS المعزول، نماذج التفاعل، المصادقة. كانت هذه المقالات مفيدة بحد ذاتها، لكنني دائمًا شعرت أنها تفتقر إلى الأساس. إذ تفترض أنك تعرف بالفعل ما هو Blazor، ولماذا وُجد، وكيف يندرج في منظومة .NET. ليس الجميع يعرف ذلك، وهذا أمر طبيعي تمامًا.&lt;/p>
&lt;p>لذا أبدأ شيئًا جديدًا: &lt;strong>Blazor من الصفر&lt;/strong>. سلسلة متكاملة، مبنية من الأساس، موجّهة للمطوّرين الذين يريدون فهمًا حقيقيًا لما يبنونه — لا مجرد لصق الكود حتى يعمل.&lt;/p>
&lt;h2 id="لمن-هذه-السلسلة">لمن هذه السلسلة&lt;/h2>
&lt;p>هذه السلسلة مناسبة لك إذا كنت:&lt;/p>
&lt;ul>
&lt;li>مطوّر .NET سمعت عن Blazor لكن لم تجد الوقت أو نقطة الانطلاق المناسبة للتعمق فيه.&lt;/li>
&lt;li>جرّبت Blazor وجعلته يعمل، لكنك تشعر أنك تخمّن &lt;em>لماذا&lt;/em> تعمل الأشياء.&lt;/li>
&lt;li>قادمًا من عالم JavaScript/React/Angular وتريد فهم رؤية Microsoft للواجهة الأمامية الحديثة.&lt;/li>
&lt;li>تريد مرجعًا موحّدًا ومتسقًا بدلًا من توثيق متفرّق ومقالات مبعثرة.&lt;/li>
&lt;/ul>
&lt;p>لا تحتاج إلى أن تكون مطوّرًا متقدمًا. تحتاج فقط إلى إتقان أساسيات C# — الكلاسات، الواجهات، async/await. إذا كنت قادرًا على كتابة API CRUD بسيط في ASP.NET Core، فأنت مستعد.&lt;/p>
&lt;h2 id="ما-سنتناوله">ما سنتناوله&lt;/h2>
&lt;p>إليك خارطة الطريق التقريبية لما أخطط له:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>ما هو Blazor؟&lt;/strong> — نماذج الاستضافة، التاريخ، المقارنة مع تطوير الويب التقليدي&lt;/li>
&lt;li>&lt;strong>تطبيقك الأول في Blazor&lt;/strong> — البنية الأولية للمشروع، التشغيل محليًا&lt;/li>
&lt;li>&lt;strong>المكوّنات&lt;/strong> — اللبنة الأساسية لكل واجهة في Blazor&lt;/li>
&lt;li>&lt;strong>ربط البيانات والأحداث&lt;/strong> — جعل واجهتك تفاعلية&lt;/li>
&lt;li>&lt;strong>التواصل بين المكوّنات&lt;/strong> — المعاملات، EventCallbacks، القيم المتتالية&lt;/li>
&lt;li>&lt;strong>التوجيه والتنقل&lt;/strong> — كيف يتعامل Blazor مع الـ URLs وانتقالات الصفحات&lt;/li>
&lt;li>&lt;strong>حقن التبعيات&lt;/strong> — الخدمات، النطاقات، وحاوية DI في Blazor&lt;/li>
&lt;li>&lt;strong>النماذج والتحقق&lt;/strong> — EditForm، DataAnnotations، المحقّقات المخصصة&lt;/li>
&lt;li>&lt;strong>HTTP والبيانات الخارجية&lt;/strong> — استدعاء APIs من تطبيق Blazor&lt;/li>
&lt;li>&lt;strong>المصادقة والتفويض&lt;/strong> — تأمين تطبيقك بالطريقة الصحيحة&lt;/li>
&lt;li>&lt;strong>التشغيل البيني مع JavaScript&lt;/strong> — متى تحتاج للوصول إلى المتصفح&lt;/li>
&lt;li>&lt;strong>الأداء والتحسين&lt;/strong> — التمثيل الافتراضي، التحميل الكسول، استراتيجيات الرندر&lt;/li>
&lt;li>&lt;strong>اختبار مكوّنات Blazor&lt;/strong> — bUnit وكيف يبدو الاختبار الجيد&lt;/li>
&lt;li>&lt;strong>النشر&lt;/strong> — النشر على Azure وIIS والمضيفين الثابتين&lt;/li>
&lt;/ol>
&lt;p>ستتطور هذه القائمة. بعض المواضيع ستتوسع لتشمل عدة مقالات؛ وقد تتدمج أخرى. سأحدّث هذه المقالة مع تقدم السلسلة وأضيف الروابط عند النشر.&lt;/p>
&lt;h2 id="لماذا-سلسلة-ولماذا-الآن">لماذا سلسلة، ولماذا الآن&lt;/h2>
&lt;p>نضج Blazor كثيرًا. مع .NET 8 و9، أُعيد تصميم نموذج الرندر جذريًا — SSR الثابت، streaming rendering، Server التفاعلي، WebAssembly التفاعلي، والوضع التلقائي، كلها تتعايش الآن تحت سقف واحد. إنه إطار عمل مثير للاهتمام وقادر حقًا، لكن التعقيد المتزايد يجعل تجربة البداية مربكة في أحيان كثيرة.&lt;/p>
&lt;p>أريد بناء مرجع يلتقي بك حيث أنت ويأخذك عبر المنظومة بشكل منهجي. ليس بديلًا عن التوثيق الرسمي — فهو جيد ويجب أن تقرأه — بل رفيق يشرح &lt;em>لماذا&lt;/em> وراء &lt;em>ماذا&lt;/em>.&lt;/p>
&lt;h2 id="كيف-تتابع-السلسلة">كيف تتابع السلسلة&lt;/h2>
&lt;p>كل مقالة في السلسلة ستكون مكتفية بذاتها بما يكفي للقراءة منفردة، لكنها ستتراكم فوق بعضها أيضًا. إذا كنت تبدأ من الصفر، أنصح بالتسلسل. إذا كنت تنضم لسد ثغرة محددة، فهذا مقبول أيضًا — سأضع روابط للمقالات السابقة الضرورية عند الحاجة.&lt;/p>
&lt;p>كود كل مقالة سيكون متاحًا على GitHub. سأشارك الروابط مع التقدم.&lt;/p>
&lt;p>إلى اللقاء في المقالة التالية.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category><category>Series</category></item><item><title>أنواع اتحادات C#: النقابات التمييزية قادمة أخيرًا</title><link>https://emimontesdeoca.github.io/ar/posts/csharp-union-types/</link><pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/csharp-union-types/</guid><description>نظرة متعمقة على أنواع الاتحادات التمييزية القادمة في C# - ما هي، وكيفية عملها، ولماذا ستغير طريقة تصميم نطاقاتك.</description><content:encoded>&lt;p>إذا كنت تكتب C# لفترة طويلة من الوقت، فمن المحتمل جدًا أن تصطدم بحائط عند محاولتك تصميم شيء يمكن أن يكون &amp;ldquo;واحدًا من عدة أشياء&amp;rdquo;. ربما كنت بحاجة إلى طريقة لإرجاع قيمة نجاح أو خطأ. ربما كنت تقوم ببناء نظام دفع يتعامل مع بطاقات الائتمان والتحويلات المصرفية والمحافظ الرقمية، ولكل منها بيانات مختلفة تمامًا. أو ربما نظرت للتو إلى F# أو Rust وفكرت، &amp;ldquo;لماذا لا يمكنني الحصول على ذلك في C#؟&amp;rdquo;&lt;/p>
&lt;p>لقد انتهى الانتظار تقريبًا. &lt;strong>النقابات التي تعاني من التمييز تتجه إلى لغة C#.&lt;/strong>&lt;/p>
&lt;p>لقد كانت هذه إحدى الميزات اللغوية الأكثر طلبًا لسنوات، حيث تمتد مناقشات المجتمع إلى عام 2017 وما قبله. يعمل فريق تصميم لغة C# على اقتراح يقدم كلمة رئيسية &lt;code>union&lt;/code> لتحديد التسلسلات الهرمية للأنواع المغلقة مع مطابقة الأنماط الشاملة. في هذا المنشور، أريد أن أطلعكم على ماهية النقابات التي تعاني من التمييز، وسبب أهميتها إلى هذا الحد، وكيف كنا نقوم بتزييفها حتى الآن، وكيف تبدو الصيغة المقترحة بالفعل - مع أمثلة كود حقيقية لكل منها.&lt;/p>
&lt;p>ملاحظة سريعة قبل أن نتعمق: حتى كتابة هذه السطور، لا تزال ميزة الأنواع الموحدة في مرحلة الاقتراح والمعاينة. يعتمد بناء الجملة والسلوك الذي أصفه هنا على أحدث وثائق التصميم المتاحة للجمهور والمناقشات من فريق تصميم لغة C#. قد تتغير الأمور قبل الإصدار النهائي. سأكون واضحًا بشأن ما تم تأكيده مقابل ما لا يزال قيد المناقشة.&lt;/p>
&lt;h2 id="ما-هي-النقابات-التي-تعاني-من-التمييز">ما هي النقابات التي تعاني من التمييز؟&lt;/h2>
&lt;p>في جوهره، الاتحاد التمييزي (يسمى أحيانًا &amp;ldquo;الاتحاد الموسوم&amp;rdquo; أو &amp;ldquo;نوع المجموع&amp;rdquo;) هو نوع يمكنه الاحتفاظ بواحدة من مجموعة ثابتة من القيم المحتملة، حيث يمكن لكل متغير أن يحمل بيانات مختلفة. الجزء &amp;ldquo;التمييزي&amp;rdquo; يعني أن وقت التشغيل يعرف دائمًا المتغير - هناك علامة تحدده.&lt;/p>
&lt;p>فكر في الأمر مثل &lt;code>enum&lt;/code>، ولكن حيث يمكن لكل عضو حمل حمولته الخاصة من البيانات.&lt;/p>
&lt;p>إذا كنت قد استخدمت لغات أخرى، فمن المحتمل أنك رأيت هذا المفهوم من قبل:&lt;/p>
&lt;p>&lt;strong>F#&lt;/strong> لديها نقابات تمييزية منذ اليوم الأول:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fsharp" data-lang="fsharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">type&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Shape&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> Circle &lt;span style="color:#ff7b72">of&lt;/span> radius&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> Rectangle &lt;span style="color:#ff7b72">of&lt;/span> width&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">*&lt;/span> height&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> Triangle &lt;span style="color:#ff7b72">of&lt;/span> &lt;span style="color:#ff7b72">base&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">*&lt;/span> height&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#ff7b72">float&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>الصدأ&lt;/strong> يستخدم &lt;code>enum&lt;/code> لنفس الفكرة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-rust" data-lang="rust">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">enum&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Shape&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>Circle&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681"> &lt;/span>radius: &lt;span style="color:#ff7b72">f64&lt;/span> },&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>Rectangle&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681"> &lt;/span>width: &lt;span style="color:#ff7b72">f64&lt;/span>,&lt;span style="color:#6e7681"> &lt;/span>height: &lt;span style="color:#ff7b72">f64&lt;/span> },&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>Triangle&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681"> &lt;/span>base: &lt;span style="color:#ff7b72">f64&lt;/span>,&lt;span style="color:#6e7681"> &lt;/span>height: &lt;span style="color:#ff7b72">f64&lt;/span> },&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>}&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يحقق &lt;strong>TypeScript&lt;/strong> شيئًا مشابهًا مع الاتحادات ذات العلامات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-typescript" data-lang="typescript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">type&lt;/span> Shape &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> { kind&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;circle&amp;#34;&lt;/span>; radius: &lt;span style="color:#ff7b72">number&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> { kind&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;rectangle&amp;#34;&lt;/span>; width: &lt;span style="color:#ff7b72">number&lt;/span>; height: &lt;span style="color:#ff7b72">number&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">|&lt;/span> { kind&lt;span style="color:#ff7b72;font-weight:bold">:&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;triangle&amp;#34;&lt;/span>; base: &lt;span style="color:#ff7b72">number&lt;/span>; height: &lt;span style="color:#ff7b72">number&lt;/span> };
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في كل هذه الحالات، يعرف المترجم كل المتغيرات الممكنة ويمكنه أن يفرض عليك التعامل معها جميعًا. هذه هي القوة العظمى: &lt;strong>التحقق الشامل&lt;/strong>. إذا قمت بإضافة متغير جديد، يخبرك المترجم في كل مكان نسيت التعامل معه.&lt;/p>
&lt;p>لم يكن لدى C# أبدًا طريقة من الدرجة الأولى للتعبير عن ذلك. حتى الآن.&lt;/p>
&lt;h2 id="كيف-نحاكي-النقابات-اليوم">كيف نحاكي النقابات اليوم&lt;/h2>
&lt;p>على مر السنين، توصل مجتمع C# إلى العديد من الحلول، ولكل منها مقايضاتها الخاصة. اسمحوا لي أن أتطرق إلى الأساليب الأكثر شيوعًا.&lt;/p>
&lt;h3 id="السجلات-المجردة-بالميراث">السجلات المجردة بالميراث&lt;/h3>
&lt;p>الحل البديل الأكثر اصطلاحًا في لغة C# الحديثة هو استخدام السجلات المجردة ذات الأنواع المشتقة المختومة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Shape&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Circle&lt;/span>(&lt;span style="color:#ff7b72">double&lt;/span> Radius) : Shape;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Rectangle&lt;/span>(&lt;span style="color:#ff7b72">double&lt;/span> Width, &lt;span style="color:#ff7b72">double&lt;/span> Height) : Shape;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Triangle&lt;/span>(&lt;span style="color:#ff7b72">double&lt;/span> Base, &lt;span style="color:#ff7b72">double&lt;/span> Height) : Shape;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> Shape() { } &lt;span style="color:#8b949e;font-style:italic">// Prevent external inheritance&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وهذا يعمل بشكل جيد إلى حد معقول. تحصل على الثبات، والمساواة في القيمة، ويمكنك استخدام مطابقة الأنماط:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">double&lt;/span> Area(Shape shape) =&amp;gt; shape &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Shape.Circle c =&amp;gt; Math.PI * c.Radius * c.Radius,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Shape.Rectangle r =&amp;gt; r.Width * r.Height,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Shape.Triangle t =&amp;gt; &lt;span style="color:#a5d6ff">0.5&lt;/span> * t.Base * t.Height,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> InvalidOperationException(&lt;span style="color:#a5d6ff">&amp;#34;Unknown shape&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```ولكن&lt;/span> &lt;span style="color:#f85149">هناك&lt;/span> &lt;span style="color:#f85149">عيوب&lt;/span> &lt;span style="color:#f85149">كبيرة&lt;/span>. &lt;span style="color:#f85149">لا&lt;/span> &lt;span style="color:#f85149">يعرف&lt;/span> &lt;span style="color:#f85149">المترجم&lt;/span> &lt;span style="color:#f85149">أن&lt;/span> &lt;span style="color:#f85149">التسلسل&lt;/span> &lt;span style="color:#f85149">الهرمي&lt;/span> &lt;span style="color:#f85149">مغلق،&lt;/span> &lt;span style="color:#f85149">لذلك&lt;/span> &lt;span style="color:#f85149">تحتاج&lt;/span> &lt;span style="color:#f85149">دائمًا&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">`&lt;/span>_&lt;span style="color:#f85149">`&lt;/span> &lt;span style="color:#f85149">تجاهل&lt;/span> &lt;span style="color:#f85149">الذراع&lt;/span> &lt;span style="color:#f85149">أو&lt;/span> &lt;span style="color:#f85149">تحصل&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">تحذير&lt;/span>. &lt;span style="color:#f85149">إذا&lt;/span> &lt;span style="color:#f85149">أضفت&lt;/span> &lt;span style="color:#f85149">متغيرًا&lt;/span> &lt;span style="color:#f85149">جديدًا،&lt;/span> &lt;span style="color:#f85149">فلن&lt;/span> &lt;span style="color:#f85149">يخبرك&lt;/span> &lt;span style="color:#f85149">المترجم&lt;/span> &lt;span style="color:#f85149">بجميع&lt;/span> &lt;span style="color:#f85149">الأماكن&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">نسيت&lt;/span> &lt;span style="color:#f85149">التعامل&lt;/span> &lt;span style="color:#f85149">معها&lt;/span> &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">حيث&lt;/span> &lt;span style="color:#f85149">يبتلعها&lt;/span> &lt;span style="color:#f85149">التجاهل&lt;/span> &lt;span style="color:#f85149">بصمت&lt;/span>. &lt;span style="color:#f85149">وهذا&lt;/span> &lt;span style="color:#f85149">يتعارض&lt;/span> &lt;span style="color:#f85149">تمامًا&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">الهدف&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> &lt;span style="color:#f85149">المكتبة&lt;/span> &lt;span style="color:#f85149">الوحيدة&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">هناك&lt;/span> &lt;span style="color:#f85149">طريقة&lt;/span> &lt;span style="color:#f85149">شائعة&lt;/span> &lt;span style="color:#f85149">أخرى&lt;/span> &lt;span style="color:#f85149">وهي&lt;/span> &lt;span style="color:#f85149">حزمة&lt;/span> [OneOf](https:&lt;span style="color:#8b949e;font-style:italic">//github.com/mcintyre321/OneOf) NuGet:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> OneOf&amp;lt;Success&amp;lt;Order&amp;gt;, NotFound, ValidationError&amp;gt; ProcessOrder(OrderRequest request)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Usage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = ProcessOrder(request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>result.Switch(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> success =&amp;gt; Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Order {success.Value.Id} processed&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> notFound =&amp;gt; Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;Order not found&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> error =&amp;gt; Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Validation failed: {error.Message}&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يوفر OneOf فحصًا شاملاً في وقت الترجمة من خلال معلمات النوع العامة، وهو أمر رائع. ولكنها تعتمد على المطابقة الموضعية (النوع الأول، النوع الثاني، وما إلى ذلك)، وتصبح التوقيعات العامة غير عملية بسرعة، ولا تتكامل مع مطابقة أنماط اللغة. إنه اختراق ذكي، لكنه لا يزال اختراقًا.&lt;/p>
&lt;h3 id="التعداد-اليدوي--نمط-البيانات">التعداد اليدوي + نمط البيانات&lt;/h3>
&lt;p>يتبع بعض المطورين المسار الكلاسيكي باستخدام علامة التعداد وحاوية البيانات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">enum&lt;/span> PaymentType { CreditCard, BankTransfer, DigitalWallet }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Payment&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> PaymentType Type { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CreditCardInfo? CreditCard { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> BankTransferInfo? BankTransfer { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DigitalWalletInfo? DigitalWallet { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">init&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا هش. لا شيء يمنعك من ضبط &lt;code>Type&lt;/code> على &lt;code>CreditCard&lt;/code> ولكن ملء خاصية &lt;code>BankTransfer&lt;/code>. لا يستطيع المترجم مساعدتك، وينتهي الأمر بأخطاء وقت التشغيل وعمليات التحقق من القيمة الفارغة في كل مكان. إنه أسلوب &amp;ldquo;الكتابة الصارمة&amp;rdquo; في نمذجة الكتابة، ولا يمكن قياسه.&lt;/p>
&lt;p>تشترك كل هذه الأساليب في مشكلة أساسية: **إنهم يحاربون اللغة بدلاً من العمل بها. ** لا يستطيع المترجم التفكير في مجموعة الاحتمالات المغلقة، لذلك تفقد الخاصية الأكثر قيمة للنقابات التمييزية - التحقق الشامل.&lt;/p>
&lt;h2 id="اقتراح-c-الكلمة-الرئيسية-union">اقتراح C#: الكلمة الرئيسية &lt;code>union&lt;/code>&lt;/h2>
&lt;p>يقدم اقتراح فريق لغة C# كلمة رئيسية مخصصة &lt;code>union&lt;/code> تحدد مجموعة مغلقة من الأعضاء المحددين، كل منهم يحمل بيانات اختياريًا. إليك بناء الجملة الأساسي كما تم اقتراحه:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Shape
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(&lt;span style="color:#ff7b72">double&lt;/span> Radius),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Rectangle(&lt;span style="color:#ff7b72">double&lt;/span> Width, &lt;span style="color:#ff7b72">double&lt;/span> Height),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Triangle(&lt;span style="color:#ff7b72">double&lt;/span> Base, &lt;span style="color:#ff7b72">double&lt;/span> Height)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا كل شيء. نظيفة وموجزة، ويمكن قراءتها على الفور. يحدد كل عضو داخل الاتحاد متغيرًا مميزًا ببياناته الخاصة. يعرف المترجم أن &lt;code>Shape&lt;/code> يمكن أن يكون واحدًا فقط من هذه الأشياء الثلاثة.&lt;/p>
&lt;p>تحت الغطاء، يقوم المترجم بإنشاء تسلسل هرمي للنوع المختوم - مشابه لما تكتبه يدويًا مع السجلات المجردة، ولكن مع وعي المترجم الكامل بالطبيعة المغلقة للنوع. هذا يعني أن المترجم يمكنه فرض الشمولية في مطابقة الأنماط، وهي الميزة الأساسية.&lt;/p>
&lt;h3 id="الأعضاء-ذوو-القيمة-فقط">الأعضاء ذوو القيمة فقط&lt;/h3>
&lt;p>لا يتعين على أعضاء الاتحاد حمل البيانات. يمكنك مزج الأعضاء الحاملين للبيانات مع الأعضاء ذوي القيمة البسيطة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Option&amp;lt;T&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Some(T Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> None
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا هو النوع الكلاسيكي &lt;code>Option&lt;/code>/&lt;code>Maybe&lt;/code> الذي ظل المبرمجون الوظيفيون يطلبونه في لغة C# لسنوات. &lt;code>None&lt;/code> لا يحمل أي بيانات — إنها مجرد علامة.&lt;/p>
&lt;h3 id="النقابات-العامة">النقابات العامة&lt;/h3>
&lt;p>كما ترون من المثال &lt;code>Option&amp;lt;T&amp;gt;&lt;/code> أعلاه، تدعم النقابات الأدوية العامة. فيما يلي مثال أكثر تعقيدًا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Result&amp;lt;T, E&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Ok(T Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(E Err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يفتح هذا أسلوبًا كاملاً لمعالجة الأخطاء لا يعتمد على الاستثناءات لحالات الفشل المتوقعة - وهو أمر كان ممارسة قياسية في Rust واللغات الوظيفية لسنوات.&lt;/p>
&lt;h3 id="النقابات-مع-الأساليب">النقابات مع الأساليب&lt;/h3>
&lt;p>يسمح الاقتراح أيضًا للنقابات بالحصول على أساليب وخصائص محسوبة وتنفيذ واجهات، تمامًا مثل أي نوع آخر:```csharp
union Shape
{
Circle(double Radius),
Rectangle(double Width, double Height),
Triangle(double Base, double Height);&lt;/p>
&lt;pre>&lt;code>public double Area =&amp;gt; this switch
{
Circle(var r) =&amp;gt; Math.PI * r * r,
Rectangle(var w, var h) =&amp;gt; w * h,
Triangle(var b, var h) =&amp;gt; 0.5 * b * h
};
public double Perimeter =&amp;gt; this switch
{
Circle(var r) =&amp;gt; 2 * Math.PI * r,
Rectangle(var w, var h) =&amp;gt; 2 * (w + h),
Triangle(var b, var h) =&amp;gt; b + h + Math.Sqrt(b * b + h * h)
};
&lt;/code>&lt;/pre>
&lt;p>}&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">لاحظ&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">كيف&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تعبير&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>switch&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">داخل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>Area&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">و&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>Perimeter&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يحتاج&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إلى&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ذراع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">افتراضي&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يعرف&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المترجم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الاتحاد&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">شامل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">هناك&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ثلاثة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">متغيرات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فقط،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ويتم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التعامل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الثلاثة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">جميعًا&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">قمت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بإضافة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">متغير&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">رابع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لاحقًا،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فسيقوم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المترجم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بوضع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">علامة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">على&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">كل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>switch&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الذي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يتعامل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">معه&lt;/span>.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">## تكامل مطابقة الأنماط
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">لقد&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تطورت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مطابقة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الأنماط&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">في&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لغة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>C&lt;span style="color:#8b949e;font-style:italic"># منذ الإصدار 7.0، وتم تصميم أنواع الاتحاد لتكون مواطنًا من الدرجة الأولى في هذا النظام.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">### تعبيرات التبديل الشاملة
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">الميزة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الأكثر&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تأثيرًا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">هي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فحص&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التبديل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الشامل&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">النقابات،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المترجم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#f85149">يعرف&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">جميع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الحالات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المحتملة&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">```&lt;/span>csharp&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>string&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">Describe&lt;/span>(Shape&lt;span style="color:#6e7681"> &lt;/span>shape)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>shape&lt;span style="color:#6e7681"> &lt;/span>switch&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">{&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">Circle&lt;/span>(var&lt;span style="color:#6e7681"> &lt;/span>r)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;A circle with radius {r}&amp;#34;&lt;/span>,&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">Rectangle&lt;/span>(var&lt;span style="color:#6e7681"> &lt;/span>w,&lt;span style="color:#6e7681"> &lt;/span>var&lt;span style="color:#6e7681"> &lt;/span>h)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;A {w}x{h} rectangle&amp;#34;&lt;/span>,&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">Triangle&lt;/span>(var&lt;span style="color:#6e7681"> &lt;/span>b,&lt;span style="color:#6e7681"> &lt;/span>var&lt;span style="color:#6e7681"> &lt;/span>h)&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;A triangle with base {b} and height {h}&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">}&lt;/span>;&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لا يوجد ذراع تجاهل. لا &lt;code>_ =&amp;gt; throw new NotImplementedException()&lt;/code>. إذا نسيت حالة ما، فسيقوم المترجم بإصدار خطأ، وليس تحذيرًا. وهذا تحسن أساسي في السلامة.&lt;/p>
&lt;h3 id="مطابقة-الأنماط-المتداخلة">مطابقة الأنماط المتداخلة&lt;/h3>
&lt;p>تتكون النقابات بشكل طبيعي من أنماط متداخلة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Expr
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Literal(&lt;span style="color:#ff7b72">double&lt;/span> Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Add(Expr Left, Expr Right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Multiply(Expr Left, Expr Right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Negate(Expr Inner)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">double&lt;/span> Evaluate(Expr expr) =&amp;gt; expr &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Literal(&lt;span style="color:#ff7b72">var&lt;/span> v) =&amp;gt; v,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Add(&lt;span style="color:#ff7b72">var&lt;/span> left, &lt;span style="color:#ff7b72">var&lt;/span> right) =&amp;gt; Evaluate(left) + Evaluate(right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Multiply(&lt;span style="color:#ff7b72">var&lt;/span> left, &lt;span style="color:#ff7b72">var&lt;/span> right) =&amp;gt; Evaluate(left) * Evaluate(right),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Negate(&lt;span style="color:#ff7b72">var&lt;/span> inner) =&amp;gt; -Evaluate(inner)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا النوع من بنية البيانات العودية شائع للغاية في المترجمين والمترجمين الفوريين ومحركات القواعد والنمذجة الرياضية. اليوم في لغة C# ستحتاج إلى تسلسل هرمي عميق للفصل ونمط الزائر. مع النقابات، الكود أبسط بشكل كبير.&lt;/p>
&lt;h3 id="شروط-الحراسة">شروط الحراسة&lt;/h3>
&lt;p>مطابقة الأنماط مع النقابات تدعم حراس &lt;code>when&lt;/code> تمامًا كما تتوقع:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> Classify(Shape shape) =&amp;gt; shape &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(&lt;span style="color:#ff7b72">var&lt;/span> r) when r &amp;gt; &lt;span style="color:#a5d6ff">100&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Large circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(&lt;span style="color:#ff7b72">var&lt;/span> r) when r &amp;gt; &lt;span style="color:#a5d6ff">10&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Medium circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Circle(_) =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Small circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Rectangle(&lt;span style="color:#ff7b72">var&lt;/span> w, &lt;span style="color:#ff7b72">var&lt;/span> h) when w == h =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Square&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Rectangle _ =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Rectangle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Triangle _ =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Triangle&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="أمثلة-عملية">أمثلة عملية&lt;/h2>
&lt;p>اسمحوا لي أن أستعرض بعض السيناريوهات الواقعية حيث تعمل أنواع الاتحادات على تحسين الكود بشكل كبير.&lt;/p>
&lt;h3 id="نمط-النتيجة-استبدال-الاستثناءات-للأخطاء-المتوقعة">نمط النتيجة: استبدال الاستثناءات للأخطاء المتوقعة&lt;/h3>
&lt;p>أحد الأنماط الأكثر شيوعًا في تطوير التطبيقات الحديثة هو تمثيل العمليات التي يمكن أن تنجح أو تفشل دون استخدام استثناءات للتحكم في التدفق. يجب أن تكون الاستثناءات استثنائية — أشياء مثل فشل الشبكة أو حالات نفاد الذاكرة. يعد خطأ التحقق من الصحة أو نتيجة &amp;ldquo;لم يتم العثور عليه&amp;rdquo; نتيجة متوقعة، وليس استثناءً.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union Result&amp;lt;T, E&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Ok(T Value),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(E Err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>union OrderError
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotFound(Guid OrderId),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> InsufficientStock(&lt;span style="color:#ff7b72">string&lt;/span> ProductId, &lt;span style="color:#ff7b72">int&lt;/span> Requested, &lt;span style="color:#ff7b72">int&lt;/span> Available),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PaymentDeclined(&lt;span style="color:#ff7b72">string&lt;/span> Reason),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ValidationFailed(IReadOnlyList&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Errors)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Result&amp;lt;Order, OrderError&amp;gt; ProcessOrder(OrderRequest request)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Validate(request, &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> errors))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationFailed(errors);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> product = catalog.Find(request.ProductId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (product &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> NotFound(request.OrderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (product.Stock &amp;lt; request.Quantity)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> InsufficientStock(request.ProductId, request.Quantity, product.Stock);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> paymentResult = paymentGateway.Charge(request.Payment);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!paymentResult.Success)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> PaymentDeclined(paymentResult.Message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> order = CreateOrder(request, product);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Ok(order);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يُجبر المتصل بعد ذلك على التعامل مع كل النتائج المحتملة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = ProcessOrder(request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> response = result &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Ok(&lt;span style="color:#ff7b72">var&lt;/span> order) =&amp;gt; Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/orders/{order.Id}&amp;#34;&lt;/span>, order),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(NotFound(&lt;span style="color:#ff7b72">var&lt;/span> id)) =&amp;gt; Results.NotFound(&lt;span style="color:#a5d6ff">$&amp;#34;Order {id} not found&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(InsufficientStock(&lt;span style="color:#ff7b72">var&lt;/span> pid, &lt;span style="color:#ff7b72">var&lt;/span> req, &lt;span style="color:#ff7b72">var&lt;/span> avail)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.Conflict(&lt;span style="color:#a5d6ff">$&amp;#34;Product {pid}: requested {req}, only {avail} available&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(PaymentDeclined(&lt;span style="color:#ff7b72">var&lt;/span> reason)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.UnprocessableEntity(&lt;span style="color:#a5d6ff">$&amp;#34;Payment declined: {reason}&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Error(ValidationFailed(&lt;span style="color:#ff7b72">var&lt;/span> errors)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Errors = errors })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لا توجد كتل للمحاولة، ولا توجد أنواع استثناءات منسية، ولا توجد مفاجآت في وقت التشغيل. يكون كل وضع فشل مرئيًا في توقيع النوع ويتم فرضه بواسطة المترجم. يعد هذا تحسينًا هائلاً لموثوقية واجهة برمجة التطبيقات (API).&lt;/p>
&lt;h3 id="نمذجة-النطاق-أنواع-الدفع">نمذجة النطاق: أنواع الدفع&lt;/h3>
&lt;p>فيما يلي مثال لنمذجة النطاق الواقعي — التعامل مع طرق الدفع المختلفة في نظام التجارة الإلكترونية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union PaymentMethod
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreditCard(&lt;span style="color:#ff7b72">string&lt;/span> CardNumber, &lt;span style="color:#ff7b72">string&lt;/span> Expiry, &lt;span style="color:#ff7b72">string&lt;/span> Cvv),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BankTransfer(&lt;span style="color:#ff7b72">string&lt;/span> Iban, &lt;span style="color:#ff7b72">string&lt;/span> Bic),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet(&lt;span style="color:#ff7b72">string&lt;/span> Provider, &lt;span style="color:#ff7b72">string&lt;/span> Token),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CashOnDelivery
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">decimal&lt;/span> CalculateProcessingFee(PaymentMethod method, &lt;span style="color:#ff7b72">decimal&lt;/span> amount) =&amp;gt; method &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreditCard _ =&amp;gt; amount * &lt;span style="color:#a5d6ff">0.029&lt;/span>m + &lt;span style="color:#a5d6ff">0.30&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BankTransfer _ =&amp;gt; &lt;span style="color:#a5d6ff">1.50&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet(provider: &lt;span style="color:#a5d6ff">&amp;#34;PayPal&amp;#34;&lt;/span>, _) =&amp;gt; amount * &lt;span style="color:#a5d6ff">0.034&lt;/span>m + &lt;span style="color:#a5d6ff">0.35&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet _ =&amp;gt; amount * &lt;span style="color:#a5d6ff">0.025&lt;/span>m,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CashOnDelivery =&amp;gt; &lt;span style="color:#a5d6ff">4.99&lt;/span>m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> FormatForReceipt(PaymentMethod method) =&amp;gt; method &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreditCard(&lt;span style="color:#ff7b72">var&lt;/span> num, _, _) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Credit Card ending in {num[^4..]}&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BankTransfer(&lt;span style="color:#ff7b72">var&lt;/span> iban, _) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Bank Transfer ({iban[..4]}****)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DigitalWallet(&lt;span style="color:#ff7b72">var&lt;/span> provider, _) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Digital Wallet ({provider})&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CashOnDelivery =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Cash on Delivery&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>قارن هذا بالنهج الحالي حيث سيكون لديك واجهة أو فئة مجردة بخمسة تطبيقات مختلفة موزعة على خمسة ملفات، ربما مع نمط زائر في الأعلى. يحافظ النهج الموحد على تعريف البيانات والعمليات معًا، بحيث تكون قابلة للقراءة ومدققة بشكل شامل.&lt;/p>
&lt;h3 id="آلات-الدولة">آلات الدولة&lt;/h3>
&lt;p>أجهزة الحالة موجودة في كل مكان في البرامج — معالجة الطلبات، ومحركات سير العمل، وإدارة الاتصال، وحالة واجهة المستخدم. النقابات تجعلها واضحة وآمنة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>union ConnectionState
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Disconnected,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Connecting(&lt;span style="color:#ff7b72">string&lt;/span> Host, &lt;span style="color:#ff7b72">int&lt;/span> Port, DateTime StartedAt),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Connected(&lt;span style="color:#ff7b72">string&lt;/span> Host, &lt;span style="color:#ff7b72">int&lt;/span> Port, TcpClient Client),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Reconnecting(&lt;span style="color:#ff7b72">string&lt;/span> Host, &lt;span style="color:#ff7b72">int&lt;/span> Port, &lt;span style="color:#ff7b72">int&lt;/span> Attempt, TimeSpan Delay),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Failed(&lt;span style="color:#ff7b72">string&lt;/span> Host, Exception Error)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ConnectionState HandleEvent(ConnectionState state, ConnectionEvent evt) =&amp;gt; (state, evt) &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Disconnected, Connect(&lt;span style="color:#ff7b72">var&lt;/span> host, &lt;span style="color:#ff7b72">var&lt;/span> port)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Connecting(host, port, DateTime.UtcNow),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Connecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, _), ConnectionSucceeded(&lt;span style="color:#ff7b72">var&lt;/span> client)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Connected(h, p, client),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Connecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, _), ConnectionFailed(&lt;span style="color:#ff7b72">var&lt;/span> ex)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Reconnecting(h, p, Attempt: &lt;span style="color:#a5d6ff">1&lt;/span>, Delay: TimeSpan.FromSeconds(&lt;span style="color:#a5d6ff">1&lt;/span>)),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Reconnecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, &lt;span style="color:#ff7b72">var&lt;/span> attempt, _), ConnectionSucceeded(&lt;span style="color:#ff7b72">var&lt;/span> client)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Connected(h, p, client),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Reconnecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, &lt;span style="color:#ff7b72">var&lt;/span> attempt, _), ConnectionFailed(&lt;span style="color:#ff7b72">var&lt;/span> ex)) when attempt &amp;gt;= &lt;span style="color:#a5d6ff">5&lt;/span> =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Failed(h, ex),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Reconnecting(&lt;span style="color:#ff7b72">var&lt;/span> h, &lt;span style="color:#ff7b72">var&lt;/span> p, &lt;span style="color:#ff7b72">var&lt;/span> attempt, _), ConnectionFailed(_)) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Reconnecting(h, p, attempt + &lt;span style="color:#a5d6ff">1&lt;/span>, TimeSpan.FromSeconds(Math.Pow(&lt;span style="color:#a5d6ff">2&lt;/span>, attempt))),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Connected(_, _, &lt;span style="color:#ff7b72">var&lt;/span> client), Disconnect) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Disconnected,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; state &lt;span style="color:#8b949e;font-style:italic">// Ignore events that don&amp;#39;t apply to current state&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تحمل كل ولاية بالضبط البيانات ذات الصلة بتلك الدولة. لا يمكنك الوصول عن طريق الخطأ إلى &lt;code>TcpClient&lt;/code> عندما تكون في حالة &lt;code>Connecting&lt;/code> لأنها غير موجودة في هذا المتغير. يفرض نظام الكتابة ثوابت آلة الحالة.&lt;/p>
&lt;h2 id="اعتبارات-التسلسل-والتشغيل-المتداخلأحد-الأسئلة-العملية-التي-تطرح-فورا-مع-أنواع-الاتحادات-هو-كيف-تقوم-بتسلسلها-إذا-كنت-تقوم-بإنشاء-واجهات-برمجة-التطبيقات-أو-تخزين-البيانات-فستحتاج-إلى-تسلسل-json-للعمل-بشكل-صحيح">اعتبارات التسلسل والتشغيل المتداخلأحد الأسئلة العملية التي تطرح فورًا مع أنواع الاتحادات هو: كيف تقوم بتسلسلها؟ إذا كنت تقوم بإنشاء واجهات برمجة التطبيقات أو تخزين البيانات، فستحتاج إلى تسلسل JSON للعمل بشكل صحيح.&lt;/h2>
&lt;p>لقد ناقش فريق التصميم دعم &lt;code>System.Text.Json&lt;/code> المدمج لأنواع الاتحادات. يتضمن النهج المتوقع إجراء تسلسل باستخدام خاصية التمييز:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;$type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Circle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;radius&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">5.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;$type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Rectangle&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;width&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">10.0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;height&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">20.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يتوافق هذا مع دعم التسلسل متعدد الأشكال الحالي المقدم في .NET 7 بسمات &lt;code>JsonDerivedType&lt;/code>. من المتوقع أن تعمل النقابات مع &lt;code>System.Text.Json&lt;/code> خارج الصندوق، باستخدام اسم المتغير كمميز للنوع افتراضيًا.&lt;/p>
&lt;p>بالنسبة لـ Entity Framework Core، فإن النهج المحتمل هو تخزين القيم الموحدة باستخدام عمود تمييز - على غرار كيفية عمل تعيين وراثة الجدول لكل تسلسل هرمي (TPH) بالفعل. لا يزال التكامل الدقيق لـ EF Core قيد التصميم، ولكن البنية التحتية للتعامل مع التسلسلات الهرمية من النوع المغلق موجودة بالفعل.&lt;/p>
&lt;p>تجدر الإشارة إلى أن التشغيل المتداخل مع لغات .NET الأخرى يجب أن يكون سلسًا، حيث سيتم تجميع النقابات إلى تسلسلات هرمية قياسية لفئة IL تحت الغطاء. كود F# الذي يستهلك اتحاد C# سوف يراه كنوع تسلسل هرمي قياسي، والعكس صحيح.&lt;/p>
&lt;h2 id="كيفية-تجربتها">كيفية تجربتها&lt;/h2>
&lt;p>اعتبارًا من وقت كتابة هذا التقرير، تتوفر أنواع الاتحاد كميزة معاينة في أحدث معاينات .NET SDK. لتجربة بناء الجملة المقترح، ستحتاج إلى:&lt;/p>
&lt;ol>
&lt;li>قم بتثبيت أحدث إصدار من SDK لمعاينة .NET&lt;/li>
&lt;li>قم بتمكين إصدار لغة المعاينة في ملف مشروعك:&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;Project&lt;/span> Sdk=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;TargetFramework&amp;gt;&lt;/span>net10.0&lt;span style="color:#7ee787">&amp;lt;/TargetFramework&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;LangVersion&amp;gt;&lt;/span>preview&lt;span style="color:#7ee787">&amp;lt;/LangVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/Project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ضع في اعتبارك أن ميزات المعاينة عرضة للتغيير. قد يتطور بناء الجملة والسلوك وتشخيصات المترجم بشكل ملحوظ قبل الإصدار النهائي. لا تشحن كود الإنتاج بالاعتماد على ميزات لغة المعاينة - ولكن قم بتجربتها وقدم تعليقاتك. يقوم فريق C# بمراقبة مناقشات مستودع &lt;a href="https://github.com/dotnet/csharplang">csharpang&lt;/a> بشكل نشط.&lt;/p>
&lt;p>إذا كنت تريد متابعة تقدم الاقتراح، فإن الأماكن الرئيسية التي يجب مراقبتها هي:&lt;/p>
&lt;ul>
&lt;li>مستودع &lt;a href="https://github.com/dotnet/csharplang">dotnet/csharpang&lt;/a> لمناقشات تصميم اللغة&lt;/li>
&lt;li>مستودع &lt;a href="https://github.com/dotnet/roslyn">dotnet/roslyn&lt;/a> لتقدم تنفيذ المترجم&lt;/li>
&lt;li>مدونة .NET للإعلانات الرسمية&lt;/li>
&lt;/ul>
&lt;h2 id="المقارنة-مع-المقاربات-الموجودة">المقارنة مع المقاربات الموجودة&lt;/h2>
&lt;p>اسمحوا لي أن أقوم بإجراء مقارنة سريعة حتى تتمكن من رؤية كيفية تكديس الأساليب المختلفة:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ميزة&lt;/th>
&lt;th>سجلات مجردة&lt;/th>
&lt;th>واحد&amp;lt;T1,T2&amp;gt;&lt;/th>
&lt;th>التعداد + البيانات&lt;/th>
&lt;th>أنواع الاتحاد&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>فحص الشمولية&lt;/td>
&lt;td>❌ لا&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;td>❌ لا&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>مطابقة الأنماط&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;td>❌ محدودة&lt;/td>
&lt;td>❌ دليل&lt;/td>
&lt;td>✅ أصلي&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>إغلاق القسري للمترجم&lt;/td>
&lt;td>❌ لا&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;td>❌ لا&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>البيانات لكل متغير&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;td>⚠️ هشة&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>سهولة القراءة&lt;/td>
&lt;td>⚠️ مطول&lt;/td>
&lt;td>⚠️ الموضعية&lt;/td>
&lt;td>❌فقير&lt;/td>
&lt;td>✅ ممتاز&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>التسلسل&lt;/td>
&lt;td>✅ دليل&lt;/td>
&lt;td>⚠️ مجمع&lt;/td>
&lt;td>✅ دليل&lt;/td>
&lt;td>✅ مدمج&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>نموذجي&lt;/td>
&lt;td>⚠️ معتدل&lt;/td>
&lt;td>✅ منخفض&lt;/td>
&lt;td>⚠️ عالي&lt;/td>
&lt;td>✅ الحد الأدنى&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>لا تبعية خارجية&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;td>❌ نوجيت&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;td>✅ نعم&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="ماذا-يعني-هذا-بالنسبة-للنظام-البيئي-net">ماذا يعني هذا بالنسبة للنظام البيئي .NET&lt;/h2>
&lt;p>إن إدخال النقابات التمييزية سوف ينتشر عبر النظام البيئي .NET بأكمله. وإليكم ما أتوقع رؤيته:&lt;/p>
&lt;p>&lt;strong>سيتم تحسين تصميم المكتبة.&lt;/strong> ستتمكن واجهات برمجة التطبيقات التي تعرض حاليًا &lt;code>null&lt;/code> للإشارة إلى &amp;ldquo;لم يتم العثور عليه&amp;rdquo; أو طرح استثناءات لفشل التحقق من الصحة، من إرجاع أنواع &lt;code>Result&amp;lt;T, E&amp;gt;&lt;/code> بدلاً من ذلك. وهذا يجعل أوضاع الفشل واضحة في توقيع النوع - يمكنك معرفة الخطأ الذي يمكن أن يحدث من خلال النظر إلى توقيع الطريقة، وليس من خلال قراءة الوثائق أو التعليمات البرمجية المصدر.&lt;/p>
&lt;p>&lt;strong>أصبحت نمذجة المجال أكثر تعبيرًا.&lt;/strong> تقلصت الفجوة بين مجال المشكلة وتمثيل الكود بشكل كبير. عندما يقول خبير المجال الخاص بك &amp;ldquo;يمكن أن تكون الدفعة عبارة عن بطاقة ائتمان، أو تحويل مصرفي، أو نقدًا عند التسليم&amp;rdquo;، يمكنك تصميم ذلك مباشرةً كاتحاد بدلاً من ترجمته إلى تسلسل هرمي للميراث.&lt;/p>
&lt;p>&lt;strong>أصبحت أفكار F# متاحة لمطوري C#.&lt;/strong> لقد أعجب العديد من مطوري C# بنظام كتابة F# عن بعد ولكنهم لم يتمكنوا من اعتماد F# في مؤسساتهم. تقدم أنواع Union إحدى أقوى ميزات F# إلى C#، وهو ما يعد مكسبًا لنظام .NET البيئي بأكمله.&lt;/p>
&lt;p>&lt;strong>أخطاء أقل في وقت التشغيل.&lt;/strong> سيؤدي التحقق الشامل وحده إلى منع فئات كاملة من الأخطاء. في كل مرة تقوم فيها بإضافة متغير جديد إلى الاتحاد، سيرشدك المترجم إلى كل مكان في قاعدة التعليمات البرمجية الذي يحتاج إلى التحديث. لا مزيد من حالات &lt;code>switch&lt;/code> المنسية، ولا مزيد من &lt;code>NotImplementedException&lt;/code> في الفروع الافتراضية التي تظهر فقط في الإنتاج.&lt;/p>
&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>كانت النقابات التي تعاني من التمييز على رأس قائمة أمنيات مجتمع C# منذ ما يقرب من عقد من الزمن، وذلك لسبب وجيه. إنهم يحلون فجوة أساسية في نظام الكتابة - القدرة على نمذجة البيانات التي يمكن أن تكون &amp;ldquo;أحد الأشياء العديدة&amp;rdquo; مع الأمان الذي يفرضه المترجم.&lt;/p>
&lt;p>توفر الكلمة الأساسية &lt;code>union&lt;/code> المقترحة بنية واضحة وموجزة تتكامل بشكل عميق مع مطابقة أنماط C#، وتعمل مع الأدوية العامة، وتدعم الأساليب والواجهات، وتتيح فحصًا شاملاً لاكتشاف الأخطاء في وقت الترجمة بدلاً من وقت التشغيل.&lt;/p>
&lt;p>سواء كنت تقوم ببناء نماذج المجال، أو تصميم واجهات برمجة التطبيقات (APIs) مع أنواع أخطاء واضحة، أو تنفيذ أجهزة الحالة، أو مجرد محاولة استبدال التسلسلات الهرمية الموروثة بشيء أكثر طبيعية - فإن أنواع الاتحاد ستغير طريقة كتابتك لـ C#.&lt;/p>
&lt;p>لقد كنا ننتظر وقتا طويلا لهذا. إن بناء الجملة أنيق، والتكامل مع ميزات اللغة الحالية مدروس، وسيكون التأثير العملي محسوسًا عبر النظام البيئي .NET بأكمله.&lt;/p>
&lt;p>راقب إصدارات المعاينة، وقم بتجربة الميزة، وقدم ملاحظاتك إلى فريق تصميم اللغة. هذه واحدة من تلك الميزات التي، بمجرد استخدامها، سوف تتساءل كيف كنت تعيش بدونها.&lt;/p></content:encoded><category>.NET</category><category>C#</category></item><item><title>بناء نظام RAG في C# مع Semantic Kernel</title><link>https://emimontesdeoca.github.io/ar/posts/rag-csharp-semantic-kernel/</link><pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/rag-csharp-semantic-kernel/</guid><description>تنفيذ عملية الاسترجاع المعزز في لغة C# باستخدام النواة الدلالية والتضمينات والبحث عن المتجهات.</description><content:encoded>&lt;h2 id="مقدمة">مقدمة&lt;/h2>
&lt;p>إذا حاولت استخدام LLM للإجابة على أسئلة حول بياناتك الخاصة - مستندات الشركة، ومواصفات المنتج، وقواعد المعرفة الداخلية - فمن المحتمل أنك لاحظت أنها إما تهذي أو تقول فقط &amp;ldquo;ليس لدي معلومات حول ذلك&amp;rdquo;. وذلك لأن النموذج يعرف فقط ما تم تدريبه عليه.&lt;/p>
&lt;p>يعمل RAG (جيل الاسترجاع المعزز) على إصلاح هذه المشكلة. بدلاً من ضبط نموذج على بياناتك، يمكنك استرداد الأجزاء ذات الصلة من مستنداتك في وقت الاستعلام وتمريرها إلى LLM كسياق. يقوم النموذج بعد ذلك بإنشاء إجابات ترتكز على بياناتك الفعلية.&lt;/p>
&lt;p>في هذا المنشور، سأرشدك خلال عملية إنشاء مسار RAG كامل في لغة C# باستخدام Semantic Kernel.&lt;/p>
&lt;h2 id="كيف-يعمل-rag">كيف يعمل RAG&lt;/h2>
&lt;p>التدفق واضح ومباشر:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>الاستيعاب&lt;/strong>: قم بتقسيم مستنداتك إلى أجزاء، وقم بإنشاء تضمينات لكل جزء، ثم قم بتخزينها في قاعدة بيانات متجهة&lt;/li>
&lt;li>&lt;strong>الاستعلام&lt;/strong>: عندما يطرح المستخدم سؤالاً، أنشئ تضمينًا للاستعلام، وابحث في قاعدة بيانات المتجهات عن أجزاء مماثلة&lt;/li>
&lt;li>&lt;strong>إنشاء&lt;/strong>: قم بتمرير الأجزاء المستردة كسياق إلى LLM مع سؤال المستخدم&lt;/li>
&lt;/ol>
&lt;p>هذا كل شيء. يكمن السحر في التضمينات - فهي تلتقط المعنى الدلالي للنص كمتجهات، بحيث يمكنك العثور على المحتوى ذي الصلة حتى عندما لا تتطابق الكلمات تمامًا.&lt;/p>
&lt;h2 id="المتطلبات-الأساسية">المتطلبات الأساسية&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Extensions.VectorData.Abstractions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.InMemory
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بالنسبة للإنتاج، يمكنك تبديل مخزن الذاكرة بـ Azure AI Search أو Qdrant أو Pinecone أو أي قاعدة بيانات متجهة أخرى مدعومة. لكن الذاكرة الداخلية مثالية للتعلم وإنشاء النماذج الأولية.&lt;/p>
&lt;h2 id="إعداد-النواة">إعداد النواة&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.AzureOpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.VectorData&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.InMemory&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Embeddings&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAITextEmbeddingGeneration(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;text-embedding-3-small&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نحتاج إلى نموذجين: أحدهما لإكمال الدردشة (الإجابة على الأسئلة) والآخر لإنشاء التضمينات (تحويل النص إلى متجهات).&lt;/p>
&lt;h2 id="تحديد-نموذج-البيانات">تحديد نموذج البيانات&lt;/h2>
&lt;p>نحتاج إلى فئة لتمثيل أجزاء المستند الخاصة بنا في متجر المتجهات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.VectorData&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">DocumentChunk&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordKey]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = Guid.NewGuid().ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordData]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Content { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordData]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Source { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordData]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> ChunkIndex { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [VectorStoreRecordVector(1536)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ReadOnlyMemory&amp;lt;&lt;span style="color:#ff7b72">float&lt;/span>&amp;gt; Embedding { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تخبر السمة &lt;code>VectorStoreRecordVector(1536)&lt;/code> مخزن المتجهات بأبعاد التضمينات الخاصة بنا. ينتج النموذج &lt;code>text-embedding-3-small&lt;/code> متجهات ذات 1536 بُعدًا.&lt;/p>
&lt;h2 id="تقطيع-المستندات">تقطيع المستندات&lt;/h2>
&lt;p>قبل أن نتمكن من إنشاء التضمينات، نحتاج إلى تقسيم مستنداتنا إلى أجزاء يمكن التحكم فيها. إليك مقسم نص بسيط:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TextChunker&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; SplitText(&lt;span style="color:#ff7b72">string&lt;/span> text, &lt;span style="color:#ff7b72">int&lt;/span> maxChunkSize = &lt;span style="color:#a5d6ff">500&lt;/span>, &lt;span style="color:#ff7b72">int&lt;/span> overlap = &lt;span style="color:#a5d6ff">50&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chunks = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> paragraphs = text.Split(&lt;span style="color:#a5d6ff">&amp;#34;\n\n&amp;#34;&lt;/span>, StringSplitOptions.RemoveEmptyEntries);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> currentChunk = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> paragraph &lt;span style="color:#ff7b72">in&lt;/span> paragraphs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (currentChunk.Length + paragraph.Length &amp;gt; maxChunkSize &amp;amp;&amp;amp; currentChunk.Length &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chunks.Add(currentChunk.ToString().Trim());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Keep overlap from the end of the previous chunk&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> overlapText = currentChunk.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (overlapText.Length &amp;gt; overlap)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(overlapText[^overlap..]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(&lt;span style="color:#a5d6ff">&amp;#39; &amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(paragraph);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentChunk.Append(&lt;span style="color:#a5d6ff">&amp;#34;\n\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (currentChunk.Length &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chunks.Add(currentChunk.ToString().Trim());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> chunks;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعد التداخل مهمًا - فهو يضمن عدم فقدان السياق عند الحدود بين القطع. إذا تم تقسيم الجملة ذات الصلة إلى جزأين، فإن التداخل يعني أنها ستظهر بالكامل في واحدة منهما على الأقل.&lt;/p>
&lt;h2 id="استيعاب-المستندات">استيعاب المستندات&lt;/h2>
&lt;p>الآن دعونا نجمع كل ذلك معًا لاستيعاب المستندات في متجر المتجهات الخاص بنا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> vectorStore = &lt;span style="color:#ff7b72">new&lt;/span> InMemoryVectorStore();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> collection = vectorStore.GetCollection&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, DocumentChunk&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;documents&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> collection.CreateCollectionIfNotExistsAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> embeddingService = kernel.GetRequiredService&amp;lt;ITextEmbeddingGenerationService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task IngestDocument(&lt;span style="color:#ff7b72">string&lt;/span> content, &lt;span style="color:#ff7b72">string&lt;/span> source)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chunks = TextChunker.SplitText(content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> (&lt;span style="color:#ff7b72">int&lt;/span> i = &lt;span style="color:#a5d6ff">0&lt;/span>; i &amp;lt; chunks.Count; i++)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> embedding = &lt;span style="color:#ff7b72">await&lt;/span> embeddingService.GenerateEmbeddingAsync(chunks[i]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chunk = &lt;span style="color:#ff7b72">new&lt;/span> DocumentChunk
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Content = chunks[i],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Source = source,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ChunkIndex = i,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Embedding = embedding
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> collection.UpsertAsync(chunk);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ Ingested {chunks.Count} chunks from {source}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Ingest some documents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doc1 = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(&lt;span style="color:#a5d6ff">&amp;#34;docs/product-guide.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doc2 = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(&lt;span style="color:#a5d6ff">&amp;#34;docs/faq.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doc3 = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(&lt;span style="color:#a5d6ff">&amp;#34;docs/troubleshooting.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> IngestDocument(doc1, &lt;span style="color:#a5d6ff">&amp;#34;product-guide.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> IngestDocument(doc2, &lt;span style="color:#a5d6ff">&amp;#34;faq.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> IngestDocument(doc3, &lt;span style="color:#a5d6ff">&amp;#34;troubleshooting.md&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="البحث-عن-القطع-ذات-الصلة">البحث عن القطع ذات الصلة&lt;/h2>
&lt;p>عندما يطرح المستخدم سؤالاً، نقوم بإنشاء تضمين لاستعلامه والبحث عن أجزاء مماثلة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;List&amp;lt;DocumentChunk&amp;gt;&amp;gt; SearchAsync(&lt;span style="color:#ff7b72">string&lt;/span> query, &lt;span style="color:#ff7b72">int&lt;/span> topK = &lt;span style="color:#a5d6ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> queryEmbedding = &lt;span style="color:#ff7b72">await&lt;/span> embeddingService.GenerateEmbeddingAsync(query);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> searchResults = &lt;span style="color:#ff7b72">await&lt;/span> collection.VectorizedSearchAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> queryEmbedding,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> VectorSearchOptions { Top = topK });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;DocumentChunk&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> result &lt;span style="color:#ff7b72">in&lt;/span> searchResults.Results)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> results.Add(result.Record);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> results;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="توليد-الإجابات-مع-السياق">توليد الإجابات مع السياق&lt;/h2>
&lt;p>الآن الجزء RAG - نأخذ الأجزاء المستردة وندرجها كسياق في موجهنا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatService = kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; AskAsync(&lt;span style="color:#ff7b72">string&lt;/span> question)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 1: Retrieve relevant chunks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> relevantChunks = &lt;span style="color:#ff7b72">await&lt;/span> SearchAsync(question);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 2: Build context from chunks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> context = &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n\n---\n\n&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> relevantChunks.Select(c =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;[Source: {c.Source}]\n{c.Content}&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 3: Generate answer with context&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddSystemMessage(&lt;span style="color:#f85149">$&lt;/span>&lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> You are a helpful assistant that answers questions based on the provided context.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Use ONLY the information from the context to answer. If the context doesn&amp;#39;t contain
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> enough information to answer the question, say &amp;#34;&lt;/span>I don&lt;span style="color:#f85149">&amp;#39;&lt;/span>t have enough information
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> to answer that question.&lt;span style="color:#a5d6ff">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Do not make up information. Always cite the source document when possible.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Context:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {{context}}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(question);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> chatService.GetChatMessageContentAsync(history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;No response generated.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="استخدامه">استخدامه&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Ask questions about your documents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> answer1 = &lt;span style="color:#ff7b72">await&lt;/span> AskAsync(&lt;span style="color:#a5d6ff">&amp;#34;How do I reset my password?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Q: How do I reset my password?\nA: {answer1}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> answer2 = &lt;span style="color:#ff7b72">await&lt;/span> AskAsync(&lt;span style="color:#a5d6ff">&amp;#34;What are the system requirements?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Q: What are the system requirements?\nA: {answer2}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> answer3 = &lt;span style="color:#ff7b72">await&lt;/span> AskAsync(&lt;span style="color:#a5d6ff">&amp;#34;What&amp;#39;s the capital of France?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Q: What&amp;#39;s the capital of France?\nA: {answer3}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Should respond with &amp;#34;I don&amp;#39;t have enough information&amp;#34; since it&amp;#39;s not in the docs&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="الانتقال-إلى-الإنتاج">الانتقال إلى الإنتاج&lt;/h2>
&lt;p>يعد مخزن المتجهات الموجود في الذاكرة رائعًا للنماذج الأولية، ولكن بالنسبة للإنتاج، ستحتاج إلى قاعدة بيانات متجهات ثابتة. يحتوي Semantic Kernel على موصلات لعدة خيارات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic"># Azure AI Search&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.AzureAISearch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic"># Qdrant&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic"># Redis&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.Redis
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعد التبديل أمرًا مباشرًا نظرًا لأن جميعهم ينفذون نفس واجهة &lt;code>IVectorStore&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Instead of InMemoryVectorStore, use:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.AzureAISearch&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> vectorStore = &lt;span style="color:#ff7b72">new&lt;/span> AzureAISearchVectorStore(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Azure.Search.Documents.Indexes.SearchIndexClient(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Uri(config[&lt;span style="color:#a5d6ff">&amp;#34;AzureAISearch:Endpoint&amp;#34;&lt;/span>]),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> AzureKeyCredential(config[&lt;span style="color:#a5d6ff">&amp;#34;AzureAISearch:ApiKey&amp;#34;&lt;/span>])));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```كل&lt;/span> &lt;span style="color:#f85149">شيء&lt;/span> &lt;span style="color:#f85149">آخر&lt;/span> &lt;span style="color:#f85149">يبقى&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">حاله&lt;/span>. &lt;span style="color:#f85149">هذا&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">جمال&lt;/span> &lt;span style="color:#f85149">التجريد&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> &lt;span style="color:#f85149">نصائح&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">بناء&lt;/span> &lt;span style="color:#f85149">أنظمة&lt;/span> RAG
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">بعض&lt;/span> &lt;span style="color:#f85149">الأشياء&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تعلمتها&lt;/span> &lt;span style="color:#f85149">بالطريقة&lt;/span> &lt;span style="color:#f85149">الصعبة&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">حجم&lt;/span> &lt;span style="color:#f85149">القطعة&lt;/span> &lt;span style="color:#f85149">مهم&lt;/span> &lt;span style="color:#f85149">جدًا&lt;/span>.** &lt;span style="color:#f85149">صغير&lt;/span> &lt;span style="color:#f85149">جدًا&lt;/span> &lt;span style="color:#f85149">ويؤدي&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">فقدان&lt;/span> &lt;span style="color:#f85149">السياق&lt;/span>. &lt;span style="color:#f85149">كبيرة&lt;/span> &lt;span style="color:#f85149">جدًا&lt;/span> &lt;span style="color:#f85149">وتضيع&lt;/span> &lt;span style="color:#f85149">الرموز&lt;/span> &lt;span style="color:#f85149">المميزة&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">محتوى&lt;/span> &lt;span style="color:#f85149">غير&lt;/span> &lt;span style="color:#f85149">ذي&lt;/span> &lt;span style="color:#f85149">صلة&lt;/span>. &lt;span style="color:#f85149">ابدأ&lt;/span> &lt;span style="color:#f85149">بـ&lt;/span> &lt;span style="color:#a5d6ff">500&lt;/span>-&lt;span style="color:#a5d6ff">800&lt;/span> &lt;span style="color:#f85149">رمزًا&lt;/span> &lt;span style="color:#f85149">وقم&lt;/span> &lt;span style="color:#f85149">بالتعديل&lt;/span> &lt;span style="color:#f85149">بناءً&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">بياناتك&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">التداخل&lt;/span> &lt;span style="color:#f85149">يمنع&lt;/span> &lt;span style="color:#f85149">مشاكل&lt;/span> &lt;span style="color:#f85149">الحدود&lt;/span>.** &lt;span style="color:#f85149">عادةً&lt;/span> &lt;span style="color:#f85149">ما&lt;/span> &lt;span style="color:#f85149">يكون&lt;/span> &lt;span style="color:#f85149">التداخل&lt;/span> &lt;span style="color:#f85149">بين&lt;/span> &lt;span style="color:#f85149">القطع&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#a5d6ff">50&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#a5d6ff">100&lt;/span> &lt;span style="color:#f85149">رمزًا&lt;/span> &lt;span style="color:#f85149">كافيًا&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">استرجع&lt;/span> &lt;span style="color:#f85149">أكثر&lt;/span> &lt;span style="color:#f85149">مما&lt;/span> &lt;span style="color:#f85149">تعتقد&lt;/span>.** &lt;span style="color:#f85149">ابدأ&lt;/span> &lt;span style="color:#f85149">بـ&lt;/span> &lt;span style="color:#f85149">`&lt;/span>topK = &lt;span style="color:#a5d6ff">5&lt;/span>&lt;span style="color:#f85149">`&lt;/span> &lt;span style="color:#f85149">وقلل&lt;/span> &lt;span style="color:#f85149">إذا&lt;/span> &lt;span style="color:#f85149">كنت&lt;/span> &lt;span style="color:#f85149">تحصل&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">الكثير&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">الضوضاء&lt;/span>. &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">الأفضل&lt;/span> &lt;span style="color:#f85149">أن&lt;/span> &lt;span style="color:#f85149">يكون&lt;/span> &lt;span style="color:#f85149">لديك&lt;/span> &lt;span style="color:#f85149">سياق&lt;/span> &lt;span style="color:#f85149">إضافي&lt;/span> &lt;span style="color:#f85149">بدلاً&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">تفويت&lt;/span> &lt;span style="color:#f85149">الجزء&lt;/span> &lt;span style="color:#f85149">ذي&lt;/span> &lt;span style="color:#f85149">الصلة&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">تعد&lt;/span> &lt;span style="color:#f85149">مطالبات&lt;/span> &lt;span style="color:#f85149">النظام&lt;/span> &lt;span style="color:#f85149">أمرًا&lt;/span> &lt;span style="color:#f85149">بالغ&lt;/span> &lt;span style="color:#f85149">الأهمية&lt;/span>.** &lt;span style="color:#f85149">كن&lt;/span> &lt;span style="color:#f85149">واضحًا&lt;/span> &lt;span style="color:#f85149">جدًا&lt;/span> &lt;span style="color:#f85149">بشأن&lt;/span> &lt;span style="color:#f85149">استخدام&lt;/span> &lt;span style="color:#f85149">السياق&lt;/span> &lt;span style="color:#f85149">المتوفر&lt;/span> &lt;span style="color:#f85149">فقط&lt;/span>. &lt;span style="color:#f85149">وبدون&lt;/span> &lt;span style="color:#f85149">هذه&lt;/span> &lt;span style="color:#f85149">التعليمات،&lt;/span> &lt;span style="color:#f85149">سوف&lt;/span> &lt;span style="color:#f85149">يهلوس&lt;/span> &lt;span style="color:#f85149">النموذج&lt;/span> &lt;span style="color:#f85149">بسعادة&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;بناءً على بيانات التدريب الخاصة به&amp;#34;&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">تتبع&lt;/span> &lt;span style="color:#f85149">المصادر&lt;/span>.** &lt;span style="color:#f85149">قم&lt;/span> &lt;span style="color:#f85149">دائمًا&lt;/span> &lt;span style="color:#f85149">بتخزين&lt;/span> &lt;span style="color:#f85149">البيانات&lt;/span> &lt;span style="color:#f85149">الوصفية&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">الأجزاء&lt;/span> &lt;span style="color:#f85149">الخاصة&lt;/span> &lt;span style="color:#f85149">بك&lt;/span> &lt;span style="color:#f85149">حتى&lt;/span> &lt;span style="color:#f85149">تتمكن&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">ذكر&lt;/span> &lt;span style="color:#f85149">مصدر&lt;/span> &lt;span style="color:#f85149">الإجابة&lt;/span>. &lt;span style="color:#f85149">يثق&lt;/span> &lt;span style="color:#f85149">المستخدمون&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الإجابات&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">أكبر&lt;/span> &lt;span style="color:#f85149">عندما&lt;/span> &lt;span style="color:#f85149">يمكنهم&lt;/span> &lt;span style="color:#f85149">التحقق&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">المصدر&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">أعد&lt;/span> &lt;span style="color:#f85149">الترتيب&lt;/span> &lt;span style="color:#f85149">إذا&lt;/span> &lt;span style="color:#f85149">لزم&lt;/span> &lt;span style="color:#f85149">الأمر&lt;/span>.** &lt;span style="color:#f85149">تشابه&lt;/span> &lt;span style="color:#f85149">المتجهات&lt;/span> &lt;span style="color:#f85149">ليس&lt;/span> &lt;span style="color:#f85149">مثاليًا&lt;/span>. &lt;span style="color:#f85149">بالنسبة&lt;/span> &lt;span style="color:#f85149">للتطبيقات&lt;/span> &lt;span style="color:#f85149">الهامة،&lt;/span> &lt;span style="color:#f85149">أضف&lt;/span> &lt;span style="color:#f85149">خطوة&lt;/span> &lt;span style="color:#f85149">إعادة&lt;/span> &lt;span style="color:#f85149">التصنيف&lt;/span> &lt;span style="color:#f85149">باستخدام&lt;/span> &lt;span style="color:#f85149">نموذج&lt;/span> &lt;span style="color:#f85149">التشفير&lt;/span> &lt;span style="color:#f85149">المتبادل&lt;/span> &lt;span style="color:#f85149">لتحسين&lt;/span> &lt;span style="color:#f85149">الدقة&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> &lt;span style="color:#f85149">الخلاصة&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">يعد&lt;/span> RAG &lt;span style="color:#f85149">أحد&lt;/span> &lt;span style="color:#f85149">أكثر&lt;/span> &lt;span style="color:#f85149">الأنماط&lt;/span> &lt;span style="color:#f85149">العملية&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الذكاء&lt;/span> &lt;span style="color:#f85149">الاصطناعي&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الوقت&lt;/span> &lt;span style="color:#f85149">الحالي&lt;/span>. &lt;span style="color:#f85149">فهو&lt;/span> &lt;span style="color:#f85149">يتيح&lt;/span> &lt;span style="color:#f85149">لك&lt;/span> &lt;span style="color:#f85149">إنشاء&lt;/span> &lt;span style="color:#f85149">أنظمة&lt;/span> &lt;span style="color:#f85149">أسئلة&lt;/span> &lt;span style="color:#f85149">وأجوبة&lt;/span> &lt;span style="color:#f85149">مدعومة&lt;/span> &lt;span style="color:#f85149">بالذكاء&lt;/span> &lt;span style="color:#f85149">الاصطناعي&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">بياناتك&lt;/span> &lt;span style="color:#f85149">الخاصة&lt;/span> &lt;span style="color:#f85149">دون&lt;/span> &lt;span style="color:#f85149">ضبط&lt;/span> &lt;span style="color:#f85149">دقيق،&lt;/span> &lt;span style="color:#f85149">كما&lt;/span> &lt;span style="color:#f85149">أن&lt;/span> Semantic Kernel &lt;span style="color:#f85149">يجعلها&lt;/span> &lt;span style="color:#f85149">نظيفة&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">مدهش&lt;/span> &lt;span style="color:#f85149">في&lt;/span> C&lt;span style="color:#f85149">#&lt;/span>. &lt;span style="color:#f85149">ابدأ&lt;/span> &lt;span style="color:#f85149">بالمخزن&lt;/span> &lt;span style="color:#f85149">الموجود&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الذاكرة،&lt;/span> &lt;span style="color:#f85149">وقم&lt;/span> &lt;span style="color:#f85149">بإجراء&lt;/span> &lt;span style="color:#f85149">عملية&lt;/span> &lt;span style="color:#f85149">التقطيع&lt;/span> &lt;span style="color:#f85149">والمطالبات&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">صحيح،&lt;/span> &lt;span style="color:#f85149">ثم&lt;/span> &lt;span style="color:#f85149">قم&lt;/span> &lt;span style="color:#f85149">بالتبديل&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">قاعدة&lt;/span> &lt;span style="color:#f85149">بيانات&lt;/span> &lt;span style="color:#f85149">متجهة&lt;/span> &lt;span style="color:#f85149">حقيقية&lt;/span> &lt;span style="color:#f85149">عندما&lt;/span> &lt;span style="color:#f85149">تكون&lt;/span> &lt;span style="color:#f85149">جاهزًا&lt;/span> &lt;span style="color:#f85149">للإنتاج&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">ترميز&lt;/span> &lt;span style="color:#f85149">سعيد&lt;/span>!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> &lt;span style="color:#f85149">الموارد&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- [&lt;span style="color:#f85149">وثائق&lt;/span> &lt;span style="color:#f85149">مخزن&lt;/span> &lt;span style="color:#f85149">ناقلات&lt;/span> &lt;span style="color:#f85149">النواة&lt;/span> &lt;span style="color:#f85149">الدلالية&lt;/span>](https:&lt;span style="color:#8b949e;font-style:italic">//learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- [&lt;span style="color:#f85149">نمط&lt;/span> RAG &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">بحث&lt;/span> Azure AI](https:&lt;span style="color:#8b949e;font-style:italic">//learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- [&lt;span style="color:#f85149">نماذج&lt;/span> &lt;span style="color:#f85149">تضمين&lt;/span> &lt;span style="color:#f85149">النص&lt;/span>](https:&lt;span style="color:#8b949e;font-style:italic">//learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#embeddings)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content:encoded><category>.NET</category><category>AI</category><category>Semantic Kernel</category><category>C#</category><category>Azure</category></item><item><title>بناء سير عمل الوكيل باستخدام Microsoft Agent Framework</title><link>https://emimontesdeoca.github.io/ar/posts/agent-framework-workflows/</link><pubDate>Tue, 10 Feb 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/agent-framework-workflows/</guid><description>تصميم وتنسيق سير عمل الوكيل المتسلسل والمتوازي باستخدام Microsoft Agent Framework في .NET.</description><content:encoded>&lt;h2 id="مقدمة">مقدمة&lt;/h2>
&lt;p>إذا كنت قد قرأت منشوراتي السابقة حول Microsoft Agent Framework، فأنت تعرف كيفية إنشاء وكلاء فرديين وحتى محادثات جماعية متعددة الوكلاء. ولكن في سيناريوهات العالم الحقيقي، غالبًا ما تحتاج إلى شيء أكثر تنظيماً - سير عمل حيث ينفذ الوكلاء بترتيب معين، ويمررون النتائج لبعضهم البعض، ويتعاملون مع منطق التفرع بناءً على النتائج.&lt;/p>
&lt;p>هذا هو بالضبط ما توفره لك إمكانيات سير العمل في Agent Framework. بدلاً من إدخال الوكلاء في محادثة جماعية والأمل في اكتشاف ذلك، يمكنك تحديد خطوات وتبعيات وتدفق بيانات صريحة بين الوكلاء.&lt;/p>
&lt;h2 id="ما-هي-مسارات-عمل-الوكيل">ما هي مسارات عمل الوكيل؟&lt;/h2>
&lt;p>فكر في سير عمل الوكيل مثل خط الأنابيب. يتم التعامل مع كل خطوة بواسطة وكيل متخصص، ويتم تغذية ناتج خطوة واحدة إلى الخطوة التالية. يمكنك تنفيذ الخطوات بشكل تسلسلي، أو بالتوازي، أو بشكل مشروط بناءً على النتائج.&lt;/p>
&lt;p>بعض الأمثلة:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>خط أنابيب المحتوى&lt;/strong>: بحث ← مسودة ← مراجعة ← نشر&lt;/li>
&lt;li>&lt;strong>معالجة البيانات&lt;/strong>: استخراج ← تحويل ← التحقق من الصحة ← تحميل&lt;/li>
&lt;li>&lt;strong>دعم العملاء&lt;/strong>: التصنيف ← المسار ← الاستجابة ← المتابعة&lt;/li>
&lt;/ul>
&lt;h2 id="المتطلبات-الأساسية">المتطلبات الأساسية&lt;/h2>
&lt;p>تأكد من حصولك على أحدث حزم Agent Framework:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Agents.Core
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وتم تكوين نقطة نهاية Azure OpenAI أو OpenAI.&lt;/p>
&lt;h2 id="بناء-سير-عمل-متسلسل">بناء سير عمل متسلسل&lt;/h2>
&lt;p>لنقم ببناء سير عمل لإنشاء المحتوى باستخدام ثلاثة وكلاء: باحث، وكاتب، ومحرر.&lt;/p>
&lt;h3 id="تعريف-الوكلاء">تعريف الوكلاء&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = Kernel.CreateBuilder()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: config[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> researcher = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a research specialist. Given a topic, find key facts, statistics,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and talking points. Return a structured research brief with bullet points.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Be thorough but concise.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> writer = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical blog writer. Given a research brief, write a clear
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and engaging blog post. Use a conversational tone, include code examples
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">where&lt;/span> relevant, and structure the post with clear headings.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> editor = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Editor&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a senior editor. Review the blog post &lt;span style="color:#ff7b72">for&lt;/span> clarity, accuracy,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> grammar, and flow. Return the corrected version with a summary of
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> changes made at the end.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="التنفيذ-بالتسلسل">التنفيذ بالتسلسل&lt;/h3>
&lt;p>أبسط سير عمل هو سير عمل تسلسلي، حيث يقوم كل وكيل بمعالجة مخرجات الوكيل السابق:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents.Chat&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; RunSequentialWorkflow(&lt;span style="color:#ff7b72">string&lt;/span> topic)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> currentInput = &lt;span style="color:#a5d6ff">$&amp;#34;Research the following topic: {topic}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agents = &lt;span style="color:#ff7b72">new&lt;/span>[] { researcher, writer, editor };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> agent &lt;span style="color:#ff7b72">in&lt;/span> agents)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(currentInput);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.Append(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentInput = response.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ {agent.Name} completed&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> currentInput;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> RunSequentialWorkflow(&lt;span style="color:#a5d6ff">&amp;#34;Blazor render modes in .NET 9&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يحصل كل وكيل على السياق المتراكم، ويعالجه، وتنتقل النتيجة إلى الخطوة التالية.&lt;/p>
&lt;h2 id="التنفيذ-الموازي">التنفيذ الموازي&lt;/h2>
&lt;p>في بعض الأحيان يمكن للوكلاء العمل بشكل مستقل. على سبيل المثال، قد ترغب في البحث عن موضوعات فرعية متعددة في نفس الوقت:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;&amp;gt; RunParallelResearch(&lt;span style="color:#ff7b72">string&lt;/span>[] topics)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> tasks = topics.Select(&lt;span style="color:#ff7b72">async&lt;/span> topic =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Research: {topic}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> researcher.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.Append(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ Research completed: {topic}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">await&lt;/span> Task.WhenAll(tasks);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> results.ToList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> topics = &lt;span style="color:#ff7b72">new&lt;/span>[]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Blazor SSR streaming&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Enhanced navigation in .NET 9&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Render mode boundaries&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> briefs = &lt;span style="color:#ff7b72">await&lt;/span> RunParallelResearch(topics);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد ذلك يمكنك تغذية جميع ملخصات البحث إلى وكيل كاتب واحد لإنتاج مشاركة واحدة متماسكة.&lt;/p>
&lt;h2 id="التفرع-المشروط">التفرع المشروط&lt;/h2>
&lt;p>سير العمل الحقيقي يحتاج إلى قرارات. ربما تريد خطوة فحص الجودة التي تعيدك إلى الكاتب إذا لم يكن المنشور جيدًا بما فيه الكفاية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> qualityChecker = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;QualityChecker&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a quality assurance reviewer. Evaluate the blog post &lt;span style="color:#ff7b72">on&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">1.&lt;/span> Technical accuracy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">2.&lt;/span> Clarity and readability
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">3.&lt;/span> Completeness
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Respond with either:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#a5d6ff">&amp;#34;APPROVED&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> the post meets all criteria
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#a5d6ff">&amp;#34;REVISION NEEDED: [specific feedback]&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> changes are required
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Be strict. Only approve posts that are truly ready to publish.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; RunWithQualityLoop(&lt;span style="color:#ff7b72">string&lt;/span> topic, &lt;span style="color:#ff7b72">int&lt;/span> maxRevisions = &lt;span style="color:#a5d6ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 1: Research&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Research: {topic}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> research = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(researcher, history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 2: Write&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(research);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> draft = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(writer, history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Step 3: Quality loop&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> (&lt;span style="color:#ff7b72">int&lt;/span> i = &lt;span style="color:#a5d6ff">0&lt;/span>; i &amp;lt; maxRevisions; i++)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> qaHistory = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> qaHistory.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Review this post:\n\n{draft}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> qaResult = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(qualityChecker, qaHistory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (qaResult.Contains(&lt;span style="color:#a5d6ff">&amp;#34;APPROVED&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;✅ Approved after {i + 1} review(s)&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> draft;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;🔄 Revision {i + 1}: {qaResult[..Math.Min(100, qaResult.Length)]}...&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Send back to writer with feedback&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(&lt;span style="color:#a5d6ff">$&amp;#34;Please revise based on this feedback:\n{qaResult}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> draft = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(writer, history);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;⚠️ Max revisions reached, returning latest draft&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> draft;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; InvokeAgent(ChatCompletionAgent agent, ChatHistory history)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">new&lt;/span> System.Text.StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.Append(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا النمط مفيد للغاية. يعمل مدقق الجودة كبوابة، ويتكرر سير العمل حتى يصبح الإخراج جيدًا بدرجة كافية أو يصل إلى الحد الأقصى للمراجعة.&lt;/p>
&lt;h2 id="إضافة-المكونات-الإضافية-إلى-وكلاء-سير-العمل">إضافة المكونات الإضافية إلى وكلاء سير العمل&lt;/h2>
&lt;p>يمكن للوكلاء في سير العمل استخدام المكونات الإضافية تمامًا مثل الوكلاء المستقلين. هذا هو المكان الذي تصبح فيه الأمور قوية حقًا - يمكن للوكلاء الاتصال بواجهات برمجة التطبيقات أو الاستعلام عن قواعد البيانات أو إجراء عمليات الملفات كجزء من خطوة سير العمل الخاصة بهم:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> researcherWithTools = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;Research the topic using available tools. Summarize findings.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add a web search plugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromType&amp;lt;WebSearchPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add a database plugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromObject(&lt;span style="color:#ff7b72">new&lt;/span> DatabasePlugin(connectionString), &lt;span style="color:#a5d6ff">&amp;#34;Database&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WebSearchPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction, Description(&amp;#34;Search the web for information&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; SearchAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The search query&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> query)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Your search implementation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetStringAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34;https://api.search.example.com?q={Uri.EscapeDataString(query)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="معالجة-الأخطاء-في-سير-العمل">معالجة الأخطاء في سير العمل&lt;/h2>
&lt;p>عندما تقوم بربط عدة وكلاء، تصبح معالجة الأخطاء أمرًا بالغ الأهمية. لا تريد أن تؤدي خطوة واحدة فاشلة إلى تعطل سير العمل بأكمله بصمت:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;WorkflowResult&amp;gt; RunResilientWorkflow(&lt;span style="color:#ff7b72">string&lt;/span> input)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">new&lt;/span> WorkflowResult();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Research = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(researcher, &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory(input));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Errors.Add(&lt;span style="color:#a5d6ff">$&amp;#34;Research failed: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> writeHistory = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory(result.Research);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Draft = &lt;span style="color:#ff7b72">await&lt;/span> InvokeAgent(writer, writeHistory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Errors.Add(&lt;span style="color:#a5d6ff">$&amp;#34;Writing failed: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Draft = result.Research; &lt;span style="color:#8b949e;font-style:italic">// Fallback to research output&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result.Success = result.Errors.Count == &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WorkflowResult&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Research { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Draft { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Errors { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> Success { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="أفضل-الممارسات">أفضل الممارسات&lt;/h2>
&lt;p>بعد إنشاء العديد من مسارات عمل الوكيل، إليك ما تعلمته:- &lt;strong>حافظ على تركيز تعليمات الوكيل&lt;/strong> — يجب على كل وكيل أن يفعل شيئًا واحدًا بشكل جيد. لا تحاول أن تصنع عميلاً بسكين الجيش السويسري.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>السياق المحدد&lt;/strong> — لا تمرر سجل المحادثة بالكامل إلى كل وكيل. أعط كل خطوة ما تحتاجه فقط.&lt;/li>
&lt;li>&lt;strong>تعيين حدود للمراجعة&lt;/strong> — تعتبر حلقات الجودة رائعة ولكن يمكن تشغيلها إلى الأبد إذا لم تكن حذرًا. دائمًا ما يكون لديك حد أقصى للتكرارات.&lt;/li>
&lt;li>&lt;strong>تسجيل كل شيء&lt;/strong> — يمكن أن تكون مخرجات الوكيل غير متوقعة. قم بتسجيل مدخلات ومخرجات كل خطوة لتصحيح الأخطاء.&lt;/li>
&lt;li>&lt;strong>استخدم التنفيذ الموازي بحكمة&lt;/strong> — فهو يعمل على تسريع الأمور ولكن انتبه إلى حدود معدل واجهة برمجة التطبيقات (API) وتكاليف الرمز المميز.&lt;/li>
&lt;li>&lt;strong>اختبر النماذج الأصغر أولاً&lt;/strong> - قم بتطوير واختبار منطق سير العمل الخاص بك باستخدام GPT-3.5 قبل التبديل إلى GPT-4o للإنتاج.&lt;/li>
&lt;/ul>
&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>تتيح لك مسارات عمل الوكيل إنشاء خطوط أنابيب معقدة ومتعددة الخطوات للذكاء الاصطناعي حيث يكون كل وكيل متخصصًا. ابدأ بسير العمل المتسلسل، وأضف التنفيذ المتوازي حيث تكون الخطوات مستقلة، واستخدم التفرع المشروط لبوابات الجودة. الأنماط قابلة للتركيب - بمجرد أن تتقنها، يمكنك إنشاء بعض الأتمتة المتطورة جدًا.&lt;/p>
&lt;p>ترميز سعيد!&lt;/p>
&lt;h2 id="الموارد">الموارد&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent">وثائق إطار عمل وكيل Microsoft&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/GettingStartedWithAgents">عينات وكلاء النواة الدلالية&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>AI</category><category>Agent Framework</category><category>Semantic Kernel</category></item><item><title>دورة حياة مكون Blazor: الدليل الكامل</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-component-lifecycle/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-component-lifecycle/</guid><description>افهم كل أسلوب من أساليب دورة حياة مكونات Blazor بدءًا من التهيئة وحتى التخلص منها ومتى يتم استخدام كل منها.</description><content:encoded>&lt;p>أنا أستخدم Blazor منذ فترة وبصراحة، لقد أربكتني أساليب دورة الحياة في البداية. &lt;code>OnInitialized&lt;/code> ضد &lt;code>OnInitializedAsync&lt;/code>؟ &lt;code>OnParametersSet&lt;/code> ضد &lt;code>OnAfterRender&lt;/code>؟ متى يقوم &lt;code>StateHasChanged&lt;/code> فعليًا بتشغيل إعادة العرض؟ وبعد الكثير من التجربة والخطأ، أصبح لدي أخيرًا نموذج عقلي قوي لكل ذلك.&lt;/p>
&lt;h1 id="دورة-الحياة-في-لمحة">دورة الحياة في لمحة&lt;/h1>
&lt;p>عندما يتم عرض مكون Blazor، فإنه يمر عبر هذه الطرق بالترتيب:&lt;/p>
&lt;ol>
&lt;li>&lt;code>SetParametersAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>OnInitialized&lt;/code> / &lt;code>OnInitializedAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>OnParametersSet&lt;/code> / &lt;code>OnParametersSetAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>OnAfterRender&lt;/code> / &lt;code>OnAfterRenderAsync&lt;/code>&lt;/li>
&lt;li>&lt;code>Dispose&lt;/code> / &lt;code>DisposeAsync&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>دعونا نسير من خلال كل واحد.&lt;/p>
&lt;p>#SetParametersAsync&lt;/p>
&lt;p>هذه هي الطريقة الأولى التي تسمى. يتلقى &lt;code>ParameterView&lt;/code> الخام من المكون الأصلي. في معظم الأوقات، لا تتجاوز هذا — حيث يتعامل Blazor مع معلمات التعيين تلقائيًا. ولكن إذا كنت بحاجة إلى معالجة المعلمات المخصصة أو التحقق من صحتها قبل تعيينها:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task SetParametersAsync(ParameterView parameters)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Custom logic before parameters are set&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (parameters.TryGetValue&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Title is being set to: {title}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">base&lt;/span>.SetParametersAsync(parameters);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لقد استخدمت هذا مرة واحدة بالضبط في مشروع حقيقي. في معظم الأحيان سوف تخطي ذلك.&lt;/p>
&lt;h1 id="oninitialized--oninitializedasync">OnInitialized / OnInitializedAsync&lt;/h1>
&lt;p>هذا هو المكان الذي تقوم فيه بعمل الإعداد - تحميل البيانات، وتهيئة الخدمات، وتعيين القيم الافتراضية. يتم تشغيله &lt;strong>مرة واحدة&lt;/strong> عند إنشاء المكون لأول مرة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;Product&amp;gt; products = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الشيء الوحيد الذي أزعجني: في Blazor Server، يتم استدعاء &lt;code>OnInitializedAsync&lt;/code> &lt;strong>مرتين&lt;/strong> أثناء العرض المسبق. المرة الأولى أثناء العرض المسبق من جانب الخادم، والمرة الثانية عند إنشاء اتصال SignalR. إذا كان استدعاء API الخاص بك مكلفًا، فقد ترغب في التعامل مع ذلك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> isPrerendering = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> isPrerendering = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>أو من الأفضل استخدام &lt;code>PersistentComponentState&lt;/code> لتجنب المكالمة المزدوجة تمامًا.&lt;/p>
&lt;h1 id="onparametersset--onparameterssetasync">OnParametersSet / OnParametersSetAsync&lt;/h1>
&lt;p>يتم تشغيل هذا في كل مرة يقوم فيها المكون الأصلي بإعادة العرض وتمرير قيم المعلمات الجديدة. يتم إطلاقه أيضًا بعد &lt;code>OnInitialized&lt;/code>. هذا هو المكان المناسب للرد على تغييرات المعلمات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> CategoryId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> previousCategoryId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;Product&amp;gt; products = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnParametersSetAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (CategoryId != previousCategoryId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> previousCategoryId = CategoryId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34;api/products?category={CategoryId}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعد التحقق من &lt;code>CategoryId != previousCategoryId&lt;/code> أمرًا مهمًا — فبدونه، يمكنك إعادة تحميل البيانات في كل مرة يقوم فيها الأصل بإعادة العرض، حتى لو لم تتغير الفئة.&lt;/p>
&lt;h1 id="onafterrender--onafterrenderasync">OnAfterRender / OnAfterRenderAsync&lt;/h1>
&lt;p>يتم تشغيل هذا بعد تقديم المكون إلى DOM. تخبرك المعلمة &lt;code>firstRender&lt;/code> ما إذا كان هذا هو العرض الأولي. هذا هو المكان المناسب لاستدعاءات JS Interop نظرًا لوجود عناصر DOM في هذه المرحلة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> IJSRuntime JS { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnAfterRenderAsync(&lt;span style="color:#ff7b72">bool&lt;/span> firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JS.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;initializeChart&amp;#34;&lt;/span>, chartElement);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>أستخدم &lt;code>firstRender&lt;/code> كثيرًا لتجنب إعادة تهيئة مكتبات JavaScript في كل عملية إعادة عرض. إذا كنت تقوم بإعداد مستمعي الأحداث، أو مكتبات المخططات، أو أي شيء يلامس DOM مباشرة، فهذا هو المكان الذي تذهب إليه.&lt;/p>
&lt;h1 id="ينبغي-تقديم">ينبغي تقديم&lt;/h1>
&lt;p>هذا أقل شهرة ولكنه مفيد للغاية. يتحكم في ما إذا كان المكون يُعاد عرضه عند استدعاء &lt;code>StateHasChanged&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> shouldRender = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> ShouldRender() =&amp;gt; shouldRender;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> HeavyOperation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shouldRender = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Do a bunch of state changes without triggering renders&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> (&lt;span style="color:#ff7b72">int&lt;/span> i = &lt;span style="color:#a5d6ff">0&lt;/span>; i &amp;lt; &lt;span style="color:#a5d6ff">1000&lt;/span>; i++)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> items[i].Process();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shouldRender = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged(); &lt;span style="color:#8b949e;font-style:italic">// Now render once with all changes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لقد استخدمت هذا لتحسين المكونات التي تعالج القوائم الكبيرة. بدلاً من إعادة العرض عند كل تغيير في العنصر، يمكنك تجميع التحديثات وعرضها مرة واحدة في النهاية.&lt;/p>
&lt;h1 id="التخلص--التخلص-من-async">التخلص / التخلص من Async&lt;/h1>
&lt;p>عند إزالة مكون من واجهة المستخدم، يجب عليك تنظيف أي موارد. تنفيذ &lt;code>IDisposable&lt;/code> أو &lt;code>IAsyncDisposable&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@implements IAsyncDisposable
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> Timer? timer;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> IJSObjectReference? jsModule;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> timer = &lt;span style="color:#ff7b72">new&lt;/span> Timer(OnTick, &lt;span style="color:#79c0ff">null&lt;/span>, &lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">1000&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnAfterRenderAsync(&lt;span style="color:#ff7b72">bool&lt;/span> firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> jsModule = &lt;span style="color:#ff7b72">await&lt;/span> JS.InvokeAsync&amp;lt;IJSObjectReference&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;import&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;./timer.js&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask DisposeAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> timer?.Dispose();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (jsModule &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> jsModule.DisposeAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الأشياء الشائعة التي يجب التخلص منها: المؤقتات، واشتراكات الأحداث، ومراجع وحدة JS، و&lt;code>CancellationTokenSource&lt;/code>، وأي خدمات &lt;code>IDisposable&lt;/code> أنشأتها.&lt;/p>
&lt;p>#تغيرت الحالةهذه ليست طريقة دورة حياة، ولكنها مرتبطة ارتباطًا وثيقًا. يقول لـ Blazor &amp;ldquo;مرحبًا، لقد تغيرت حالتي، من فضلك أعد عرضي.&amp;rdquo; يستدعيه Blazor تلقائيًا بعد معالجات الأحداث، لكن في بعض الأحيان تحتاج إلى استدعائه يدويًا — عادةً عندما تتغير الحالة من خارج تدفق أحداث Blazor العادي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task StartPolling()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">while&lt;/span> (!cts.Token.IsCancellationRequested)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;Data&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/data&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged(); &lt;span style="color:#8b949e;font-style:italic">// Manual call needed since this isn&amp;#39;t a Blazor event&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(&lt;span style="color:#a5d6ff">5000&lt;/span>, cts.Token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ملاحظة مهمة: إذا كنت تقوم بالتحديث من سلسلة محادثات في الخلفية في Blazor Server، فاستخدم &lt;code>InvokeAsync&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> InvokeAsync(() =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data = newData;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>#الصورة كاملة&lt;/p>
&lt;p>إليك الترتيب الذي يحدث فيه كل شيء:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Component created
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ SetParametersAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnInitialized / OnInitializedAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnParametersSet / OnParametersSetAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ Render
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnAfterRender(firstRender: true)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Parameter change from parent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ SetParametersAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnParametersSet / OnParametersSetAsync
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ ShouldRender?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ Render
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ OnAfterRender(firstRender: false)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Component removed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ Dispose / DisposeAsync
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بمجرد أن يكون لديك هذا التدفق في رأسك، يصبح تصحيح مشكلات دورة الحياة أسهل كثيرًا.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! لا تتردد في الاتصال بي على أي وسائل التواصل الاجتماعي على **@emimontesdeoca **.&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle">دورة حياة مكون ASP.NET Core Blazor&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable">التخلص من المكونات باستخدام IDisposable وIAsyncDisposable&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>C#</category></item><item><title>Microsoft Agent Framework لإنقاذ عيد الميلاد</title><link>https://emimontesdeoca.github.io/ar/posts/agent-framework-christmas-presents/</link><pubDate>Tue, 16 Dec 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/agent-framework-christmas-presents/</guid><description>أنشئ نظام تسوق هدايا عيد الميلاد متعدد الوكلاء باستخدام Microsoft Agent Framework مع .NET.</description><content:encoded>&lt;h2 id="مقدمة">مقدمة&lt;/h2>
&lt;p>قد يكون العثور على هدايا عيد الميلاد المثالية أمرًا مرهقًا. بين تبادل الأفكار حول أفكار الهدايا، ومقارنة الأسعار عبر المتاجر، والتأكد من وصول كل شيء في الوقت المحدد، سرعان ما يصبح التسوق أثناء العطلات أمرًا مرهقًا. ماذا لو تمكنا من تفويض هذه المهام إلى عملاء الذكاء الاصطناعي المتخصصين الذين يعملون معًا؟ في هذا المنشور، سنستكشف كيفية استخدام Microsoft Agent Framework لبناء نظام متعدد الوكلاء حيث يتخصص كل وكيل في مهمة محددة، بدءًا من إنشاء أفكار الهدايا وحتى مقارنة الأسعار، ويتم تنسيق كل ذلك من خلال سير العمل.&lt;/p>
&lt;h2 id="التقويم-التقني-الاحتفالي-لعام-2025">التقويم التقني الاحتفالي لعام 2025&lt;/h2>
&lt;p align="center">
&lt;img src="https://sessionize.com/image/49aa-1140o400o3-sdJUGhdR3FCmm1KuPRM3D3.png"/>
&lt;/ص>
&lt;p>يعد هذا المشروع جزءًا من جلستي في &lt;strong>Festive Tech Calendar 2025&lt;/strong>، وهو حدث مجتمعي رائع يحتفل بالتكنولوجيا خلال موسم العطلات. يمكنك العثور على المزيد حول الحدث على &lt;a href="https://sessionize.com/festive-tech-calendar-2025/">Sessionize&lt;/a>.&lt;/p>
&lt;h2 id="ما-هو-إطار-عمل-وكيل-microsoft">ما هو إطار عمل وكيل Microsoft؟&lt;/h2>
&lt;p>Agent Framework هو حل Microsoft لبناء وتنسيق ونشر وكلاء الذكاء الاصطناعي والأنظمة متعددة الوكلاء. فهو يوفر أساسًا مرنًا لإنشاء الوكلاء الذين يمكنهم العمل بشكل تسلسلي أو متزامن أو من خلال أنماط التسليم. يدعم إطار العمل نماذج OpenAI وAzure OpenAI وMicrosoft Foundry، مما يجعله متعدد الاستخدامات بشكل لا يصدق.&lt;/p>
&lt;p>تشمل الميزات الرئيسية ما يلي:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>تنسيق متعدد الوكلاء&lt;/strong>: الدردشة الجماعية، وأنماط التسلسل، والمتزامنة، والتسليم&lt;/li>
&lt;li>&lt;strong>نظام المكونات الإضافية&lt;/strong>: يمتد ليشمل الوظائف الأصلية وOpenAPI وبروتوكول السياق النموذجي (MCP)&lt;/li>
&lt;li>&lt;strong>دعم سير العمل&lt;/strong>: أنشئ مسارات وكيل معقدة باستخدام منفذين وحواف&lt;/li>
&lt;/ul>
&lt;h2 id="المتطلبات-الأساسية">المتطلبات الأساسية&lt;/h2>
&lt;p>قبل الغوص في الكود، تأكد من أن لديك:&lt;/p>
&lt;p>-.نت 9&lt;/p>
&lt;ul>
&lt;li>الوصول إلى Azure OpenAI (أو مفتاح OpenAI API)&lt;/li>
&lt;li>Visual Studio أو Visual Studio Code&lt;/li>
&lt;/ul>
&lt;p>قم بتثبيت الحزم المطلوبة (لاحظ أن علامة &lt;code>--prerelease&lt;/code> مطلوبة أثناء المعاينة):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Agents.AI.Workflows --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.AI.OpenAI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.Identity
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.AI.Agents.Persistent --prerelease
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="وكلاء-تسوق-الهدايا">وكلاء تسوق الهدايا&lt;/h2>
&lt;p>سيتكون مكتشف هدايا عيد الميلاد الخاص بنا من ثلاثة وكلاء متخصصين يعملون معًا:&lt;/p>
&lt;ol>
&lt;li>** وكيل فكرة الهدية ** - يُنشئ اقتراحات هدايا إبداعية بناءً على الملف الشخصي للمستلم&lt;/li>
&lt;li>** وكيل مقارنة الأسعار ** - يجد أفضل الأسعار عبر المتاجر المختلفة&lt;/li>
&lt;li>&lt;strong>وكيل الملخص&lt;/strong> - يجمع التوصيات النهائية&lt;/li>
&lt;/ol>
&lt;h2 id="النماذج">النماذج&lt;/h2>
&lt;p>لنبدأ بتحديد نماذج البيانات التي ستتدفق عبر مسار الوكلاء:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftRecipient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Age { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Interests { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = [];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Budget { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftIdea&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Description { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Category { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> EstimatedPrice { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PriceResult&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GiftName { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Store { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Url { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftRecommendation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> GiftIdea Gift { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;PriceResult&amp;gt; Prices { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = [];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> PriceResult? BestDeal { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="بناء-الوكلاء">بناء الوكلاء&lt;/h2>
&lt;p>يكمن جمال Agent Framework في مدى سهولة إنشاء وكلاء متخصصين. كل وكيل هو ببساطة &lt;code>ChatClientAgent&lt;/code> مع موجه نظام محدد يحدد خبرته.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI.Workflows&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ChristmasAgentFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> AIAgent CreateGiftIdeaAgent(IChatClient chatClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">@&amp;#34;You are a creative Christmas gift advisor. When given information about a person
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> (age, interests, budget), you suggest thoughtful and personalized gift ideas.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> For each suggestion, provide:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Gift name
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Brief description of why it&amp;#39;s a good fit
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Category (Electronics, Books, Fashion, Home, Experience, etc.)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Estimated price range
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Always suggest 3-5 gift options within the specified budget.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Format your response as a structured list.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> AIAgent CreatePriceComparisonAgent(IChatClient chatClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">@&amp;#34;You are a price comparison specialist. Given a list of gift ideas,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> you research and compare prices from different online retailers.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> For each gift, provide:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Store name (Amazon, Best Buy, Target, Walmart, etc.)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Current price
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Any available discounts or deals
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Always highlight the best deal for each item.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Consider shipping costs and delivery times for Christmas.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> AIAgent CreateSummaryAgent(IChatClient chatClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">@&amp;#34;You are a gift recommendation summarizer. Take the gift ideas and price
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> comparisons and create a final recommendation report.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Your summary should:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Rank gifts by value (quality vs price)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Highlight the top pick with reasoning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Include total cost estimate
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Add any tips for holiday shopping
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Make the summary cheerful and festive!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="إنشاء-سير-العمل">إنشاء سير العمل&lt;/h2>
&lt;p>والآن يأتي الجزء الممتع، وهو ربط وكلائنا بسير عمل متسلسل. يوفر إطار عمل الوكيل &lt;code>WorkflowBuilder&lt;/code> و&lt;code>AgentWorkflowBuilder&lt;/code> لتكوين الوكلاء في أنماط مختلفة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ChristmasGiftWorkflow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task RunAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Set up the Azure OpenAI client&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> endpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ?? &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> InvalidOperationException(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT is not set.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> deploymentName = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_DEPLOYMENT_NAME&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o-mini&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chatClient = &lt;span style="color:#ff7b72">new&lt;/span> AzureOpenAIClient(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Uri(endpoint),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GetChatClient(deploymentName)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AsIChatClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create our specialized agents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AIAgent giftIdeaAgent = ChristmasAgentFactory.CreateGiftIdeaAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AIAgent priceAgent = ChristmasAgentFactory.CreatePriceComparisonAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AIAgent summaryAgent = ChristmasAgentFactory.CreateSummaryAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Build a sequential workflow: Ideas -&amp;gt; Prices -&amp;gt; Summary&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> workflow = AgentWorkflowBuilder.BuildSequential(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;ChristmasGiftFinder&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [giftIdeaAgent, priceAgent, summaryAgent]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Define our gift recipient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> recipient = &lt;span style="color:#ff7b72">new&lt;/span> GiftRecipient
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Mom&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Age = &lt;span style="color:#a5d6ff">55&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Interests = [&lt;span style="color:#a5d6ff">&amp;#34;gardening&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;cooking&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;reading&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;yoga&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Budget = &lt;span style="color:#a5d6ff">100&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">$@&amp;#34;Find Christmas gifts for {recipient.Name},
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> age {recipient.Age}, who enjoys {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, recipient.Interests)}.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Budget: &lt;span style="color:#f85149">$&lt;/span>{recipient.Budget}&lt;span style="color:#a5d6ff">&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Execute the workflow with streaming&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">StreamingRun&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> InProcessExecution.StreamAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> workflow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessage(ChatRole.User, prompt));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Send the turn token to start processing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> run.TrySendMessageAsync(&lt;span style="color:#ff7b72">new&lt;/span> TurnToken(emitEvents: &lt;span style="color:#79c0ff">true&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Watch for workflow events&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (WorkflowEvent evt &lt;span style="color:#ff7b72">in&lt;/span> run.WatchStreamAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (evt &lt;span style="color:#ff7b72">is&lt;/span> AgentRunUpdateEvent agentUpdate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(agentUpdate.Data);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (evt &lt;span style="color:#ff7b72">is&lt;/span> WorkflowOutputEvent outputEvent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n🎄 Final Recommendations:&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(outputEvent.Data);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="تشغيل-الوكلاء-بشكل-متزامن">تشغيل الوكلاء بشكل متزامن&lt;/h2>
&lt;p>ماذا لو أردنا البحث عن هدايا لعدة أشخاص في وقت واحد؟ يدعم Agent Framework التنفيذ المتزامن، وهو مثالي لهذا السيناريو:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task FindGiftsForEveryoneAsync(IChatClient chatClient, List&amp;lt;GiftRecipient&amp;gt; recipients)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create an agent for each recipient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agents = recipients.Select(r =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> ChatClientAgent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$@&amp;#34;Find the perfect Christmas gift for {r.Name} (age {r.Age}),
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> who loves {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, r.Interests)}. Budget: ${r.Budget}.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Provide one well-researched recommendation with price.&lt;span style="color:#a5d6ff">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> )).ToList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Build a concurrent workflow - all agents run in parallel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> workflow = AgentWorkflowBuilder.BuildConcurrent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;FamilyGiftFinder&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> agents);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">StreamingRun&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> InProcessExecution.StreamAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> workflow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessage(ChatRole.User, &lt;span style="color:#a5d6ff">&amp;#34;Find gifts now!&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> run.TrySendMessageAsync(&lt;span style="color:#ff7b72">new&lt;/span> TurnToken(emitEvents: &lt;span style="color:#79c0ff">true&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (WorkflowEvent evt &lt;span style="color:#ff7b72">in&lt;/span> run.WatchStreamAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (evt &lt;span style="color:#ff7b72">is&lt;/span> WorkflowOutputEvent output)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🎁 All gift recommendations ready!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(output.Data);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="منفذون-مخصصون-لمزيد-من-التحكمبالنسبة-للسيناريوهات-الأكثر-تعقيدا-يمكنك-إنشاء-برامج-تنفيذية-مخصصة-تمنحك-تحكما-دقيقا-في-سير-العمل">منفذون مخصصون لمزيد من التحكمبالنسبة للسيناريوهات الأكثر تعقيدًا، يمكنك إنشاء برامج تنفيذية مخصصة تمنحك تحكمًا دقيقًا في سير العمل:&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">sealed&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">GiftValidatorExecutor&lt;/span> : Executor&amp;lt;List&amp;lt;ChatMessage&amp;gt;, List&amp;lt;ChatMessage&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> GiftValidatorExecutor() : &lt;span style="color:#ff7b72">base&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#34;GiftValidator&amp;#34;&lt;/span>) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask&amp;lt;List&amp;lt;ChatMessage&amp;gt;&amp;gt; HandleAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;ChatMessage&amp;gt; messages,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IWorkflowContext context,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CancellationToken cancellationToken = &lt;span style="color:#ff7b72">default&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> lastMessage = messages.LastOrDefault()?.Text ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Validate that suggestions are within budget&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (lastMessage.Contains(&lt;span style="color:#a5d6ff">&amp;#34;over budget&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;⚠️ Some suggestions exceeded budget, filtering...&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Add validation logic here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;✅ Gift suggestions validated!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> messages;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكنك بعد ذلك إدراج هذا المنفذ في سير العمل الخاص بك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> validator = &lt;span style="color:#ff7b72">new&lt;/span> GiftValidatorExecutor();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> workflow = &lt;span style="color:#ff7b72">new&lt;/span> WorkflowBuilder(giftIdeaAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEdge(giftIdeaAgent, validator)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEdge(validator, priceAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEdge(priceAgent, summaryAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithOutputFrom(summaryAgent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="إضافة-بحث-ويب-حقيقي-باستخدام-bing-grounding">إضافة بحث ويب حقيقي باستخدام Bing Grounding&lt;/h2>
&lt;p>حتى الآن، يقوم وكلاؤنا بإنشاء استجابات بناءً على معرفة نموذج الذكاء الاصطناعي. ولكن ماذا لو أردنا البحث في الويب عن أسعار المنتجات الفعلية ومدى توفرها؟ هذا هو المكان الذي يأتي فيه &lt;strong>التأريض باستخدام بحث Bing&lt;/strong>. إنها أداة متوفرة في Microsoft Foundry (المعروف سابقًا باسم Azure AI Foundry) والتي تسمح لعملائك بدمج بيانات الويب العامة في الوقت الفعلي عند إنشاء الاستجابات.&lt;/p>
&lt;p>أولاً، ستحتاج إلى إنشاء مورد &lt;strong>التأريض باستخدام بحث Bing&lt;/strong> في &lt;a href="https://portal.azure.com/#create/Microsoft.BingGroundingSearch">Azure Portal&lt;/a>. تأكد من إنشائه في نفس مجموعة الموارد مثل مشروع الذكاء الاصطناعي الخاص بك.&lt;/p>
&lt;h3 id="إعداد-التأريض-باستخدام-بحث-bing">إعداد التأريض باستخدام بحث Bing&lt;/h3>
&lt;p>إن جمال التأريض مع Bing Search هو أنه يتكامل مباشرة مع Azure AI Agents. يقرر الوكيل متى يستخدم أداة البحث بناءً على استعلام المستخدم، ويبحث في الويب، ويستخدم النتائج لإنشاء استجابة مرتكزة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.Agents.Persistent&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">BingGroundingSetup&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;PersistentAgent&amp;gt; CreateAgentWithBingGroundingAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> projectEndpoint,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> modelDeploymentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> bingConnectionId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create the Persistent Agents client&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agentClient = &lt;span style="color:#ff7b72">new&lt;/span> PersistentAgentsClient(projectEndpoint, &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Configure the Bing Grounding tool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bingGroundingTool = &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingToolDefinition(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingSearchToolParameters(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [new BingGroundingSearchConfiguration(bingConnectionId)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create the agent with Bing Grounding enabled&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agent = &lt;span style="color:#ff7b72">await&lt;/span> agentClient.Administration.CreateAgentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model: modelDeploymentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#a5d6ff">&amp;#34;ChristmasPriceHunter&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> instructions: &lt;span style="color:#a5d6ff">@&amp;#34;You are a Christmas gift price comparison specialist.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> When given gift ideas, use Bing to search for:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Current prices at major retailers (Amazon, Best Buy, Target, Walmart)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Available discounts and holiday deals
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> - Shipping times to ensure delivery before Christmas
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Always provide URLs to the products you find.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Highlight the best deals and recommend where to buy.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tools: [bingGroundingTool]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> agent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="إنشاء-وكيل-لمقارنة-الأسعار-باستخدام-بحث-حقيقي">إنشاء وكيل لمقارنة الأسعار باستخدام بحث حقيقي&lt;/h3>
&lt;p>لنقم الآن بإنشاء وكيل كامل لمقارنة الأسعار يقوم بالبحث في الويب عن معلومات المنتج الحقيقية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.Agents.Persistent&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ChristmasPriceAgent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> PersistentAgentsClient _client;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _modelDeployment;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _bingConnectionId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ChristmasPriceAgent(&lt;span style="color:#ff7b72">string&lt;/span> projectEndpoint, &lt;span style="color:#ff7b72">string&lt;/span> modelDeployment, &lt;span style="color:#ff7b72">string&lt;/span> bingConnectionId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _client = &lt;span style="color:#ff7b72">new&lt;/span> PersistentAgentsClient(projectEndpoint, &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _modelDeployment = modelDeployment;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _bingConnectionId = bingConnectionId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; FindGiftPricesAsync(&lt;span style="color:#ff7b72">string&lt;/span> giftIdeas)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create agent with Bing Grounding&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bingTool = &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingToolDefinition(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> BingGroundingSearchToolParameters(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [new BingGroundingSearchConfiguration(_bingConnectionId)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> agent = &lt;span style="color:#ff7b72">await&lt;/span> _client.Administration.CreateAgentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model: _modelDeployment,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#a5d6ff">&amp;#34;PriceHunter&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> instructions: &lt;span style="color:#a5d6ff">@&amp;#34;Search the web for current prices on the given gift ideas.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> For each gift, find prices from at least 2-3 different stores.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Include direct links to the products.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Note any Christmas sales or discounts available.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tools: [bingTool]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create a thread for the conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> thread = &lt;span style="color:#ff7b72">await&lt;/span> _client.Threads.CreateThreadAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Add the gift ideas as a message&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _client.Messages.CreateMessageAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> thread.Id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MessageRole.User,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34;Find current prices for these gift ideas: {giftIdeas}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Run the agent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> _client.Runs.CreateRunAsync(thread.Id, agent.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Wait for completion&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(&lt;span style="color:#a5d6ff">500&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run = &lt;span style="color:#ff7b72">await&lt;/span> _client.Runs.GetRunAsync(thread.Id, run.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">while&lt;/span> (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get the response&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> messages = _client.Messages.GetMessages(thread.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = messages
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(m =&amp;gt; m.Role == MessageRole.Agent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SelectMany(m =&amp;gt; m.ContentItems)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OfType&amp;lt;MessageTextContent&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Clean up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _client.Threads.DeleteThreadAsync(thread.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response?.Text ?? &lt;span style="color:#a5d6ff">&amp;#34;No results found.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Clean up the agent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _client.Administration.DeleteAgentAsync(agent.Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="دمج-bing-grounding-في-سير-العمل">دمج Bing Grounding في سير العمل&lt;/h3>
&lt;p>فيما يلي كيفية استخدام وكيل الأسعار المدعوم من Bing في سير عمل كامل للبحث عن الهدايا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task RunWithBingGroundingAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> projectEndpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;PROJECT_ENDPOINT&amp;#34;&lt;/span>)!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> modelDeployment = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;MODEL_DEPLOYMENT_NAME&amp;#34;&lt;/span>) ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bingConnectionId = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;BING_CONNECTION_ID&amp;#34;&lt;/span>)!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Connection ID format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{account}/projects/{project}/connections/{connection}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🔍 Searching the web for real prices with Bing Grounding...\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> priceAgent = &lt;span style="color:#ff7b72">new&lt;/span> ChristmasPriceAgent(projectEndpoint, modelDeployment, bingConnectionId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// First, generate gift ideas (could come from another agent)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> giftIdeas = &lt;span style="color:#a5d6ff">&amp;#34;1. Milwaukee cordless drill set, 2. Weber portable grill, 3. Carhartt beanie&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Search for real prices&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> priceResults = &lt;span style="color:#ff7b72">await&lt;/span> priceAgent.FindGiftPricesAsync(giftIdeas);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;📊 Price Comparison Results:&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(priceResults);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n🎁 Happy Shopping! 🎁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="معالجة-الاستشهادات-من-نتائج-bing">معالجة الاستشهادات من نتائج Bing&lt;/h3>
&lt;p>أحد الجوانب المهمة في التأريض باستخدام بحث Bing هو أن الردود تتضمن اقتباسات مع روابط إلى مواقع الويب المصدر. وإليك كيفية استخراجها وعرضها:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ProcessBingGroundingResponse(IEnumerable&amp;lt;PersistentThreadMessage&amp;gt; messages)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> messages.Where(m =&amp;gt; m.Role == MessageRole.Agent))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> content &lt;span style="color:#ff7b72">in&lt;/span> message.ContentItems)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (content &lt;span style="color:#ff7b72">is&lt;/span> MessageTextContent textContent)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = textContent.Text;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Process URL citations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (textContent.Annotations != &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> annotation &lt;span style="color:#ff7b72">in&lt;/span> textContent.Annotations)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (annotation &lt;span style="color:#ff7b72">is&lt;/span> MessageTextUriCitationAnnotation uriAnnotation)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Replace citation placeholder with markdown link&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response = response.Replace(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uriAnnotation.Text,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">$&amp;#34; [{uriAnnotation.UriCitation.Title}]({uriAnnotation.UriCitation.Uri})&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(response);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن، عند تشغيل وكيل مقارنة الأسعار، فإنه يستخدم &lt;strong>التأريض باستخدام بحث Bing&lt;/strong> للعثور على قوائم المنتجات الحقيقية والأسعار الحالية والصفقات المتاحة من الويب المباشر. يقرر الوكيل تلقائيًا متى يجب البحث بناءً على الاستعلام ويعيد الإجابات المؤرضة مع الاستشهادات المناسبة.&lt;/p>
&lt;p>##البرنامج الكامل&lt;/p>
&lt;p>إليك كيفية تجميع كل شيء معًا في &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.AI.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Agents.AI.Workflows&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.AI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🎄 Christmas Gift Finder - Powered by AI Agents 🎄\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> endpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT&amp;#34;&lt;/span>)!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> deployment = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_DEPLOYMENT_NAME&amp;#34;&lt;/span>) ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o-mini&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatClient = &lt;span style="color:#ff7b72">new&lt;/span> AzureOpenAIClient(&lt;span style="color:#ff7b72">new&lt;/span> Uri(endpoint), &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GetChatClient(deployment)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AsIChatClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Create the agent team&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> ideaAgent = ChristmasAgentFactory.CreateGiftIdeaAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> priceAgent = ChristmasAgentFactory.CreatePriceComparisonAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> summaryAgent = ChristmasAgentFactory.CreateSummaryAgent(chatClient);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Build the workflow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> workflow = AgentWorkflowBuilder.BuildSequential(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;ChristmasGiftWorkflow&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ideaAgent, priceAgent, summaryAgent]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;Who are you shopping for? &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> recipientName = Console.ReadLine() ?? &lt;span style="color:#a5d6ff">&amp;#34;Someone special&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;What are their interests? &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> interests = Console.ReadLine() ?? &lt;span style="color:#a5d6ff">&amp;#34;general&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;What&amp;#39;s your budget? $&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> budget = Console.ReadLine() ?? &lt;span style="color:#a5d6ff">&amp;#34;50&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">$&amp;#34;Find Christmas gifts for {recipientName} who enjoys {interests}. Budget: ${budget}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n🔍 Searching for the perfect gifts...\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> run = &lt;span style="color:#ff7b72">await&lt;/span> InProcessExecution.StreamAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> workflow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessage(ChatRole.User, prompt));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> run.TrySendMessageAsync(&lt;span style="color:#ff7b72">new&lt;/span> TurnToken(emitEvents: &lt;span style="color:#79c0ff">true&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> evt &lt;span style="color:#ff7b72">in&lt;/span> run.WatchStreamAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (evt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> AgentRunUpdateEvent update:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(update.Update.Text);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> WorkflowOutputEvent output:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;\n\n🎁 Happy Shopping! 🎁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>يجعل Microsoft Agent Framework من السهل بشكل مدهش إنشاء أنظمة متعددة الوكلاء حيث يتخصص كل وكيل في مهمة محددة. ومن خلال تنسيق هؤلاء الوكلاء من خلال سير العمل، يمكننا معالجة المشكلات المعقدة مثل التسوق في عيد الميلاد بطريقة منظمة وفعالة.&lt;/p>
&lt;p>ما يجعل هذا أكثر قوة هو القدرة على إضافة إمكانات العالم الحقيقي من خلال الأدوات. من خلال دمج &lt;strong>Grounding مع Bing Search&lt;/strong> من Microsoft Foundry، يستطيع وكيل مقارنة الأسعار لدينا البحث فعليًا في الويب عن الأسعار والصفقات الحالية ومدى التوفر - مما يحول روبوت الدردشة البسيط الذي يعمل بالذكاء الاصطناعي إلى مساعد تسوق مفيد حقًا مع الاستشهادات المناسبة.&lt;/p>
&lt;p>إن دعم إطار العمل للأنماط التسلسلية والمتزامنة والتسليم يعني أنه يمكنك تصميم أنظمة الوكلاء التي تتوافق مع احتياجاتك الدقيقة. سواء كان الأمر يتعلق بالعثور على الهدايا، أو التخطيط للرحلات، أو أي مهمة أخرى متعددة الخطوات، فإن Agent Framework يوفر العناصر الأساسية لتحقيق ذلك.في موسم العطلات هذا، اسمح لعملاء الذكاء الاصطناعي بالتعامل مع البحث بينما تركز أنت على تغليف الهدايا والاستمتاع بالوقت مع العائلة!&lt;/p>
&lt;h2 id="كود-المصدر">كود المصدر&lt;/h2>
&lt;p>تستند المفاهيم الموضحة في هذا المنشور إلى عينات إطار عمل الوكيل الرسمية. يمكنك استكشاف المزيد من الأمثلة على &lt;a href="https://github.com/microsoft/agent-framework">مستودع Microsoft Agent Framework GitHub&lt;/a>.&lt;/p>
&lt;p>إجازة سعيدة وترميز سعيد! 🎄&lt;/p></content:encoded><category>.NET</category><category>Azure</category><category>AI</category><category>Agent Framework</category></item><item><title>بناء مجمع تغذية RSS مدعوم بالذكاء الاصطناعي</title><link>https://emimontesdeoca.github.io/ar/posts/ai-agent-socials/</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/ai-agent-socials/</guid><description>أتمتة مراقبة موجز RSS وإنشاء منشورات الوسائط الاجتماعية باستخدام Semantic Kernel وAzure OpenAI.</description><content:encoded>&lt;p>باعتباري أحد أفضل اللاعبين في Microsoft وعشاقًا للتكنولوجيا، أجد نفسي دائمًا غارقًا في محيط من المحتوى المذهل المنشور عبر DevBlogs من Microsoft. بدءًا من إعلانات .NET وحتى تحديثات Visual Studio، ومن ابتكارات Azure إلى الأبحاث العميقة حول Semantic Kernel - هناك دائمًا شيء جديد ومثير يحدث في نظام Microsoft البيئي.&lt;/p>
&lt;p>المشكلة؟ &lt;strong>مواكبة كل ذلك يكاد يكون من المستحيل.&lt;/strong>&lt;/p>
&lt;p>كنت أرغب في البقاء على اطلاع بأحدث الإعلانات ومشاركتها مع شبكتي، ولكن التحقق يدويًا من سبعة خلاصات RSS مختلفة، وقراءة المقالات، وصياغة منشورات جذابة على وسائل التواصل الاجتماعي، وتتبع ما قمت بمشاركته بالفعل، أصبحت وظيفة بدوام كامل في حد ذاتها. كل صباح، كنت أفتح عدة علامات تبويب في المتصفح، وأتصفح عشرات المقالات، وأحاول أن أتذكر المقالات التي قمت بمشاركتها بالفعل، ثم أقضي وقتًا ثمينًا في كتابة منشورات حول المقالات التي لفتت انتباهي.&lt;/p>
&lt;p>لذلك فعلت ما كان سيفعله أي مطور – &lt;strong>لقد قمت بتشغيله تلقائيًا&lt;/strong>.&lt;/p>
&lt;p>في هذا الدليل الشامل، سأرشدك عبر كيفية إنشاء مجمع موجزات RSS مدعوم بالذكاء الاصطناعي والذي يراقب خلاصات RSS المتعددة لـ Microsoft DevBlogs للمحتوى الجديد، ويستخدم Azure OpenAI وSemantic Kernel لتحليل المقالات وإنشاء منشورات جذابة، وإنشاء وثائق تخفيض السعر التفصيلية لكل مقالة تم تحليلها، وإرسال إشعارات عبر Telegram حتى أتمكن من مراجعة المحتوى ومشاركته، وتتبع كل شيء لتجنب المنشورات المكررة، وتشغيله تلقائيًا عبر GitHub Actions.&lt;/p>
&lt;p>دعونا نتعمق في كل جانب من جوانب هذا الحل.&lt;/p>
&lt;h2 id="القصة-وراء-هذا-المشروع">القصة وراء هذا المشروع&lt;/h2>
&lt;h3 id="العيش-مع-الحمل-الزائد-للمعلومات">العيش مع الحمل الزائد للمعلومات&lt;/h3>
&lt;p>اسمحوا لي أن أرسم لكم صورة لصباحي النموذجي قبل أن أقوم ببناء هذه الأداة. كنت أستيقظ وأتناول قهوتي وأفتح جهاز الكمبيوتر المحمول الخاص بي للتحقق من الجديد في النظام البيئي لمطوري Microsoft. أولاً، سأنتقل إلى موقع DevBlogs الرئيسي لمعرفة ما إذا كان هناك أي إعلانات رئيسية. ثم سأقوم بمراجعة مدونة .NET على وجه التحديد لأن هذه هي مجموعتي التكنولوجية الأساسية. بعد ذلك، سأنتقل إلى مدونة Semantic Kernel نظرًا لأن الذكاء الاصطناعي أصبح ذا أهمية متزايدة. كانت مدونة Visual Studio هي التالية في القائمة لأن تحديثات IDE يمكن أن تؤثر بشكل كبير على سير العمل اليومي. ثم جاءت مدونة DevOps للأخبار المتعلقة بـ CI/CD وGitHub، تليها مدونة All Things Azure لتحديثات البنية التحتية السحابية، وأخيرًا مدونة Azure SQL لابتكارات قواعد البيانات.&lt;/p>
&lt;p>هذه سبعة خلاصات مختلفة للتحقق منها. تنشر كل من هذه المدونات عدة مقالات أسبوعيًا، وأحيانًا عدة مقالات يوميًا خلال فترات الإعلان الرئيسية مثل .NET Conf أو Build. من المحتمل أن يكون هذا عشرات المقالات لتتبعها وقراءتها ومشاركتها. وهنا الأمر - كشخص يقدر مشاركة المعرفة مع المجتمع، لم أرغب في قراءة هذه المقالات فقط. أردت مشاركة أهم الأشياء مع شبكتي على LinkedIn، لمساعدة المطورين الآخرين على البقاء على اطلاع أيضًا.لكن إنشاء منشور جيد على LinkedIn يستغرق وقتًا. تحتاج إلى قراءة المقالة جيدًا، وفهم النقاط الرئيسية، والتفكير في سبب أهميتها لجمهورك، وكتابة رابط جذاب، وتنسيق كل شيء بشكل جيد. اضرب ذلك بعدة مقالات في الأسبوع، وستحصل على ساعات العمل.&lt;/p>
&lt;h3 id="ما-أردته-حقا">ما أردته حقًا&lt;/h3>
&lt;p>بعد التعامل مع هذا لعدة أشهر، جلست وفكرت في الشكل الذي سيبدو عليه الحل الأمثل. أولاً وقبل كل شيء، لم أرغب أبدًا في تفويت أي إعلانات مهمة مرة أخرى. يجب أن يلتقط النظام المقالات الجديدة تلقائيًا بمجرد نشرها. وأردت أيضًا توفير الوقت في إنشاء المحتوى من خلال السماح للذكاء الاصطناعي بالمساعدة في صياغة منشورات جذابة - ليس لاستبدال صوتي بالكامل، ولكن لإعطائي نقطة بداية قوية يمكنني تخصيصها.&lt;/p>
&lt;p>وكان الاتساق عاملا كبيرا آخر. كنت أرغب في مشاركة المحتوى بانتظام دون الحاجة إلى تذكر القيام بذلك يدويًا كل يوم. كان جانب التتبع حاسمًا أيضًا - كنت بحاجة إلى طريقة لمعرفة ما قمت بمشاركته بالفعل لتجنب نشر التكرارات وإزعاج متابعي. أخيرًا، أردت أن أبقى منظمًا من خلال سجل دائم لكل ما قمت بمعالجته، حتى أتمكن من الرجوع إلى الوراء ومعرفة الموضوعات التي قمت بتغطيتها.&lt;/p>
&lt;h3 id="الحل-يتبلور">الحل يتبلور&lt;/h3>
&lt;p>سيتم تشغيل الحل الذي تصورته وفقًا لجدول زمني باستخدام GitHub Actions، بدون استخدام اليدين تمامًا. سيجلب جميع الخلاصات السبعة تلقائيًا دون الحاجة إلى فتح علامة تبويب متصفح واحدة. سيقرأ مكون الذكاء الاصطناعي المحتوى ويفهمه، ثم يلخصه بطريقة مفيدة لجمهوري. بدلاً من أن أضطر إلى كتابة منشورات من الصفر، فإنه سينشئ محتوى جاهزًا للمشاركة على وسائل التواصل الاجتماعي ويمكنني تعديله إذا لزم الأمر. سيتم إرسال كل شيء إلى Telegram الخاص بي للمراجعة، حتى أتمكن من إلقاء نظرة سريعة على هاتفي وتحديد ما سأشاركه. وبطبيعة الحال، فإنه سيحتفظ بسجل دائم لكل شيء للرجوع إليه في المستقبل.&lt;/p>
&lt;h2 id="قبل-أن-نبدأ-البناء">قبل أن نبدأ البناء&lt;/h2>
&lt;h3 id="ما-ستحتاجه-على-جهازك">ما ستحتاجه على جهازك&lt;/h3>
&lt;p>لمتابعة هذا البرنامج التعليمي، ستحتاج إلى تثبيت بعض الأشياء على جهاز التطوير الخاص بك. أهمها هو الإصدار 9.0 من .NET SDK أو الأحدث. هذا هو وقت التشغيل الخاص بنا ويوفر جميع أدوات البناء التي نحتاجها. إذا لم يكن مثبتًا لديك، فانتقل إلى dot.net وقم بتنزيل أحدث إصدار. التثبيت سهل ومباشر على أنظمة التشغيل Windows أو macOS أو Linux.&lt;/p>
&lt;p>ستحتاج أيضًا إلى تثبيت Git للتحكم في الإصدار. سنقوم بإرسال الكود الخاص بنا إلى GitHub واستخدام إجراءات GitHub للأتمتة، لذا يعد إعداد Git محليًا أمرًا ضروريًا. أي نسخة حديثة سوف تعمل بشكل جيد.&lt;/p>
&lt;p>بالنسبة لبيئة التطوير الخاصة بك، أوصي باستخدام Visual Studio أو VS Code. أنا شخصياً أستخدم VS Code في معظم أعمالي هذه الأيام لأنه خفيف الوزن ويتمتع بدعم ممتاز لـ C# من خلال ملحق C# Dev Kit. ولكن إذا كنت أكثر راحة مع Visual Studio الكامل، فهذا يعمل بشكل مثالي أيضًا.&lt;/p>
&lt;h3 id="الخدمات-والحسابات-التي-ستحتاج-إليهابالإضافة-إلى-الأدوات-المحلية-ستحتاج-إلى-حسابات-تحتوي-على-عدد-قليل-من-الخدمات-وأهمها-هو-azure-openai-الذي-يدعم-تحليل-الذكاء-الاصطناعي-لدينا-هذه-خدمة-الدفع-أولا-بأول-ولكن-التكاليف-ضئيلة-بالنسبة-لحالة-الاستخدام-هذه---فنحن-نتحدث-عن-سنتات-لكل-مقالة-يتم-تحليلها-إذا-لم-يكن-لديك-حساب-azure-فيمكنك-التسجيل-للحصول-على-نسخة-تجريبية-مجانية-تتضمن-بعض-الاعتمادات-للبدء">الخدمات والحسابات التي ستحتاج إليهابالإضافة إلى الأدوات المحلية، ستحتاج إلى حسابات تحتوي على عدد قليل من الخدمات. وأهمها هو Azure OpenAI، الذي يدعم تحليل الذكاء الاصطناعي لدينا. هذه خدمة الدفع أولاً بأول، ولكن التكاليف ضئيلة بالنسبة لحالة الاستخدام هذه - فنحن نتحدث عن سنتات لكل مقالة يتم تحليلها. إذا لم يكن لديك حساب Azure، فيمكنك التسجيل للحصول على نسخة تجريبية مجانية تتضمن بعض الاعتمادات للبدء.&lt;/h3>
&lt;p>للإشعارات، سنستخدم Telegram Bot. إن الشيء العظيم في Telegram هو أن واجهة برمجة التطبيقات الخاصة بهم مجانية تمامًا للاستخدام. يمكنك إنشاء العدد الذي تريده من الروبوتات وإرسال رسائل غير محدودة. سأرشدك خلال عملية الإعداد لاحقًا في هذا الدليل.&lt;/p>
&lt;p>وأخيرًا، ستحتاج إلى حساب GitHub لاستضافة التعليمات البرمجية الخاصة بك وتشغيل إجراءات GitHub. الطبقة المجانية أكثر من كافية لهذا المشروع. يمنحك GitHub 2000 دقيقة من وقت تشغيل الإجراءات شهريًا على المستودعات الخاصة، ودقائق غير محدودة على المستودعات العامة.&lt;/p>
&lt;h3 id="المكتبات-التي-تجعل-هذا-ممكنا">المكتبات التي تجعل هذا ممكنًا&lt;/h3>
&lt;p>يعتمد مشروعنا على ثلاث حزم NuGet رئيسية، تخدم كل منها غرضًا محددًا.&lt;/p>
&lt;p>الأول هو HtmlAgilityPack، وهو المعيار الذهبي لتحليل HTML في .NET. عندما نجلب مقالة من إحدى المدونات، فإننا نستعيد HTML الكامل للصفحة - بما في ذلك قوائم التنقل والتذييلات والإعلانات وجميع أنواع العناصر التي لا نهتم بها. يتيح لنا HtmlAgilityPack تحليل HTML واستخراج محتوى المقالة الذي نحتاجه فقط.&lt;/p>
&lt;p>الحزمة الثانية هي Microsoft.SemanticKernel، وهي حزمة SDK من Microsoft لدمج نماذج الذكاء الاصطناعي في التطبيقات. فكر في الأمر كجسر بين كود .NET الخاص بك ونماذج اللغات الكبيرة مثل GPT-4. فهو يتعامل مع كل تعقيدات استدعاءات واجهة برمجة التطبيقات (API)، وإدارة الرموز المميزة، وتحليل الاستجابة، مما يتيح لك التركيز على ما تريد أن يفعله الذكاء الاصطناعي بالفعل.&lt;/p>
&lt;p>الحزمة الثالثة هي System.ServiceModel.Synvation، والتي توفر دعمًا مدمجًا لتحليل خلاصات RSS وAtom. قد تبدو خدمة RSS بمثابة تقنية قديمة، ولكنها لا تزال أفضل طريقة للحصول على تحديثات منظمة من المدونات والمواقع الإخبارية. تعمل هذه الحزمة على تحويل موجزات XML الأولية إلى كائنات C# مكتوبة بقوة والتي يسهل التعامل معها.&lt;/p>
&lt;h2 id="فهم-العمارة">فهم العمارة&lt;/h2>
&lt;h3 id="كيف-تتناسب-القطع-مع-بعضها-البعض">كيف تتناسب القطع مع بعضها البعض&lt;/h3>
&lt;p>قبل أن نتعمق في الكود، اسمحوا لي أن أشرح كيفية عمل جميع المكونات معًا. إن فهم الصورة الكبيرة سيجعل تفاصيل التنفيذ أكثر وضوحًا.&lt;/p>
&lt;p>على أعلى مستوى، لدينا ملف Program.cs الرئيسي الذي يعمل كمنسق. هذه هي نقطة الدخول لتطبيقنا، وهي تنسق جميع المكونات الأخرى. عند تشغيل التطبيق، يقوم أولاً بتحميل التكوين من متغيرات البيئة – أشياء مثل مفاتيح API وبيانات اعتماد Telegram. ثم يخرج ويجلب موجزات RSS من جميع مصادر Microsoft DevBlogs السبعة. أثناء معالجة هذه الخلاصات، فإنها تعمل على إلغاء تكرار المقالات للتعامل مع الحالات التي تظهر فيها نفس المقالة في خلاصات متعددة. يقوم بفحص كل مقالة مقابل ملف التتبع الخاص بنا لمعرفة ما إذا كنا قد قمنا بمعالجتها بالفعل. بالنسبة للمقالات الجديدة، يتم تسليمها إلى محلل الذكاء الاصطناعي لمعالجتها.فئة ArticleAnalyzer هي المكان الذي يحدث فيه سحر الذكاء الاصطناعي. يتلقى هذا المكون مقالًا ويقوم بعدة أشياء به. أولاً، يقوم بجلب محتوى HTML الكامل من عنوان URL الخاص بالمقالة. ثم يقوم باستخراج نص نظيف من HTML، وإزالة كافة عناصر التنقل والبرامج النصية والتصميمات التي لا نحتاج إليها. بمجرد حصوله على نص نظيف، فإنه يرسل ذلك إلى Azure OpenAI من خلال Semantic Kernel بمطالبة مصممة بعناية. يقوم الذكاء الاصطناعي بتحليل المقالة وإرجاع استجابة منظمة تتضمن ملخصًا وموضوعات رئيسية وشرحًا لأهميتها، والأهم من ذلك، منشورًا جاهزًا للاستخدام على LinkedIn. يقوم المحلل بتحليل هذه الاستجابة وإرجاع كائن ArticleAnalogy الذي يحتوي على كل هذه المعلومات.&lt;/p>
&lt;p>تأخذ فئة MarkdownGenerator كائن ArticleAna Analysis وتقوم بإنشاء سجل دائم له. يقوم بإنشاء ملف تخفيض السعر منسق بشكل جيد يتضمن جميع البيانات التعريفية للمقالة، وتحليل الذكاء الاصطناعي، والمنشور الذي تم إنشاؤه. يتم تخزين هذه الملفات في دليل المنشورات التي تم إنشاؤها، مما يتيح لك أرشيفًا قابلاً للبحث لكل ما قمت بمعالجته.&lt;/p>
&lt;p>أخيرًا، يرسل تكامل Telegram محتوى المنشور الذي تم إنشاؤه إلى هاتفك. هذه هي النقطة التي يمكنك فيها، كإنسان، مراجعة عمل الذكاء الاصطناعي وتقرر ما إذا كنت تريد مشاركته أم لا. يرسل لك الروبوت رسالة تحتوي على محتوى المنشور، ويمكنك إما نسخها مباشرةً إلى LinkedIn أو تعديلها أولاً.&lt;/p>
&lt;h3 id="تدفق-البيانات">تدفق البيانات&lt;/h3>
&lt;p>اسمح لي أن أطلعك على ما يحدث عند نشر مقالة جديدة على مدونة .NET. يبدأ سير العمل عندما تقوم GitHub Actions بتشغيل تطبيقنا وفقًا لجدوله الزمني - لنفترض كل ست ساعات. ينشط التطبيق ويبدأ في جلب جميع خلاصات RSS السبعة. يعرض كل موجز مستند XML يحتوي على أحدث المقالات من تلك المدونة.&lt;/p>
&lt;p>أثناء تحليل كل موجز، نقوم باستخراج المقالات الفردية وتخزينها في القائمة. ولكن هنا جزء صعب - غالبًا ما تتضمن خلاصة DevBlogs الرئيسية مقالات تظهر أيضًا في خلاصات الفئات الفردية. لذلك قد تظهر مقالة حول &amp;ldquo;.NET 10&amp;rdquo; في كل من الخلاصة الرئيسية والخلاصة الخاصة بـ .NET. نحن نتعامل مع هذا من خلال تتبع عناوين URL في HashSet، مما يمنع التكرارات تلقائيًا.&lt;/p>
&lt;p>بمجرد حصولنا على قائمة المقالات المكررة، نقوم بتصفيتها وصولاً إلى المقالات الحديثة فقط - عادةً المقالات المنشورة في اليوم الأخير أو نحو ذلك. لا نريد معالجة المقالات القديمة التي تعاملنا معها بالفعل في عمليات التشغيل السابقة. ثم نتحقق من كل مقالة حديثة مقابل ملف التتبع الخاص بنا. إذا قمنا بالفعل بمعالجة مقالة ونشرها، فإننا نتخطى ذلك.&lt;/p>
&lt;p>لكل مقال جديد، نبدأ مسار تحليل الذكاء الاصطناعي. يقوم المحلل بإحضار HTML للمقالة الكاملة، وينظفها، ويرسلها إلى GPT-4 مع مطالبتنا. يقرأ الذكاء الاصطناعي المقالة ويقوم بإنشاء تحليل شامل بالإضافة إلى منشور على LinkedIn. نحفظ هذا التحليل في ملف تخفيض السعر لسجلاتنا.بعد اكتمال التحليل، نقوم بتنسيق رسالة وإرسالها عبر Telegram. تتضمن الرسالة محتوى المنشور الذي تم إنشاؤه مع عنوان URL وعلامات التصنيف الملحقة. على هاتفي، أتلقى إشعارًا، وأراجع المنشور، وإذا أعجبتني، يمكنني نسخه ومشاركته على LinkedIn ببضع نقرات فقط.&lt;/p>
&lt;p>أخيرًا، نقوم بتحديث ملف التتبع الخاص بنا لوضع علامة على هذه المقالة على أنها تمت معالجتها، لذلك لن نتعامل معها مرة أخرى في عمليات التشغيل المستقبلية. إذا تم إنشاء أي ملفات أو تعديلها، فإن GitHub Actions يُلزم هذه التغييرات مرة أخرى بالمستودع، مع الحفاظ على مزامنة كل شيء.&lt;/p>
&lt;h2 id="إعداد-المشروع-من-الصفر">إعداد المشروع من الصفر&lt;/h2>
&lt;h3 id="إنشاء-هيكل-الحل">إنشاء هيكل الحل&lt;/h3>
&lt;p>لنبدأ بالبناء. افتح المحطة الطرفية الخاصة بك وانتقل إلى المكان الذي تريد إنشاء المشروع فيه. أحب أن أبقي مشاريعي منظمة في مجلد التطوير، ولكن يمكنك وضعها في أي مكان يناسبك.&lt;/p>
&lt;p>أولاً، سنقوم بإنشاء ملف حل جديد. في .NET، الحل عبارة عن حاوية يمكنها الاحتفاظ بمشاريع متعددة. على الرغم من أنه لدينا مشروع واحد فقط في الوقت الحالي، فإن البدء بالحل يجعل من السهل إضافة المزيد من المشاريع لاحقًا إذا لزم الأمر. قم بتشغيل الأمر &lt;code>dotnet new sln -n vs-feed-linkedin&lt;/code> لإنشاء حل باسم vs-feed-linkedin.&lt;/p>
&lt;p>بعد ذلك، نحتاج إلى إنشاء مشروع تطبيق وحدة التحكم الخاص بنا. سنضع هذا في دليل فرعي src للحفاظ على تنظيم الأمور. قم بتشغيل &lt;code>dotnet new console -n VsFeedLinkedin -o src&lt;/code> لإنشاء مشروع وحدة تحكم باسم VsFeedLinkedin في مجلد src. ثم أضف هذا المشروع إلى حلنا باستخدام &lt;code>dotnet sln add src/VsFeedLinkedin.csproj&lt;/code>.&lt;/p>
&lt;p>انتقل الآن إلى دليل src باستخدام &lt;code>cd src&lt;/code>. هذا هو المكان الذي سنضيف فيه حزم NuGet الخاصة بنا ونقوم بمعظم عمليات التطوير لدينا.&lt;/p>
&lt;h3 id="إضافة-الحزم-المطلوبة">إضافة الحزم المطلوبة&lt;/h3>
&lt;p>بعد إنشاء مشروعنا، نحتاج إلى إضافة حزم NuGet الثلاث التي ذكرتها سابقًا. قم بتشغيل كل من هذه الأوامر بالتسلسل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package System.ServiceModel.Syndication --version 9.0.9
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel --version 1.30.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package HtmlAgilityPack --version 1.11.72
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد تشغيل هذه الأوامر، يجب أن يبدو ملف مشروعك كما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;Project&lt;/span> Sdk=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;OutputType&amp;gt;&lt;/span>Exe&lt;span style="color:#7ee787">&amp;lt;/OutputType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;TargetFramework&amp;gt;&lt;/span>net9.0&lt;span style="color:#7ee787">&amp;lt;/TargetFramework&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;ImplicitUsings&amp;gt;&lt;/span>enable&lt;span style="color:#7ee787">&amp;lt;/ImplicitUsings&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;Nullable&amp;gt;&lt;/span>enable&lt;span style="color:#7ee787">&amp;lt;/Nullable&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;HtmlAgilityPack&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;1.11.72&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.SemanticKernel&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;1.30.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;System.ServiceModel.Syndication&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.9&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/Project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يخبر ملف المشروع .NET أننا نبني ملفًا قابلاً للتنفيذ (OutputType هو Exe)، ونستهدف .NET 9.0، ونستخدم ميزات C# الحديثة مثل الاستخدامات الضمنية وأنواع المراجع الخالية. يسرد قسم ItemGroup تبعيات الحزمة الثلاثة مع إصداراتها الدقيقة.&lt;/p>
&lt;h2 id="الغوص-العميق-في-خلاصات-rss">الغوص العميق في خلاصات RSS&lt;/h2>
&lt;h3 id="ما-هو-نظام-rss-بالضبط">ما هو نظام RSS بالضبط؟&lt;/h3>
&lt;p>قبل أن نبدأ في كتابة التعليمات البرمجية لجلب الخلاصات، دعونا نتأكد من أننا نفهم ما نعمل معه. يرمز RSS إلى &amp;ldquo;Really Simple Syndication&amp;rdquo;، وهو تنسيق XML موحد لتوزيع تحديثات المحتوى. الفكرة بسيطة: بدلاً من مطالبة المستخدمين بزيارة موقع الويب الخاص بك لمعرفة ما إذا كان هناك محتوى جديد، يمكنك نشر ملف يمكن قراءته آليًا يسرد المحتوى الحديث الخاص بك. يمكن للتطبيقات بعد ذلك استقصاء هذا الملف بشكل دوري لاكتشاف مقالات جديدة.&lt;/p>
&lt;p>كانت خدمة RSS موجودة منذ أواخر التسعينيات وأوائل العقد الأول من القرن الحادي والعشرين. قد تعتقد أنها تقنية قديمة، لكنها في الواقع لا تزال مستخدمة على نطاق واسع - خاصة من خلال المدونات والمواقع الإخبارية والبودكاست. جمال RSS هو بساطته. إنه مجرد ملف XML ذو بنية محددة، ويمكن لأي تطبيق تحليله.&lt;/p>
&lt;h3 id="بنية-موجز-مدونات-المطورينعندما-تقوم-بإحضار-موجز-rss-من-microsoft-devblogs-يمكنك-استرجاع-مستند-xml-يتبع-بنية-محددة-في-المستوى-الأعلى-يوجد-عنصر-rss-يحتوي-على-عنصر-قناة-واحد-تمثل-القناة-المدونة-نفسها-وتتضمن-بيانات-وصفية-مثل-عنوان-المدونة-وعنوان-url-والوصف">بنية موجز مدونات المطورينعندما تقوم بإحضار موجز RSS من Microsoft DevBlogs، يمكنك استرجاع مستند XML يتبع بنية محددة. في المستوى الأعلى، يوجد عنصر RSS يحتوي على عنصر قناة واحد. تمثل القناة المدونة نفسها وتتضمن بيانات وصفية مثل عنوان المدونة وعنوان URL والوصف.&lt;/h3>
&lt;p>ستجد داخل القناة عناصر متعددة، يمثل كل منها منشور مدونة فرديًا. يتضمن كل عنصر عنوانًا (عنوان المقالة)، ورابطًا (عنوان URL الذي يمكنك من خلاله قراءة المقالة كاملة)، وتاريخ النشر (تاريخ نشر المقالة)، وعنصر dc:creator (اسم المؤلف)، وعنصر فئة واحد أو أكثر (علامات للمقالة)، ووصف (عادةً ملخص أو مقتطف من المقالة).&lt;/p>
&lt;p>فيما يلي مثال مبسط لما يبدو عليه هذا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;rss&lt;/span> version=&lt;span style="color:#a5d6ff">&amp;#34;2.0&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;channel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;title&amp;gt;&lt;/span>.NET Blog&lt;span style="color:#7ee787">&amp;lt;/title&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;link&amp;gt;&lt;/span>https://devblogs.microsoft.com/dotnet&lt;span style="color:#7ee787">&amp;lt;/link&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;description&amp;gt;&lt;/span>The latest news about .NET&lt;span style="color:#7ee787">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;item&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;title&amp;gt;&lt;/span>Announcing .NET 10&lt;span style="color:#7ee787">&amp;lt;/title&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;link&amp;gt;&lt;/span>https://devblogs.microsoft.com/dotnet/announcing-dotnet-10&lt;span style="color:#7ee787">&amp;lt;/link&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;pubDate&amp;gt;&lt;/span>Mon, 10 Dec 2025 12:00:00 GMT&lt;span style="color:#7ee787">&amp;lt;/pubDate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;dc:creator&amp;gt;&lt;/span>Microsoft&lt;span style="color:#7ee787">&amp;lt;/dc:creator&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;category&amp;gt;&lt;/span>Announcements&lt;span style="color:#7ee787">&amp;lt;/category&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;category&amp;gt;&lt;/span>.NET&lt;span style="color:#7ee787">&amp;lt;/category&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;description&amp;gt;&lt;/span>Article summary...&lt;span style="color:#7ee787">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/item&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/channel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/rss&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إن الشيء العظيم في حزمة System.ServiceModel.Synvation الخاصة بـ .NET هو أنها تقوم بتحليل كل هذا لنا. لا يتعين علينا التنقل يدويًا في عقد XML أو القلق بشأن إصدارات RSS المختلفة. نحن فقط نقوم بتحميل الخلاصة ونستعيد الكائنات المكتوبة بقوة.&lt;/p>
&lt;h3 id="الخلاصات-السبعة-التي-نراقبها">الخلاصات السبعة التي نراقبها&lt;/h3>
&lt;p>أثناء تنفيذي، أراقب سبعة خلاصات مختلفة لـ Microsoft DevBlogs. يمنحنا موجز DevBlogs الرئيسي على devblogs.microsoft.com/feed رؤية واسعة لكل ما تنشره Microsoft عبر جميع مدونات المطورين الخاصة بها. يركز الموجز الخاص بـ .NET الموجود على devblogs.microsoft.com/dotnet/feed بشكل خاص على إصدارات .NET وميزاته وأفضل الممارسات. يغطي موجز Semantic Kernel الموجود على devblogs.microsoft.com/semantic-kernel/feed تنسيق الذكاء الاصطناعي وتكامله - وهو أمر ذو أهمية متزايدة حيث أصبح الذكاء الاصطناعي محوريًا في التطوير الحديث.&lt;/p>
&lt;p>يبقيني موجز Visual Studio على devblogs.microsoft.com/visualstudio/feed على اطلاع دائم بتحسينات IDE وميزات الإنتاجية. يغطي موجز DevOps الموجود على devblogs.microsoft.com/devops/feed موضوعات Azure DevOps وGitHub وCI/CD. يركز موجز All Things Azure على devblogs.microsoft.com/all-things-azure/feed على الخدمات السحابية وأنماط الهندسة المعمارية. وأخيرًا، يغطي موجز Azure SQL الموجود على devblogs.microsoft.com/azure-sql/feed ابتكارات قواعد البيانات وميزاتها.&lt;/p>
&lt;p>قد تتساءل لماذا أتحقق من كل من الخلاصات الرئيسية وخلاصات الفئات الفردية. يمنحني الموجز الرئيسي اتساعًا - سأرى مقالات من أي مدونة لمطوري Microsoft، بما في ذلك تلك التي قد لا أعرف عنها شيئًا. تمنحني خلاصات الفئات عمقًا - فهي تضمن عدم تفويت أي شيء مهم في مجالات اهتماماتي الأساسية، حتى لو تم إخراج هذه المقالات من الخلاصة الرئيسية بواسطة محتوى أحدث.&lt;/p>
&lt;h2 id="بناء-منطق-جلب-rss">بناء منطق جلب RSS&lt;/h2>
&lt;h3 id="وظيفة-الجلب-الأساسية">وظيفة الجلب الأساسية&lt;/h3>
&lt;p>الآن دعونا نكتب بعض التعليمات البرمجية. أساس تطبيقنا هو القدرة على جلب وتحليل خلاصات RSS. إليك الوظيفة التي تتعامل مع هذا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;SyndicationFeed?&amp;gt; FetchRssFeedAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> httpClient.DefaultRequestHeaders.Add(&lt;span style="color:#a5d6ff">&amp;#34;User-Agent&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;VsFeedLinkedin/1.0&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetStringAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> stringReader = &lt;span style="color:#ff7b72">new&lt;/span> StringReader(response);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> settings = &lt;span style="color:#ff7b72">new&lt;/span> XmlReaderSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DtdProcessing = DtdProcessing.Parse,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaxCharactersFromEntities = &lt;span style="color:#a5d6ff">1024&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> xmlReader = XmlReader.Create(stringReader, settings);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> SyndicationFeed.Load(xmlReader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>اسمحوا لي أن أتعرف على ما يفعله هذا الرمز. نبدأ بإنشاء HttpClient، وهو فئة .NET المضمنة لتقديم طلبات HTTP. لقد قمنا بتعيين رأس وكيل المستخدم لأن بعض الخوادم تحظر الطلبات التي لا تحدد هويتها. من الممارسات الجيدة ضبط هذا حتى عندما لا تتطلب الخوادم ذلك.نقوم بعد ذلك بتقديم طلب GET إلى عنوان URL للخلاصة ونتلقى الرد كسلسلة. تحتوي هذه السلسلة على ملف XML الأولي لخلاصة RSS.&lt;/p>
&lt;p>لتحليل ملف XML هذا، نقوم بإنشاء StringReader لتغليف سلسلة الاستجابة الخاصة بنا، ثم نقوم بتكوين بعض إعدادات XmlReaderSettings. يعد إعداد DtdProcessing مهمًا - تتضمن خلاصات RSS أحيانًا إعلانات DTD (تعريف نوع المستند) التي تحتاج إلى معالجة. يعد إعداد MaxCharactersFromEntities بمثابة إجراء أمني يمنع هجمات XML بالقنابل عن طريق الحد من مقدار توسيع الكيان الذي يمكن أن يحدث.&lt;/p>
&lt;p>وأخيرًا، نقوم بإنشاء XmlReader بهذه الإعدادات واستخدام SyndicationFeed.Load لتحليل XML إلى كائن SyndicationFeed مكتوب بقوة. يمنحنا هذا إمكانية الوصول إلى البيانات التعريفية للخلاصة وجميع عناصرها من خلال خصائص C# الرائعة بدلاً من التنقل بتنسيق XML الأولي.&lt;/p>
&lt;h3 id="جلب-موجزات-متعددة-مع-معالجة-الأخطاء">جلب موجزات متعددة مع معالجة الأخطاء&lt;/h3>
&lt;p>في العالم الحقيقي، تفشل طلبات الشبكة. تتعطل الخوادم، وتنتهي مهلة الاتصالات، ويمكن أن يكون XML مشوهًا. نحن بحاجة للتعامل مع هذه الحالات بأمان. إليك كيفية جلب جميع خلاصاتنا مع المرونة في مواجهة حالات الفشل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> allArticles = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;(SyndicationItem item, &lt;span style="color:#ff7b72">string&lt;/span> feedUrl)&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> seenUrls = &lt;span style="color:#ff7b72">new&lt;/span> HashSet&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> feedUrl &lt;span style="color:#ff7b72">in&lt;/span> feedUrls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34; 📡 Fetching {feedUrl}...&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> feed = &lt;span style="color:#ff7b72">await&lt;/span> FetchRssFeedAsync(feedUrl);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (feed?.Items != &lt;span style="color:#79c0ff">null&lt;/span> &amp;amp;&amp;amp; feed.Items.Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72">in&lt;/span> feed.Items)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> itemUrl = item.Links.FirstOrDefault()?.Uri.ToString() ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(itemUrl) &amp;amp;&amp;amp; seenUrls.Add(itemUrl))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> allArticles.Add((item, feedUrl));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34; ⚠️ Failed to fetch {feedUrl}: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نحن نحتفظ بمجموعتين هنا. ستحتوي قائمة allArticles على جميع المقالات التي نجدها، بالإضافة إلى الخلاصة التي أتت منها. يتتبع SeenUrls HashSet عناوين URL للمقالات التي رأيناها بالفعل، مما يساعدنا على تجنب التكرارات.&lt;/p>
&lt;p>نقوم بالتكرار خلال كل عنوان URL للخلاصة ونقوم بتغليف عملية الجلب في كتلة محاولة الالتقاط. إذا فشل جلب موجز معين - ربما يكون الخادم معطلاً مؤقتًا - فإننا نسجل تحذيرًا ونستمر في البث التالي. بهذه الطريقة، لا تمنعنا مشكلة في إحدى الخلاصات من معالجة الخلاصات الأخرى.&lt;/p>
&lt;p>لكل خلاصة تم جلبها بنجاح، نقوم بالتكرار من خلال عناصرها. نقوم باستخراج عنوان URL للمقالة من مجموعة الروابط الخاصة بالعنصر. تقوم طريقة HashSet.Add بإرجاع خطأ إذا كان عنوان URL موجودًا بالفعل في المجموعة، وهو مثالي لمنطق إلغاء البيانات المكررة لدينا. نضيف المقالة إلى قائمتنا فقط إذا كانت جديدة.&lt;/p>
&lt;p>نقوم بتخزين عنوان URL للخلاصة بجانب كل مقالة لأن هذه المعلومات قد تكون مفيدة لاحقًا - على سبيل المثال، قد نرغب في معرفة الخلاصة المحددة التي جاءت منها المقالة لأغراض تصحيح الأخطاء أو التسجيل.&lt;/p>
&lt;h2 id="التعامل-مع-التكرارات-وحالة-التتبع">التعامل مع التكرارات وحالة التتبع&lt;/h2>
&lt;h3 id="تحدي-إلغاء-البيانات-المكررة">تحدي إلغاء البيانات المكررة&lt;/h3>
&lt;p>كما ذكرت سابقًا، تمتلك Microsoft DevBlogs بنية موجزة هرمية تخلق تحديًا مثيرًا للاهتمام. عندما ينشر أحد أعضاء فريق .NET مقالًا حول، على سبيل المثال، تحسينات الأداء في .NET 10، فمن المحتمل أن تظهر هذه المقالة في كل من خلاصة DevBlogs الرئيسية والخلاصة الخاصة بـ .NET. في بعض الأحيان قد يظهر في موجز Visual Studio إذا كان يتعلق بميزات IDE.&lt;/p>
&lt;p>إذا قمنا بسذاجة بمعالجة كل مقالة من كل خلاصة، فسينتهي بنا الأمر إلى تحليل ونشر نفس المقالة عدة مرات. سيؤدي ذلك إلى إهدار مكالمات API إلى Azure OpenAI، وإرسال بريد عشوائي إلى Telegram بإشعارات مكررة، وربما إزعاج متابعينا إذا قمنا بنشر نسخ مكررة.الحل هو إلغاء البيانات المكررة المستندة إلى عنوان URL. تحتوي كل مقالة على عنوان URL فريد، لذا يمكننا استخدامه كمعرف. تعتبر بنية بيانات HashSet مثالية لهذا لأنها توفر وقت بحث O(1) وتمنع التكرارات تلقائيًا. عندما نحاول إضافة عنوان URL موجود بالفعل في المجموعة، فإن طريقة الإضافة ترجع ببساطة خطأ، مما يتيح لنا معرفة أنه يجب علينا تخطي هذه المقالة.&lt;/p>
&lt;h3 id="الحالة-المستمرة-مع-تخفيض-السعر">الحالة المستمرة مع تخفيض السعر&lt;/h3>
&lt;p>يعالج إلغاء البيانات المكررة التكرارات في عملية تشغيل واحدة، ولكن ماذا عن عمليات التشغيل المختلفة؟ عندما يتم تشغيل تطبيقنا كل ست ساعات، نحتاج إلى تذكر المقالات التي قمنا بمعالجتها بالفعل حتى لا نتعامل معها مرة أخرى.&lt;/p>
&lt;p>لقد اخترت تخزين هذه الحالة في ملف تخفيض السعر المسمىpost-articles.md. لماذا تخفيض السعر؟ عدة أسباب. أولاً، إنها قابلة للقراءة من قبل الإنسان. يمكنني فتح الملف ورؤية المقالات التي قمت بمشاركتها على الفور. ثانيًا، يتم التحكم في الإصدار. وبما أن هذا الملف موجود في مستودع Git الخاص بنا، فلدي سجل كامل بمواعيد معالجة المقالات. ثالثًا، إنه بمثابة توثيق. يمكن لأي شخص ينظر إلى المستودع أن يرى ما فعله التطبيق.&lt;/p>
&lt;p>تنسيق هذا الملف بسيط. يحتوي على رأس، وطابع زمني يوضح آخر مرة تم فيها تشغيل التطبيق، ثم قائمة بالمقالات بتنسيق رابط تخفيض السعر:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#79c0ff;font-weight:bold"># Posted Articles
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#79c0ff;font-weight:bold">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-style:italic">*Last run: 2025-12-10 15:30:00*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>List of articles posted to LinkedIn:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">-&lt;/span> [&lt;span style="color:#7ee787">Announcing .NET 10&lt;/span>](https://devblogs.microsoft.com/dotnet/announcing-dotnet-10?wt.mc_id=DT-MVP-5004972) - Posted on 2025-12-10 15:30:00 (Published: 2025-12-10)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">-&lt;/span> [&lt;span style="color:#7ee787">Visual Studio 2026 Preview&lt;/span>](https://devblogs.microsoft.com/visualstudio/vs-2026-preview?wt.mc_id=DT-MVP-5004972) - Posted on 2025-12-09 10:15:00 (Published: 2025-12-09)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="تحميل-ملف-التتبع-وتحليله">تحميل ملف التتبع وتحليله&lt;/h3>
&lt;p>للتحقق مما إذا كنا قد قمنا بالفعل بمعالجة مقالة، نحتاج إلى تحميل هذا الملف واستخراج عناوين URL. إليك الوظيفة التي تقوم بذلك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> HashSet&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; LoadPostedArticles(&lt;span style="color:#ff7b72">string&lt;/span> filePath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> postedUrls = &lt;span style="color:#ff7b72">new&lt;/span> HashSet&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!File.Exists(filePath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> postedUrls;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> lines = File.ReadAllLines(filePath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> line &lt;span style="color:#ff7b72">in&lt;/span> lines)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> match = System.Text.RegularExpressions.Regex.Match(line, &lt;span style="color:#a5d6ff">@&amp;#34;\(([^)]+)\)&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (match.Success)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> url = match.Groups[&lt;span style="color:#a5d6ff">1&lt;/span>].Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (url.Contains(&lt;span style="color:#a5d6ff">&amp;#34;?wt.mc_id=&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url = url.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, url.IndexOf(&lt;span style="color:#a5d6ff">&amp;#34;?wt.mc_id=&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (url.Contains(&lt;span style="color:#a5d6ff">&amp;#34;?&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url = url.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, url.IndexOf(&lt;span style="color:#a5d6ff">&amp;#34;?&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url = url.TrimEnd(&lt;span style="color:#a5d6ff">&amp;#39;/&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> postedUrls.Add(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> postedUrls;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تقوم هذه الوظيفة بإرجاع HashSet الذي يحتوي على جميع عناوين URL التي قمنا بمعالجتها بالفعل. نبدأ بالتحقق من وجود الملف - عند التشغيل لأول مرة، لن يكون موجودًا، لذلك نعيد مجموعة فارغة.&lt;/p>
&lt;p>لكل سطر في الملف، نستخدم regex لاستخراج عنوان URL من تنسيق رابط تخفيض السعر. يتطابق التعبير العادي &lt;code>\(([^)]+)\)&lt;/code> مع أي شيء داخل الأقواس، وهو المكان الذي تخزن فيه روابط تخفيض السعر عناوين URL الخاصة بها.&lt;/p>
&lt;p>ثم تأتي خطوة مهمة: تطبيع عنوان URL. يمكن أن تختلف عناوين URL لنفس المقالة في التنسيق. قد يقدم لنا موجز RSS &lt;code>https://devblogs.microsoft.com/dotnet/article&lt;/code>، ولكن نسختنا المحفوظة تحتوي على معلمة تتبع ملحقة: &lt;code>https://devblogs.microsoft.com/dotnet/article?wt.mc_id=DT-MVP-5004972&lt;/code>. تحتوي بعض عناوين URL على خطوط مائلة زائدة، والبعض الآخر لا تحتوي عليها.&lt;/p>
&lt;p>للتعامل مع هذا الأمر، نقوم بإزالة أي معلمات استعلام (كل شيء بعد &lt;code>?&lt;/code>) وإزالة الخطوط المائلة اللاحقة. يضمن هذا التطبيع أننا نتعرف على المقالات على أنها مكررة حتى لو كانت عناوين URL الخاصة بها تختلف بهذه الطرق السطحية.&lt;/p>
&lt;h3 id="حفظ-المقالات-الجديدة">حفظ المقالات الجديدة&lt;/h3>
&lt;p>عندما نعالج مقالة بنجاح، نحتاج إلى إضافتها إلى ملف التتبع الخاص بنا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> SavePostedArticle(&lt;span style="color:#ff7b72">string&lt;/span> filePath, &lt;span style="color:#ff7b72">string&lt;/span> url, &lt;span style="color:#ff7b72">string&lt;/span> title, DateTimeOffset publishDate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> markdownEntry = &lt;span style="color:#a5d6ff">$&amp;#34;- [{title}]({url}) - Posted on {DateTime.Now:yyyy-MM-dd HH:mm:ss} (Published: {publishDate:yyyy-MM-dd})\n&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!File.Exists(filePath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> File.WriteAllText(filePath, &lt;span style="color:#a5d6ff">&amp;#34;# Posted Articles\n\n*Last run: {DateTime.Now:yyyy-MM-dd HH:mm:ss}*\n\nList of articles posted:\n\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> File.AppendAllText(filePath, markdownEntry);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تقوم هذه الوظيفة بإنشاء إدخال بتنسيق تخفيض السعر مع عنوان المقالة كرابط، متبوعًا بطوابع زمنية توضح متى قمنا بنشره ومتى تم نشره في الأصل. إذا لم يكن الملف موجودًا بعد، فإننا نقوم بإنشائه برأس أولاً.&lt;/p>
&lt;h2 id="محرك-تحليل-الذكاء-الاصطناعي">محرك تحليل الذكاء الاصطناعي&lt;/h2>
&lt;h3 id="فهم-النواة-الدلاليةنصل-الآن-إلى-الجزء-الأكثر-إثارة-من-تطبيقنا---تحليل-الذكاء-الاصطناعي-semantic-kernel-هو-sdk-مفتوح-المصدر-من-microsoft-لدمج-نماذج-اللغات-الكبيرة-في-التطبيقات-إنه-أكثر-من-مجرد-غلاف-حول-مكالمات-api-فهو-يوفر-إطارا-لبناء-تطبيقات-الذكاء-الاصطناعي-المتطورة-مع-ميزات-مثل-المكونات-الإضافية-والمخططات-والذاكرة">فهم النواة الدلاليةنصل الآن إلى الجزء الأكثر إثارة من تطبيقنا - تحليل الذكاء الاصطناعي. Semantic Kernel هو SDK مفتوح المصدر من Microsoft لدمج نماذج اللغات الكبيرة في التطبيقات. إنه أكثر من مجرد غلاف حول مكالمات API. فهو يوفر إطارًا لبناء تطبيقات الذكاء الاصطناعي المتطورة مع ميزات مثل المكونات الإضافية والمخططات والذاكرة.&lt;/h3>
&lt;p>بالنسبة لحالة الاستخدام الخاصة بنا، فإننا نستخدم إمكانات إكمال الدردشة الخاصة بـ Semantic Kernel. سنرسل مطالبة إلى Azure OpenAI، وسيقوم النموذج بتحليل مقالتنا وإنشاء استجابة. يتعامل Semantic Kernel مع كل تعقيدات مصادقة واجهة برمجة التطبيقات (API)، وتنسيق الطلب، وتحليل الاستجابة.&lt;/p>
&lt;h3 id="إعداد-محلل-المقالات">إعداد محلل المقالات&lt;/h3>
&lt;p>دعونا نلقي نظرة على كيفية إعداد فئة المحلل لدينا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HtmlAgilityPack&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">VsFeedLinkedin.Services&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ArticleAnalyzer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> Kernel _kernel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IChatCompletionService _chatService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ArticleAnalyzer(&lt;span style="color:#ff7b72">string&lt;/span> endpoint, &lt;span style="color:#ff7b72">string&lt;/span> apiKey, &lt;span style="color:#ff7b72">string&lt;/span> deploymentName)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: deploymentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: endpoint,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: apiKey
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _chatService = _kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يستخدم Semantic Kernel نمط البناء للتكوين. نقوم بإنشاء KernelBuilder، ونضيف خدمة إكمال الدردشة Azure OpenAI الخاصة بنا مع بيانات الاعتماد اللازمة، ثم نبني النواة. من النواة المبنية، نقوم باسترداد واجهة IChatCompletionService، والتي سنستخدمها لإرسال المطالبات وتلقي الاستجابات.&lt;/p>
&lt;p>يأخذ المُنشئ ثلاث معلمات: نقطة نهاية Azure OpenAI (شيء مثل &lt;code>https://your-resource.openai.azure.com/&lt;/code>)، ومفتاح واجهة برمجة التطبيقات (API)، واسم النشر (مثل &lt;code>gpt-4o&lt;/code>). ويتم تمريرها من متغيرات البيئة، مما يحافظ على أمان بيانات الاعتماد الخاصة بنا.&lt;/p>
&lt;h3 id="صياغة-الموجه-المثالي">صياغة الموجه المثالي&lt;/h3>
&lt;p>تعد المطالبة التي نرسلها إلى الذكاء الاصطناعي أمرًا بالغ الأهمية. تنتج المطالبة المصممة جيدًا مخرجات متسقة وعالية الجودة. تؤدي المطالبة الغامضة أو سيئة التنظيم إلى نتائج غير متسقة ومتواضعة. لقد أمضيت وقتًا طويلاً في تكرار هذه المطالبة للحصول على النتائج التي أسعدني:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> You are a professional tech content analyst and LinkedIn content creator.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Analyze the following Microsoft DevBlogs article and create an engaging LinkedIn post.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Article Title: {title}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Author: {author}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> URL: {url}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> Tags: {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, tags)}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Article Content:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {cleanContent}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Please provide:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">1.&lt;/span> A brief summary (&lt;span style="color:#a5d6ff">2&lt;/span>-&lt;span style="color:#a5d6ff">3&lt;/span> sentences) of the key points
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">2.&lt;/span> The main technologies or topics covered
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">3.&lt;/span> Why &lt;span style="color:#ff7b72">this&lt;/span> &lt;span style="color:#ff7b72">is&lt;/span> relevant &lt;span style="color:#ff7b72">for&lt;/span> developers/tech professionals
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">4.&lt;/span> An engaging LinkedIn post (max &lt;span style="color:#a5d6ff">1300&lt;/span> characters) that:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Starts with a hook or attention-grabbing statement
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Highlights the key &lt;span style="color:#ff7b72">value&lt;/span> &lt;span style="color:#ff7b72">for&lt;/span> readers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Includes a call to action
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Uses appropriate emojis (but not too many)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Maintains a professional yet approachable tone
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - DO NOT include hashtags &lt;span style="color:#ff7b72">in&lt;/span> the post (they will be added separately)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - DO NOT include the URL &lt;span style="color:#ff7b72">in&lt;/span> the post (it will be added separately)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Format your response &lt;span style="color:#ff7b72">as&lt;/span> follows:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> Summary
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Your summary here]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> Key Topics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [List of main topics/technologies]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> Relevance
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Why this matters]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">##&lt;/span> LinkedIn Post
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Your engaging LinkedIn post here]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>اسمحوا لي أن أشرح قرارات التصميم هنا. نبدأ بإعطاء الذكاء الاصطناعي دورًا واضحًا: &amp;ldquo;أنت محلل محتوى تقني محترف ومنشئ محتوى على LinkedIn&amp;rdquo;. يؤدي هذا إلى إعداد النموذج للاستجابة بالأسلوب والصوت المناسبين.&lt;/p>
&lt;p>نحن نقدم كل السياق الذي يحتاجه الذكاء الاصطناعي: عنوان المقالة، والمؤلف، وعنوان URL، والعلامات من موجز RSS، ومحتوى المقالة بالكامل. كلما زاد السياق الذي نقدمه، كلما كان التحليل أفضل.&lt;/p>
&lt;p>ثم نحدد بالضبط ما نريده مرة أخرى. أطلب أربعة أشياء: الملخص، والموضوعات الرئيسية، وشرح الصلة، ومنشور LinkedIn. بالنسبة لمنشور LinkedIn على وجه التحديد، أقدم تعليمات تفصيلية حول ما يجعل المنشور جيدًا - يجب أن يكون له تأثير، وقيمة مميزة، ويتضمن عبارة تحث المستخدم على اتخاذ إجراء، ويستخدم الرموز التعبيرية بشكل مناسب، ويحافظ على نبرة احترافية.&lt;/p>
&lt;p>التعليمات السلبية لها نفس القدر من الأهمية. أخبر الذكاء الاصطناعي صراحةً بعدم تضمين علامات التصنيف أو عنوان URL في المنشور. لماذا؟ لأنني أضفتها بشكل منفصل، وإذا شملها الذكاء الاصطناعي، فسيكون لدي نسخ مكررة. هذا النوع من التعليمات الصريحة يمنع الأخطاء الشائعة.&lt;/p>
&lt;p>وأخيرا، أحدد تنسيق الإخراج الدقيق. من خلال السؤال عن الأقسام المميزة برؤوس ##، أجعل من السهل تحليل الاستجابة برمجيًا. الذكاء الاصطناعي جيد جدًا في اتباع تعليمات التنسيق، وهذا الاتساق يجعل كود التحليل الخاص بنا أبسط وأكثر موثوقية.&lt;/p>
&lt;h3 id="تنفيذ-التحليل">تنفيذ التحليل&lt;/h3>
&lt;p>إليك كيفية تجميع كل ذلك معًا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ArticleAnalysis&amp;gt; AnalyzeArticleAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> url,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> htmlContent,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> author,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; tags)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> cleanContent = ExtractTextFromHtml(htmlContent);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (cleanContent.Length &amp;gt; &lt;span style="color:#a5d6ff">8000&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleanContent = cleanContent.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">8000&lt;/span>) + &lt;span style="color:#a5d6ff">&amp;#34;...&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> chatHistory = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chatHistory.AddUserMessage(prompt);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _chatService.GetChatMessageContentAsync(chatHistory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> responseText = response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ParseAnalysisResponse(responseText, title, url, author, tags);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```نقوم&lt;/span> &lt;span style="color:#f85149">أولاً&lt;/span> &lt;span style="color:#f85149">باستخراج&lt;/span> &lt;span style="color:#f85149">النص&lt;/span> &lt;span style="color:#f85149">النظيف&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">محتوى&lt;/span> HTML (&lt;span style="color:#f85149">سأشرح&lt;/span> &lt;span style="color:#f85149">ذلك&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">القسم&lt;/span> &lt;span style="color:#f85149">التالي&lt;/span>). &lt;span style="color:#f85149">ثم&lt;/span> &lt;span style="color:#f85149">نقوم&lt;/span> &lt;span style="color:#f85149">باقتطاع&lt;/span> &lt;span style="color:#f85149">المحتوى&lt;/span> &lt;span style="color:#f85149">إذا&lt;/span> &lt;span style="color:#f85149">كان&lt;/span> &lt;span style="color:#f85149">طويلاً&lt;/span> &lt;span style="color:#f85149">جدًا&lt;/span>. &lt;span style="color:#f85149">نماذج&lt;/span> &lt;span style="color:#f85149">اللغات&lt;/span> &lt;span style="color:#f85149">الكبيرة&lt;/span> &lt;span style="color:#f85149">لها&lt;/span> &lt;span style="color:#f85149">حدود&lt;/span> &lt;span style="color:#f85149">رمزية،&lt;/span> &lt;span style="color:#f85149">وقد&lt;/span> &lt;span style="color:#f85149">تتجاوزها&lt;/span> &lt;span style="color:#f85149">المقالات&lt;/span> &lt;span style="color:#f85149">الطويلة&lt;/span> &lt;span style="color:#f85149">جدًا&lt;/span>. &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">خلال&lt;/span> &lt;span style="color:#f85149">الحد&lt;/span> &lt;span style="color:#f85149">الأقصى&lt;/span> &lt;span style="color:#f85149">لعدد&lt;/span> &lt;span style="color:#f85149">الأحرف&lt;/span> &lt;span style="color:#f85149">وهو&lt;/span> &lt;span style="color:#a5d6ff">8000&lt;/span> &lt;span style="color:#f85149">حرف،&lt;/span> &lt;span style="color:#f85149">فإننا&lt;/span> &lt;span style="color:#f85149">نضمن&lt;/span> &lt;span style="color:#f85149">البقاء&lt;/span> &lt;span style="color:#f85149">ضمن&lt;/span> &lt;span style="color:#f85149">الحدود&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">الاستمرار&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">توفير&lt;/span> &lt;span style="color:#f85149">سياق&lt;/span> &lt;span style="color:#f85149">جوهري&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">نقوم&lt;/span> &lt;span style="color:#f85149">بإنشاء&lt;/span> &lt;span style="color:#f85149">كائن&lt;/span> ChatHistory &lt;span style="color:#f85149">ونضيف&lt;/span> &lt;span style="color:#f85149">مطالبتنا&lt;/span> &lt;span style="color:#f85149">كرسالة&lt;/span> &lt;span style="color:#f85149">مستخدم&lt;/span>. &lt;span style="color:#f85149">هذا&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">مفهوم&lt;/span> Semantic Kernel &lt;span style="color:#f85149">للتفاعلات&lt;/span> &lt;span style="color:#f85149">القائمة&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">الدردشة&lt;/span>. &lt;span style="color:#f85149">نرسل&lt;/span> &lt;span style="color:#f85149">هذا&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">خدمة&lt;/span> &lt;span style="color:#f85149">إكمال&lt;/span> &lt;span style="color:#f85149">الدردشة&lt;/span> &lt;span style="color:#f85149">ونحصل&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">الرد&lt;/span>. &lt;span style="color:#f85149">وأخيرا،&lt;/span> &lt;span style="color:#f85149">نقوم&lt;/span> &lt;span style="color:#f85149">بتحليل&lt;/span> &lt;span style="color:#f85149">الاستجابة&lt;/span> &lt;span style="color:#f85149">لاستخراج&lt;/span> &lt;span style="color:#f85149">الأقسام&lt;/span> &lt;span style="color:#f85149">الفردية&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> &lt;span style="color:#f85149">تحليل&lt;/span> &lt;span style="color:#f85149">استجابة&lt;/span> &lt;span style="color:#f85149">الذكاء&lt;/span> &lt;span style="color:#f85149">الاصطناعي&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">يقوم&lt;/span> &lt;span style="color:#f85149">الذكاء&lt;/span> &lt;span style="color:#f85149">الاصطناعي&lt;/span> &lt;span style="color:#f85149">بإرجاع&lt;/span> &lt;span style="color:#f85149">استجابته&lt;/span> &lt;span style="color:#f85149">كنص&lt;/span> &lt;span style="color:#f85149">منسق&lt;/span> &lt;span style="color:#f85149">بالبنية&lt;/span> &lt;span style="color:#f85149">المطلوبة&lt;/span>. &lt;span style="color:#f85149">نحن&lt;/span> &lt;span style="color:#f85149">بحاجة&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">تحليل&lt;/span> &lt;span style="color:#f85149">هذا&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الحقول&lt;/span> &lt;span style="color:#f85149">الفردية&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> ArticleAnalysis ParseAnalysisResponse(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> response,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> url,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> author,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; tags)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> analysis = &lt;span style="color:#ff7b72">new&lt;/span> ArticleAnalysis
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Title = title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Url = url,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Author = author,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Tags = tags,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RawAnalysis = response
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sections = response.Split(&lt;span style="color:#a5d6ff">&amp;#34;##&amp;#34;&lt;/span>, StringSplitOptions.RemoveEmptyEntries);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> section &lt;span style="color:#ff7b72">in&lt;/span> sections)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> lines = section.Trim().Split(&lt;span style="color:#a5d6ff">&amp;#39;\n&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">2&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (lines.Length &amp;lt; &lt;span style="color:#a5d6ff">2&lt;/span>) &lt;span style="color:#ff7b72">continue&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sectionTitle = lines[&lt;span style="color:#a5d6ff">0&lt;/span>].Trim().ToLower();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sectionContent = lines[&lt;span style="color:#a5d6ff">1&lt;/span>].Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (sectionTitle)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;summary&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.Summary = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;key topics&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.KeyTopics = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;relevance&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.Relevance = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;linkedin post&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analysis.LinkedInPost = sectionContent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> analysis;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>قمنا بتقسيم الاستجابة حسب علامات &lt;code>##&lt;/code>، مما يعطينا كل قسم. لكل قسم، نقوم بتقسيمه بواسطة سطر جديد لفصل الرأس عن المحتوى. نستخدم بعد ذلك عبارة التبديل لتعيين محتوى كل قسم إلى الخاصية المناسبة.&lt;/p>
&lt;p>نقوم أيضًا بتخزين الاستجابة الأولية غير المحللة. وهذا مفيد لتصحيح الأخطاء – إذا حدث خطأ ما في التحليل، فيمكننا أن ننظر إلى ما أعاده الذكاء الاصطناعي بالفعل.&lt;/p>
&lt;h2 id="استخراج-المحتوى-من-html">استخراج المحتوى من HTML&lt;/h2>
&lt;h3 id="لماذا-نحتاج-إلى-تنظيف-html">لماذا نحتاج إلى تنظيف HTML&lt;/h3>
&lt;p>عندما نجلب مقالة من إحدى المدونات، نحصل على HTML الكامل للصفحة. يتضمن ذلك أكثر من مجرد محتوى المقالة - فهناك قوائم التنقل، والترويسات، والتذييلات، والأشرطة الجانبية، وعناصر واجهة المقالة ذات الصلة، وأقسام التعليقات، والبرامج النصية للتحليلات والتتبع، وأوراق الأنماط، وجميع أنواع العناصر الأخرى.&lt;/p>
&lt;p>إذا أرسلنا كل هذا إلى الذكاء الاصطناعي لدينا، فسوف تحدث العديد من الأشياء السيئة. سيتعين على الذكاء الاصطناعي معالجة الكثير من النصوص غير ذات الصلة، مما يؤدي إلى إهدار الرموز المميزة وربما إرباك التحليل. قد يتم تضمين نص التنقل والتذييل في الملخص. سيتم التعامل مع البرامج النصية وCSS كمحتوى، مما يزيد من تلويث التحليل.&lt;/p>
&lt;p>نحن بحاجة إلى استخراج محتوى المقالة فقط – الجزء الذي سيقرأه القارئ البشري بالفعل.&lt;/p>
&lt;h3 id="استخدام-htmlagilitypack">استخدام HtmlAgilityPack&lt;/h3>
&lt;p>HtmlAgilityPack عبارة عن مكتبة تحليل HTML قوية لـ .NET. على عكس XML، غالبًا ما يكون HTML مشوهًا - فقد لا يتم إغلاق العلامات بشكل صحيح، وقد لا يتم اقتباس السمات بشكل صحيح. يتعامل HtmlAgilityPack مع كل هذا بأمان، مما يمنحنا بنية تشبه DOM يمكننا الاستعلام عنها ومعالجتها.&lt;/p>
&lt;p>ها هي وظيفة الاستخراج لدينا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> ExtractTextFromHtml(&lt;span style="color:#ff7b72">string&lt;/span> html)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(html))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> doc = &lt;span style="color:#ff7b72">new&lt;/span> HtmlDocument();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> doc.LoadHtml(html);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> nodesToRemove = doc.DocumentNode.SelectNodes(&lt;span style="color:#a5d6ff">&amp;#34;//script|//style|//nav|//footer|//header&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (nodesToRemove != &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> node &lt;span style="color:#ff7b72">in&lt;/span> nodesToRemove)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> node.Remove();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> text = doc.DocumentNode.InnerText;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text = System.Text.RegularExpressions.Regex.Replace(text, &lt;span style="color:#a5d6ff">@&amp;#34;\s+&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34; &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> text.Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نقوم بتحميل HTML إلى مستند HtmlDocument، والذي يقوم بتوزيعه إلى بنية شجرة. ثم نستخدم XPath لتحديد جميع العقد التي نريد إزالتها. يحدد تعبير XPath &lt;code>//script|//style|//nav|//footer|//header&lt;/code> جميع عناصر البرنامج النصي (لا نحتاج إلى كود JavaScript)، وعناصر النمط (لا نحتاج إلى CSS)، وعناصر التنقل (قوائم التنقل)، وعناصر التذييل، وعناصر الرأس.&lt;/p>
&lt;p>بعد إزالة هذه العقد، نحصل على خاصية InnerText، التي تستخرج كل محتوى النص أثناء تجريد علامات HTML. وهذا يعطينا النص الواضح للمقالة.وأخيرا، نقوم بتنظيف المساحة البيضاء. غالبًا ما يحتوي HTML على الكثير من المسافات البيضاء الإضافية لأغراض التنسيق - مسافات متعددة وعلامات تبويب وأسطر جديدة. نستخدم التعبير العادي لاستبدال أي تسلسل من أحرف المسافات البيضاء بمسافة واحدة، ثم نقوم بقص النتيجة.&lt;/p>
&lt;h3 id="جلب-المقالة-كاملة">جلب المقالة كاملة&lt;/h3>
&lt;p>يقدم لنا موجز RSS ملخصات فقط، وليس محتوى المقالة بالكامل. للحصول على النص الكامل، نحتاج إلى جلب صفحة الويب الخاصة بالمقالة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; FetchArticleContentAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> httpClient.DefaultRequestHeaders.Add(&lt;span style="color:#a5d6ff">&amp;#34;User-Agent&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;VsFeedLinkedin/1.0&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetStringAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;⚠️ Failed to fetch article content: {ex.Message}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا أمر واضح ومباشر – نقوم بإرسال طلب HTTP GET إلى عنوان URL للمقالة ونعيد استجابة HTML. نقوم بلفها في محاولة التقاط لأن طلبات الشبكة يمكن أن تفشل، ونفضل إرجاع سلسلة فارغة بدلاً من تعطل التطبيق بأكمله.&lt;/p>
&lt;h2 id="إنشاء-وثائق-دائمة">إنشاء وثائق دائمة&lt;/h2>
&lt;h3 id="لماذا-يتم-إنشاء-ملفات-markdown">لماذا يتم إنشاء ملفات Markdown&lt;/h3>
&lt;p>في كل مرة نقوم بتحليل مقال ما، نقوم بإنشاء ملف تخفيض تفصيلي يوثق هذا التحليل. وهذا يخدم عدة أغراض.&lt;/p>
&lt;p>أولاً، يقوم بإنشاء أرشيف قابل للبحث. وبمرور الوقت، ستتمكن من إنشاء مجموعة من المقالات التي تم تحليلها. يمكنك البحث في هذه الملفات للعثور على المحتوى السابق حول موضوعات محددة.&lt;/p>
&lt;p>ثانياً، توفر الشفافية. يمكنك أن ترى بالضبط ما تم إنشاؤه بواسطة الذكاء الاصطناعي لكل مقالة، بما في ذلك التحليل الكامل ومنشور LinkedIn.&lt;/p>
&lt;p>ثالثًا، إنه مفيد لتصحيح الأخطاء. إذا حدث خطأ ما في إحدى المشاركات، فيمكنك إلقاء نظرة على ملف تخفيض السعر لفهم ما حدث.&lt;/p>
&lt;h3 id="فئة-مولد-تخفيض-السعر">فئة مولد تخفيض السعر&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">MarkdownGenerator&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _outputDirectory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> MarkdownGenerator(&lt;span style="color:#ff7b72">string&lt;/span> outputDirectory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _outputDirectory = outputDirectory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Directory.Exists(_outputDirectory))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Directory.CreateDirectory(_outputDirectory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GenerateMarkdownFile(ArticleAnalysis analysis)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sb = &lt;span style="color:#ff7b72">new&lt;/span> StringBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> safeTitle = GenerateSafeFileName(analysis.Title);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> fileName = &lt;span style="color:#a5d6ff">$&amp;#34;{analysis.AnalyzedAt:yyyy-MM-dd}_{safeTitle}.md&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> filePath = Path.Combine(_outputDirectory, fileName);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;# {analysis.Title}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;## Article Information&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Author:** {analysis.Author}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **URL:** [{analysis.Url}]({analysis.Url})&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Published:** {analysis.PublishDate:yyyy-MM-dd}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Analyzed:** {analysis.AnalyzedAt:yyyy-MM-dd HH:mm:ss}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">$&amp;#34;- **Tags:** {string.Join(&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;, analysis.Tags)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;## AI Analysis&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;### Summary&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(analysis.Summary);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;### Key Topics&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(analysis.KeyTopics);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;### Relevance for Developers&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(analysis.Relevance);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;## Generated LinkedIn Post&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;```&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(ana Analysis.LinkedInPost);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;```&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.AppendLine(&lt;span style="color:#a5d6ff">&amp;#34;*This analysis was generated using AI (Semantic Kernel with Azure OpenAI)*&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> File.WriteAllText(filePath, sb.ToString());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> filePath;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يأخذ المنشئ مسار دليل الإخراج ويقوم بإنشائه إذا لم يكن موجودًا. تأخذ طريقة GenerateMarkdownFile كائن ArticleAnalogy وتنتج مستند تخفيض السعر منسق بشكل جيد.&lt;/p>
&lt;p>يتضمن اسم الملف التاريخ ونسخة منقحة من العنوان. وهذا يجعل من السهل فرز الملفات بترتيب زمني والتعرف عليها في لمحة.&lt;/p>
&lt;h3 id="التعامل-مع-أسماء-الملفات-غير-الآمنة">التعامل مع أسماء الملفات غير الآمنة&lt;/h3>
&lt;p>يمكن أن تحتوي عناوين المقالات على أحرف غير مسموح بها في أسماء الملفات - أشياء مثل النقطتين والشرطات المائلة وعلامات الاستفهام وعلامات الاقتباس. نحن بحاجة إلى تعقيم هذه:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GenerateSafeFileName(&lt;span style="color:#ff7b72">string&lt;/span> title)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> invalidChars = Path.GetInvalidFileNameChars();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> safeTitle = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>(title
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(c =&amp;gt; !invalidChars.Contains(c))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToArray());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> safeTitle = safeTitle.Replace(&lt;span style="color:#a5d6ff">&amp;#34; &amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>).Replace(&lt;span style="color:#a5d6ff">&amp;#34;--&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (safeTitle.Length &amp;gt; &lt;span style="color:#a5d6ff">50&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> safeTitle = safeTitle.Substring(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">50&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> safeTitle.TrimEnd(&lt;span style="color:#a5d6ff">&amp;#39;-&amp;#39;&lt;/span>).ToLowerInvariant();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نستخدم Path.GetInvalidFileNameChars() للحصول على قائمة بالأحرف التي لا يمكن أن تظهر في أسماء الملفات على نظام التشغيل الحالي. نقوم بتصفية هذه العناصر، واستبدال المسافات بواصلات لتسهيل القراءة، وتحديد الطول بـ 50 حرفًا، وتحويلها إلى أحرف صغيرة لتحقيق الاتساق.&lt;/p>
&lt;h2 id="إعداد-إشعارات-تيليجرام">إعداد إشعارات تيليجرام&lt;/h2>
&lt;h3 id="لماذا-اخترت-تيليجرام">لماذا اخترت تيليجرام&lt;/h3>
&lt;p>بالنسبة لعنصر الإشعارات، فكرت في عدة خيارات – البريد الإلكتروني، والرسائل النصية القصيرة، وSlack، وDiscord، وTelegram. لقد اخترت Telegram في النهاية لعدة أسباب.&lt;/p>
&lt;p>واجهة برمجة التطبيقات (API) مجانية تمامًا ولا توجد حدود للمعدلات للاستخدام المعقول. تفرض العديد من خدمات الإشعارات حدودًا على عدد الرسائل التي يمكنك إرسالها مجانًا، لكن Telegram لا يقيد رسائل الروبوت للمستخدمين الفرديين.&lt;/p>
&lt;p>واجهة برمجة تطبيقات الروبوت بسيطة بشكل لا يصدق. إنها مجرد طلبات HTTP مع حمولات JSON. لا توجد تدفقات مصادقة معقدة، ولا يلزم وجود خطافات ويب للوظائف الأساسية.يعمل Telegram في كل مكان – على هاتفي، وعلى سطح المكتب، وفي متصفح الويب. يمكنني تلقي الإخطارات أينما كنت والرد عليها على الفور.&lt;/p>
&lt;p>تدعم الرسائل التنسيق الغني. يمكنني استخدام النص الغامق والخط المائل وحتى كتل التعليمات البرمجية لجعل إشعاراتي أكثر قابلية للقراءة.&lt;/p>
&lt;h3 id="إنشاء-بوت-telegram-الخاص-بك">إنشاء بوت Telegram الخاص بك&lt;/h3>
&lt;p>يعد إعداد روبوت Telegram أمرًا سهلاً بشكل مدهش. افتح Telegram وابحث عن @BotFather – هذا هو روبوت Telegram الرسمي لإنشاء الروبوتات وإدارتها. ابدأ محادثة مع BotFather وأرسل الأمر /newbot. سيطلب منك BotFather اسمًا لروبوتك (هذا هو اسم العرض) واسم مستخدم (يجب أن يكون فريدًا وينتهي بـ &amp;ldquo;bot&amp;rdquo;). بمجرد تقديم هذه العناصر، سيقوم BotFather بإنشاء الروبوت الخاص بك ويمنحك رمزًا مميزًا لواجهة برمجة التطبيقات. هذا الرمز المميز يشبه كلمة المرور - احتفظ به سرًا ولا تضعه في المستودعات العامة.&lt;/p>
&lt;p>للعثور على معرف الدردشة الخاص بك حتى يعرف الروبوت مكان إرسال الرسائل، ابدأ محادثة مع الروبوت الجديد الخاص بك عن طريق البحث عنه والضغط على ابدأ. ثم قم بالوصول إلى عنوان URL &lt;code>https://api.telegram.org/bot&amp;lt;YOUR_TOKEN&amp;gt;/getUpdates&lt;/code> في متصفحك أو باستخدام حليقة. ابحث عن كائن &lt;code>chat&lt;/code> في الرد - الحقل &lt;code>id&lt;/code> هو معرف الدردشة الخاص بك.&lt;/p>
&lt;h3 id="إرسال-الرسائل-عبر-api">إرسال الرسائل عبر API&lt;/h3>
&lt;p>هذه هي وظيفتنا لإرسال رسائل Telegram:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task SendToTelegramAsync(&lt;span style="color:#ff7b72">string&lt;/span> botToken, &lt;span style="color:#ff7b72">string&lt;/span> chatId, &lt;span style="color:#ff7b72">string&lt;/span> message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> telegramApiUrl = &lt;span style="color:#a5d6ff">$&amp;#34;https://api.telegram.org/bot{botToken}/sendMessage&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> payload = &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> chat_id = chatId,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text = message,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parse_mode = &lt;span style="color:#a5d6ff">&amp;#34;HTML&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> jsonContent = JsonSerializer.Serialize(payload);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> content = &lt;span style="color:#ff7b72">new&lt;/span> StringContent(jsonContent, Encoding.UTF8, &lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.PostAsync(telegramApiUrl, content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!response.IsSuccessStatusCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> errorContent = &lt;span style="color:#ff7b72">await&lt;/span> response.Content.ReadAsStringAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">$&amp;#34;Telegram API error: {response.StatusCode} - {errorContent}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تعتمد Telegram Bot API على REST. نقوم بإجراء طلب POST إلى نقطة نهاية sendMessage باستخدام نص JSON يحتوي على معرف الدردشة (مكان الإرسال)، ونص الرسالة (ما يجب إرساله)، ووضع التحليل اختياريًا (للتنسيق).&lt;/p>
&lt;p>يتيح لنا تعيين parse_mode على &amp;ldquo;HTML&amp;rdquo; استخدام علامات HTML الأساسية في رسائلنا - أشياء مثل &lt;code>&amp;lt;b&amp;gt;bold&amp;lt;/b&amp;gt;&lt;/code> و&lt;code>&amp;lt;i&amp;gt;italic&amp;lt;/i&amp;gt;&lt;/code>. وهذا يمكن أن يجعل الإشعارات أكثر قابلية للقراءة، على الرغم من أننا نرسل نصًا عاديًا في حالة الاستخدام الحالية لدينا.&lt;/p>
&lt;p>إذا فشل الطلب، فإننا نطرح استثناءً يتضمن تفاصيل حول الخطأ الذي حدث. يساعد هذا في تصحيح الأخطاء إذا كان هناك شيء لا يعمل.&lt;/p>
&lt;h2 id="تكوين-التطبيق">تكوين التطبيق&lt;/h2>
&lt;h3 id="متغيرات-البيئة">متغيرات البيئة&lt;/h3>
&lt;p>يحتاج تطبيقنا إلى عدة أجزاء من المعلومات الحساسة - مفاتيح واجهة برمجة التطبيقات، ورموز الروبوت، وعناوين URL لنقطة النهاية. لا ينبغي لنا أبدًا أن نبرمجها أو نلزمها بالتحكم في الإصدار. وبدلاً من ذلك، نستخدم متغيرات البيئة، والتي يمكن تعيينها بشكل آمن في كل بيئة يتم فيها تشغيل التطبيق.&lt;/p>
&lt;p>بالنسبة إلى Telegram، نحتاج إلى TELEGRAM_BOT_TOKEN (الرمز المميز الذي قدمه لك BotFather) وTELEGRAM_CHAT_ID (معرف الدردشة الخاص بك حيث يجب إرسال الرسائل).&lt;/p>
&lt;p>بالنسبة إلى Azure OpenAI، نحتاج إلى AZURE_OPENAI_ENDPOINT (عنوان URL لموردك)، وAZURE_OPENAI_API_KEY (مفتاح API الخاص بك)، وAZURE_OPENAI_DEPLOYMENT (اسم النموذج الذي تم نشره، مثل &amp;ldquo;gpt-4o&amp;rdquo;).&lt;/p>
&lt;h3 id="تحميل-التكوين-في-التعليمات-البرمجية">تحميل التكوين في التعليمات البرمجية&lt;/h3>
&lt;p>إليك كيفية تحميل هذه القيم عند بدء تشغيل التطبيق:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> telegramBotToken = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;TELEGRAM_BOT_TOKEN&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> telegramChatId = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;TELEGRAM_CHAT_ID&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> azureOpenAiEndpoint = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_ENDPOINT&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> azureOpenAiKey = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_API_KEY&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> azureOpenAiDeployment = Environment.GetEnvironmentVariable(&lt;span style="color:#a5d6ff">&amp;#34;AZURE_OPENAI_DEPLOYMENT&amp;#34;&lt;/span>) ?? &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> aiAnalysisEnabled = !&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(azureOpenAiEndpoint) &amp;amp;&amp;amp;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> !&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(azureOpenAiKey);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نحن نستخدم Environment.GetEnvironmentVariable لقراءة كل قيمة. بالنسبة لاسم النشر، نقدم الإعداد الافتراضي &amp;ldquo;gpt-4o&amp;rdquo; إذا لم يتم تعيين أي قيمة.&lt;/p>
&lt;p>نتحقق بعد ذلك مما إذا كان ينبغي تمكين تحليل الذكاء الاصطناعي من خلال التحقق من أن لدينا نقطة نهاية ومفتاح واجهة برمجة التطبيقات. يسمح هذا للتطبيق بالعمل في وضع منخفض إذا لم يتم تكوين Azure OpenAI - فسيستمر في جلب الخلاصات وتتبع المقالات، فقط بدون تحليل الذكاء الاصطناعي.### تدهور رشيق&lt;/p>
&lt;p>مفهوم التدهور الرشيق هذا مهم. لا نريد أن يتعطل التطبيق لمجرد عدم تكوين ميزة اختيارية واحدة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ArticleAnalyzer? articleAnalyzer = &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MarkdownGenerator? markdownGenerator = &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (aiAnalysisEnabled)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;🤖 AI Analysis enabled - Using Azure OpenAI with Semantic Kernel&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> articleAnalyzer = &lt;span style="color:#ff7b72">new&lt;/span> ArticleAnalyzer(azureOpenAiEndpoint!, azureOpenAiKey!, azureOpenAiDeployment);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> markdownGenerator = &lt;span style="color:#ff7b72">new&lt;/span> MarkdownGenerator(articlesOutputDir);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;ℹ️ AI Analysis disabled - Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY to enable&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إذا تم تمكين الذكاء الاصطناعي، فإننا نقوم بإنشاء المحلل ومولد تخفيض السعر. إذا لم يكن الأمر كذلك، فإننا نتركها فارغة ونتخطى الخطوات المتعلقة بالذكاء الاصطناعي أثناء المعالجة. لا يزال التطبيق يوفر قيمة من خلال جلب الخلاصات وإرسال الإشعارات الأساسية، حتى بدون تحسين الذكاء الاصطناعي.&lt;/p>
&lt;h2 id="الأتمتة-باستخدام-إجراءات-github">الأتمتة باستخدام إجراءات GitHub&lt;/h2>
&lt;h3 id="لماذا-إجراءات-جيثب">لماذا إجراءات جيثب&lt;/h3>
&lt;p>القوة الحقيقية لهذا الحل تأتي من الأتمتة. لا نريد تشغيل التطبيق يدويًا كل بضع ساعات، بل نريد تشغيله تلقائيًا في الخلفية.&lt;/p>
&lt;p>إجراءات GitHub مثالية لهذا الغرض. إنه مدمج في GitHub، لذا لا توجد خدمة إضافية لإعدادها. إنه مجاني للمستودعات العامة ويتضمن دقائق مجانية سخية للمستودعات الخاصة. ويمكن تشغيله وفقًا لجدول زمني، مما يؤدي إلى تشغيل تطبيقنا على فترات منتظمة. يحتوي على إدارة أسرار مدمجة لتخزين مفاتيح API الخاصة بنا بشكل آمن. ويمكنه إجراء التغييرات مرة أخرى على المستودع، مما يحافظ على تحديث ملف التتبع الخاص بنا.&lt;/p>
&lt;h3 id="ملف-سير-العمل">ملف سير العمل&lt;/h3>
&lt;p>قم بإنشاء ملف على .github/workflows/fetch-and-notify.yml بالمحتوى التالي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Fetch DevBlogs and Notify&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">on&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">schedule&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">cron&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#39;0 */6 * * *&amp;#39;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">workflow_dispatch&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">jobs&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">fetch-and-notify&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">runs-on&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">ubuntu-latest&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">steps&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Checkout repository&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/checkout@v4&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Setup .NET&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/setup-dotnet@v4&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">with&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">dotnet-version&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#39;9.0.x&amp;#39;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Restore dependencies&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">dotnet restore src/VsFeedLinkedin.csproj&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Build&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">dotnet build src/VsFeedLinkedin.csproj --no-restore&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Run application&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">env&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">TELEGRAM_BOT_TOKEN&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.TELEGRAM_BOT_TOKEN }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">TELEGRAM_CHAT_ID&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.TELEGRAM_CHAT_ID }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">AZURE_OPENAI_ENDPOINT&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.AZURE_OPENAI_ENDPOINT }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">AZURE_OPENAI_API_KEY&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.AZURE_OPENAI_API_KEY }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">AZURE_OPENAI_DEPLOYMENT&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">${{ secrets.AZURE_OPENAI_DEPLOYMENT }}&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">dotnet run --project src/VsFeedLinkedin.csproj&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Commit and push changes&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>|&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git config user.name &amp;#34;GitHub Actions Bot&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git config user.email &amp;#34;actions@github.com&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> if [[ -n $(git status --porcelain posted-articles.md generated-posts/) ]]; then
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> TIMESTAMP=$(date +%Y%m%d_%H%M%S)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git add posted-articles.md generated-posts/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git commit -m &amp;#34;chore($TIMESTAMP): processed new articles&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> git push
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> else
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> echo &amp;#34;No changes to commit&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> fi&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>اسمحوا لي أن أشرح كل جزء. يحدد القسم &amp;ldquo;وقت تشغيل سير العمل&amp;rdquo;. يستخدم مشغل الجدول الزمني صيغة cron - &lt;code>0 */6 * * *&lt;/code> تعني &amp;ldquo;في الدقيقة 0 من كل 6 ساعات.&amp;rdquo; لذلك يتم تشغيل سير العمل في منتصف الليل، والساعة 6 صباحًا، والظهيرة، والساعة 6 مساءً بالتوقيت العالمي المنسق. يسمح مشغل Workflow_dispatch بالتشغيل اليدوي من واجهة مستخدم GitHub، وهو أمر مفيد للاختبار.&lt;/p>
&lt;p>تعمل المهمة على نظام التشغيل ubuntu-latest، وهو جهاز Linux افتراضي. نحن نتحقق من مستودعنا، ونقوم بإعداد .NET 9، ونستعيد حزم NuGet، ونبني المشروع.&lt;/p>
&lt;p>خطوة تشغيل التطبيق هي المكان الذي يحدث فيه السحر. نقوم بتمرير أسرارنا كمتغيرات بيئة باستخدام بناء الجملة ${{ Secrets.SECRET_NAME }}. يتم تخزين هذه الأسرار بشكل آمن في GitHub ولا يتم كشفها أبدًا في السجلات.&lt;/p>
&lt;p>وأخيرًا، نلتزم بأي تغييرات مرة أخرى في المستودع. نقوم بتكوين Git بهوية الروبوت، والتحقق مما إذا كانت هناك أي تغييرات على ملف التتبع الخاص بنا أو دليل المنشورات التي تم إنشاؤها، وإذا كان الأمر كذلك، فقم بإنشاء التزام ودفعه.&lt;/p>
&lt;h3 id="إعداد-الأسرار">إعداد الأسرار&lt;/h3>
&lt;p>لإضافة أسرار إلى مستودع GitHub الخاص بك، انتقل إلى إعدادات المستودع الخاص بك، ثم الأسرار والمتغيرات، ثم الإجراءات. انقر فوق &amp;ldquo;سر المستودع الجديد&amp;rdquo; وأضف كل متغير من متغيرات البيئة الخاصة بك. يجب أن تتطابق الأسماء تمامًا مع ما نشير إليه في ملف سير العمل.&lt;/p>
&lt;h2 id="الختام">الختام&lt;/h2>
&lt;h3 id="ما-قمنا-ببنائه">ما قمنا ببنائه&lt;/h3>
&lt;p>بالنظر إلى كل ما قمنا بتغطيته، قمنا ببناء مجمع موجز RSS شامل ومدعوم بالذكاء الاصطناعي والذي يعمل على أتمتة ما كان في السابق عملية يدوية شاقة. يراقب التطبيق سبعة خلاصات لـ Microsoft DevBlogs تلقائيًا، ويلتقط كل مقالة جديدة بمجرد نشرها. فهو يتعامل مع تعقيدات إلغاء البيانات المكررة، ويتعرف على وقت ظهور المقالة نفسها في خلاصات متعددة.يقوم تحليل الذكاء الاصطناعي المدعوم من Semantic Kernel وAzure OpenAI بقراءة محتوى المقالة وفهمه، وإنشاء ملخصات، وتحديد الموضوعات الرئيسية، وشرح مدى صلتها بالموضوع - كل ذلك تلقائيًا. والأهم من ذلك، أنه ينشئ منشورات جذابة على LinkedIn يمكنني مشاركتها مع الحد الأدنى من التحرير.&lt;/p>
&lt;p>يعني تكامل Telegram أنني سأتلقى إشعارًا على هاتفي عندما يكون هناك محتوى جديد لمراجعته. يمكنني إلقاء نظرة على الرسالة، وتحديد ما إذا كنت أرغب في مشاركتها، والتصرف على الفور.&lt;/p>
&lt;p>ولأنه يعمل على GitHub Actions وفقًا لجدول زمني، فلا يتعين علي أن أتذكر القيام بأي شيء. يعمل النظام في الخلفية، ولا أشارك إلا عندما يكون هناك شيء يستحق المشاركة.&lt;/p>
&lt;h3 id="التقنيات-التي-جعلت-ذلك-ممكنا">التقنيات التي جعلت ذلك ممكنًا&lt;/h3>
&lt;p>جمع هذا المشروع بين العديد من التقنيات التي لعبت كل منها دورًا حاسمًا. قدم .NET 9 أساسًا متينًا بميزات اللغة الحديثة والأداء الممتاز. لقد جعل Semantic Kernel تكامل الذكاء الاصطناعي أمرًا مباشرًا، حيث تعامل مع كل تعقيدات استدعاءات واجهة برمجة التطبيقات وإدارة الاستجابة. لقد وفر Azure OpenAI الذكاء – القدرة على فهم المحتوى التقني وتحليله فعليًا. حل HtmlAgilityPack المشكلة الفوضوية المتمثلة في استخراج النص النظيف من صفحات الويب. System.ServiceModel.Syndicator جعل تحليل RSS أمرًا سهلاً. أعطتنا واجهة برمجة تطبيقات Telegram Bot إشعارات مجانية وموثوقة. وربطت إجراءات GitHub كل ذلك معًا من خلال التنفيذ الآلي المجدول.&lt;/p>
&lt;h3 id="التفكير-في-التكاليف">التفكير في التكاليف&lt;/h3>
&lt;p>سؤال واحد قد يكون لديك: ما هي تكلفة تشغيل هذا؟ الجواب هو: ليس كثيرا على الاطلاق.&lt;/p>
&lt;p>Telegram مجاني تمامًا – لا توجد رسوم لإرسال الرسائل من خلال برنامج الروبوت الخاص بك.&lt;/p>
&lt;p>إجراءات GitHub مجانية للمستودعات العامة. بالنسبة للمستودعات الخاصة، تحصل على 2000 دقيقة شهريًا على المستوى المجاني، وهو أكثر من كافٍ لحالة الاستخدام الخاصة بنا.&lt;/p>
&lt;p>Azure OpenAI هو المكون الوحيد المدفوع، والتكاليف ضئيلة. باستخدام GPT-4o، يتكلف تحليل مقال مدونة نموذجي ما بين سنت وثلاثة سنتات. حتى لو كنت تقوم بمعالجة عشرات المقالات شهريًا، فإنك تتوقع أقل من دولار واحد من تكاليف الذكاء الاصطناعي.&lt;/p>
&lt;h3 id="أين-يمكنك-أن-تأخذ-هذا-بعد-ذلك">أين يمكنك أن تأخذ هذا بعد ذلك&lt;/h3>
&lt;p>على الرغم من أن هذا الحل يناسب احتياجاتي بشكل رائع، إلا أن هناك العديد من الطرق التي يمكنك من خلالها توسيع نطاقه. يمكنك إضافة دعم لمنصات اجتماعية متعددة - ربما النشر على Twitter/X أو Mastodon أو Bluesky بالإضافة إلى LinkedIn. يمكنك تنفيذ تحليل المشاعر لتتبع أسلوب المقالات بمرور الوقت وتحديد الاتجاهات. يمكنك السماح بنماذج مطالبات مختلفة لخلاصات مختلفة، وإنشاء أنماط مختلفة من المنشورات لموضوعات مختلفة. يمكنك إنشاء لوحة تحكم ويب لمراجعة المنشورات وإدارتها بدلاً من استخدام Telegram. يمكنك تتبع مقاييس التفاعل للمحتوى المنشور لمعرفة الموضوعات التي تلقى صدى أكبر لدى جمهورك.&lt;/p>
&lt;h3 id="الأفكار-النهائيةأكثر-ما-أحبه-في-هذا-المشروع-هو-أنه-يجسد-فلسفة-أؤمن-بها-بشدة-يجب-أن-تتعامل-الأتمتة-مع-الأجزاء-المملة-مع-ترك-الأجزاء-الإبداعية-وأجزاء-صنع-القرار-للبشر-يقوم-النظام-بكل-الأعمال-الشاقة---الجلب-والتحليل-والتحليل-والتوليد---ولكنني-ما-زلت-أراجع-كل-شيء-قبل-المشاركة-تعد-المنشورات-التي-يتم-إنشاؤها-بواسطة-الذكاء-الاصطناعي-بمثابة-نقاط-بداية-يمكنني-تخصيصها-وتخصيصها">الأفكار النهائيةأكثر ما أحبه في هذا المشروع هو أنه يجسد فلسفة أؤمن بها بشدة: يجب أن تتعامل الأتمتة مع الأجزاء المملة مع ترك الأجزاء الإبداعية وأجزاء صنع القرار للبشر. يقوم النظام بكل الأعمال الشاقة - الجلب والتحليل والتحليل والتوليد - ولكنني ما زلت أراجع كل شيء قبل المشاركة. تعد المنشورات التي يتم إنشاؤها بواسطة الذكاء الاصطناعي بمثابة نقاط بداية يمكنني تخصيصها وتخصيصها.&lt;/h3>
&lt;p>من خلال الجمع بين قوة .NET وSemantic Kernel وAzure OpenAI، قمنا بإنشاء أداة توفر ساعات من العمل اليدوي كل أسبوع مع الحفاظ على الجودة والاتساق. إنه نوع من الأتمتة العملية التي تُحدث فرقًا حقيقيًا في الحياة اليومية.&lt;/p>
&lt;p>إذا قمت ببناء شيء مماثل أو لديك أفكار للتحسينات، فأنا أحب أن أسمع عنها. لا تتردد في التواصل على LinkedIn!&lt;/p>
&lt;p>برمجة سعيدة، وعيد ميلاد سعيد! 🎄&lt;/p></content:encoded><category>.NET</category><category>Azure</category><category>NuGet</category><category>CI/CD</category><category>Docker</category><category>AI</category></item><item><title>بناء أنظمة الذكاء الاصطناعي متعددة الوكلاء باستخدام Microsoft Agent Framework</title><link>https://emimontesdeoca.github.io/ar/posts/microsoft-agent-framework-multi-agent/</link><pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/microsoft-agent-framework-multi-agent/</guid><description>دليل عملي لبناء أنظمة الذكاء الاصطناعي متعددة الوكلاء وتنسيقها ونشرها باستخدام Microsoft Agent Framework في .NET.</description><content:encoded>&lt;h2 id="مقدمة">مقدمة&lt;/h2>
&lt;p>لقد دخلنا عصر أنظمة الذكاء الاصطناعي متعددة الوكلاء. فبدلاً من استخدام الذكاء الاصطناعي المتجانس للتعامل مع كل شيء، تتجه الصناعة نحو وكلاء متخصصين يتعاونون لحل المشكلات المعقدة، تمامًا مثل فريق جيد التنظيم من الخبراء. وكيل يبحث، وآخر يحلل، وثالث يكتب، والمنسق يبقي الجميع على المسار الصحيح.&lt;/p>
&lt;p>إذا كنت قد عملت مع نماذج لغوية كبيرة، فمن المحتمل أن تكون قد وصلت إلى الحد الأقصى لما يمكن أن تفعله مطالبة واحدة. تمتلئ نوافذ السياق، وتتشابك التعليمات، وتتدهور الجودة. تعمل البنى متعددة الوكلاء على حل هذه المشكلة عن طريق تحليل المهام المعقدة إلى مسؤوليات مركزة، حيث يكون كل وكيل خبيرًا في شيء واحد.&lt;/p>
&lt;p>يمنح Microsoft Agent Framework، وهو جزء من النظام البيئي Semantic Kernel الأوسع، لمطوري .NET مجموعة أدوات من الدرجة الأولى لبناء هذه الأنواع من الأنظمة بالضبط. في هذا المنشور، سننتقل من الصفر إلى مسار عمل متعدد الوكلاء يعمل بشكل كامل، ويغطي المفاهيم الأساسية وأنماط التنسيق والتعليمات البرمجية العملية التي تحتاجها للبدء.&lt;/p>
&lt;h2 id="ما-هو-إطار-عمل-وكيل-microsoft">ما هو إطار عمل وكيل Microsoft؟&lt;/h2>
&lt;p>يعد Agent Framework بمثابة إجابة Microsoft لبناء وتنسيق ونشر وكلاء الذكاء الاصطناعي والأنظمة متعددة الوكلاء في .NET. إنه يجلس جنبًا إلى جنب مع Semantic Kernel ويتكامل معه بشكل عميق، وهو عبارة عن SDK مفتوح المصدر من Microsoft لتنسيق الذكاء الاصطناعي منذ عام 2023.&lt;/p>
&lt;p>فكر في الأمر بهذه الطريقة: &lt;strong>النواة الدلالية&lt;/strong> تمنحك الأساسيات (النوى، والمكونات الإضافية، والذاكرة، والمخططات)، بينما يمنحك &lt;strong>إطار عمل الوكيل&lt;/strong> تجريدات عالية المستوى مصممة خصيصًا للتواصل والتنسيق بين الوكيل.&lt;/p>
&lt;p>يدعم إطار العمل العديد من موفري النماذج بما في ذلك Azure OpenAI وOpenAI والنماذج المستضافة على Azure AI Foundry. إنه لا يعتمد على النماذج في التصميم ولكنه متكامل بعمق مع نظام Azure البيئي، مما يجعله مقنعًا بشكل خاص لسيناريوهات المؤسسات.&lt;/p>
&lt;p>تشمل القدرات الرئيسية ما يلي:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>أنواع الوكلاء المتعددة&lt;/strong>: &lt;code>ChatCompletionAgent&lt;/code>، و&lt;code>OpenAIAssistantAgent&lt;/code>، و&lt;code>AzureAIAgent&lt;/code> للواجهات الخلفية المختلفة&lt;/li>
&lt;li>&lt;strong>أنماط التنسيق&lt;/strong>: سير عمل الدردشة التسلسلية والمتزامنة والتسليم والجماعية&lt;/li>
&lt;li>&lt;strong>نظام المكونات الإضافية&lt;/strong>: توسيع الوكلاء باستخدام وظائف C# الأصلية، ومواصفات OpenAPI، وأدوات بروتوكول سياق النموذج (MCP)&lt;/li>
&lt;li>&lt;strong>إدارة المحادثة&lt;/strong>: الترابط المضمن وإدارة السجل واستراتيجيات الإنهاء&lt;/li>
&lt;li>&lt;strong>قابلية الملاحظة&lt;/strong>: التكامل مع OpenTelemetry لتتبع تفاعلات الوكيل&lt;/li>
&lt;/ul>
&lt;h2 id="المفاهيم-الأساسية">المفاهيم الأساسية&lt;/h2>
&lt;p>قبل أن نكتب أي كود، دعونا نحدد المفردات. يدور إطار عمل الوكيل حول بعض التجريدات الأساسية.&lt;/p>
&lt;h3 id="الوكلاء">الوكلاء&lt;/h3>
&lt;p>الوكيل هو كيان مدعوم بنموذج الذكاء الاصطناعي، ويتم تكوينه بتعليمات محددة (موجه النظام)، واسم، واختياريًا مجموعة من المكونات الإضافية أو الأدوات. كل وكيل هو متخصص - أنت تحدد ما يعرفه، وما يمكنه فعله، وكيف يجب أن يتصرف.&lt;/p>
&lt;h3 id="وكيل-إكمال-الدردشةنوع-الوكيل-الأكثر-مباشرة-فهو-يغلف-نقطة-نهاية-إكمال-الدردشة-azure-openai-وopenai-وما-إلى-ذلك-ويحافظ-على-المحادثة-إنها-عديمة-الحالة-بين-الاستدعاءات---أنت-تقدم-السجل-وهو-يستجيب-وهذا-يجعلها-خفيفة-الوزن-وسهلة-التفكير">وكيل إكمال الدردشةنوع الوكيل الأكثر مباشرة. فهو يغلف نقطة نهاية إكمال الدردشة (Azure OpenAI، وOpenAI، وما إلى ذلك) ويحافظ على المحادثة. إنها عديمة الحالة بين الاستدعاءات - أنت تقدم السجل، وهو يستجيب. وهذا يجعلها خفيفة الوزن وسهلة التفكير.&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ChatCompletionAgent agent = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;You are a senior code reviewer. Analyze the provided code for bugs, security issues, and style violations. Be concise and actionable.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="openaiassistantagent">OpenAIAssistantAgent&lt;/h3>
&lt;p>يستفيد نوع الوكيل هذا من واجهة برمجة التطبيقات OpenAI Assistants API، التي توفر حالة المحادثة من جانب الخادم، ومعالجة الملفات، وتفسير التعليمات البرمجية. إنها ثقيلة الوزن ولكنها تمنحك سلاسل رسائل مستمرة وأدوات مدمجة مثل مترجم التعليمات البرمجية والبحث عن الملفات.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>OpenAIAssistantAgent agent = &lt;span style="color:#ff7b72">await&lt;/span> OpenAIAssistantAgent.CreateAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> clientProvider: clientProvider,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> definition: &lt;span style="color:#ff7b72">new&lt;/span> OpenAIAssistantDefinition(&lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;DataAnalyst&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;You analyze datasets and produce statistical summaries.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="وكيلgroupchat">وكيلGroupChat&lt;/h3>
&lt;p>هذا هو المنسق. &lt;code>AgentGroupChat&lt;/code> يدير المحادثات متعددة الأدوار بين عدة وكلاء، ويتحكم في من يتحدث بعد ذلك، ومتى تنتهي المحادثة، وكيفية مشاركة السجل. إنه المكان الذي يحدث فيه سحر التعاون متعدد الوكلاء.&lt;/p>
&lt;h2 id="أنماط-التنسيق">أنماط التنسيق&lt;/h2>
&lt;p>يدعم إطار العمل أربعة أنماط تزامن أساسية، كل منها يناسب مشاكل مختلفة.&lt;/p>
&lt;h3 id="متسلسل">متسلسل&lt;/h3>
&lt;p>ينفذ الوكلاء واحدًا تلو الآخر بترتيب محدد. يتم تغذية مخرجات الوكيل A إلى الوكيل B، الذي يتم تغذية مخرجاته إلى الوكيل C. وهذا مثالي لخطوط الأنابيب: مسودة → مراجعة → تحرير → نشر.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Conceptual flow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> draft = &lt;span style="color:#ff7b72">await&lt;/span> writerAgent.InvokeAsync(&lt;span style="color:#a5d6ff">&amp;#34;Write a blog post about .NET 9&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> reviewed = &lt;span style="color:#ff7b72">await&lt;/span> reviewerAgent.InvokeAsync(&lt;span style="color:#a5d6ff">$&amp;#34;Review this: {draft}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> edited = &lt;span style="color:#ff7b72">await&lt;/span> editorAgent.InvokeAsync(&lt;span style="color:#a5d6ff">$&amp;#34;Edit based on feedback: {reviewed}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="متزامنة">متزامنة&lt;/h3>
&lt;p>يعمل العديد من الوكلاء على نفس المدخلات في وقت واحد. تقوم بتوزيع العمل ومن ثم تجميع النتائج. يعد هذا أمرًا رائعًا للحصول على وجهات نظر متنوعة - مثل جعل ثلاثة مراجعين ينظرون إلى طلب السحب نفسه.&lt;/p>
&lt;h3 id="التسليم">التسليم&lt;/h3>
&lt;p>يقرر الوكيل نقل التحكم إلى وكيل آخر استنادًا إلى سياق المحادثة. يحاكي هذا كيفية عمل فريق خدمة العملاء: يتعامل وكيل الخط الأمامي مع الاستفسارات الأساسية ويصعدها إلى المتخصصين عند الحاجة.&lt;/p>
&lt;h3 id="دردشة-جماعية">دردشة جماعية&lt;/h3>
&lt;p>يشارك العديد من الوكلاء في محادثة مفتوحة، ويتناوبون بناءً على استراتيجية الاختيار. تطبق فئة &lt;code>AgentGroupChat&lt;/code> هذا النمط من خلال منطق أخذ الأدوار والإنهاء القابل للتكوين.&lt;/p>
&lt;h2 id="بناء-وكيلك-الأول">بناء وكيلك الأول&lt;/h2>
&lt;p>دعونا نصبح عمليين. فيما يلي كيفية إنشاء وكيلك الأول خطوة بخطوة.&lt;/p>
&lt;h3 id="المتطلبات-الأساسية">المتطلبات الأساسية&lt;/h3>
&lt;p>سوف تحتاج إلى:&lt;/p>
&lt;ul>
&lt;li>.NET 9 SDK&lt;/li>
&lt;li>مورد Azure OpenAI بنموذج منشور (على سبيل المثال، &lt;code>gpt-4o&lt;/code>)&lt;/li>
&lt;li>Visual Studio أو VS Code&lt;/li>
&lt;/ul>
&lt;h3 id="إعداد-المشروع">إعداد المشروع&lt;/h3>
&lt;p>قم بإنشاء تطبيق وحدة تحكم جديد وقم بتثبيت الحزم المطلوبة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new console -n AgentDemo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd AgentDemo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Agents.Core --prerelease
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.AI.OpenAI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Azure.Identity
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="إنشاء-وكيل-بسيط">إنشاء وكيل بسيط&lt;/h3>
&lt;p>أولاً، قم بإعداد النواة باستخدام تكوين Azure OpenAI الخاص بك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> credentials: &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Kernel kernel = builder.Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن قم بإنشاء وكيل واستدعائه:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ChatCompletionAgent agent = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;TechWriter&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical writer specializing &lt;span style="color:#ff7b72">in&lt;/span> software documentation.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Write clear, concise content aimed at experienced developers.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Use code examples when appropriate.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Always structure your output with headings and bullet points.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatHistory history = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddUserMessage(&lt;span style="color:#a5d6ff">&amp;#34;Explain dependency injection in .NET in 200 words.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (ChatMessageContent response &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(response.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا كل شيء. لديك وكيل العمل. لكن القوة الحقيقية تأتي عندما يعمل الوكلاء معًا.&lt;/p>
&lt;h2 id="تنسيق-متعدد-الوكلاء-باستخدام-agentgroupchat">تنسيق متعدد الوكلاء باستخدام AgentGroupChat&lt;/h2>
&lt;p>دعونا نبني شيئًا أكثر إثارة للاهتمام — دردشة جماعية يتعاون فيها العديد من الوكلاء. يدير الفصل &lt;code>AgentGroupChat&lt;/code> تدفق المحادثة، بما في ذلك من يتحدث بعد ذلك ومتى يتوقف.&lt;/p>
&lt;h3 id="تعريف-الوكلاء">تعريف الوكلاء&lt;/h3>
&lt;p>سنقوم بإنشاء ثلاثة وكلاء: كاتب، ومراجع، ومحرر.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>ChatCompletionAgent writer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a content writer. When given a topic, produce a well-structured
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> draft. Focus &lt;span style="color:#ff7b72">on&lt;/span> clarity and technical accuracy.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When you receive feedback &lt;span style="color:#ff7b72">from&lt;/span> the Reviewer, incorporate it &lt;span style="color:#ff7b72">into&lt;/span> a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> revised draft.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent reviewer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a content reviewer. Analyze drafts &lt;span style="color:#ff7b72">for&lt;/span> technical accuracy,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> clarity, and completeness. Provide specific, actionable feedback.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Do NOT rewrite the content &lt;span style="color:#f85149">—&lt;/span> only provide feedback.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When the content meets your standards, respond with: APPROVED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent editor = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Editor&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are an editor. Once content &lt;span style="color:#ff7b72">is&lt;/span> approved &lt;span style="color:#ff7b72">by&lt;/span> the Reviewer,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> polish it &lt;span style="color:#ff7b72">for&lt;/span> grammar, tone, and formatting.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Output only the final polished version.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When you have produced the final version, respond with: COMPLETE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="إعداد-الدردشة-الجماعية">إعداد الدردشة الجماعية&lt;/h3>
&lt;p>يحتاج &lt;code>AgentGroupChat&lt;/code> إلى تكوينين رئيسيين: &lt;strong>استراتيجية الاختيار&lt;/strong> (من يتحدث بعد ذلك) و &lt;strong>استراتيجية الإنهاء&lt;/strong> (متى يتوقف).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>AgentGroupChat chat = &lt;span style="color:#ff7b72">new&lt;/span>(writer, reviewer, editor)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecutionSettings = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SelectionStrategy = &lt;span style="color:#ff7b72">new&lt;/span> SequentialSelectionStrategy(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> ApprovalTerminationStrategy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Agents = [editor],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaximumIterations = &lt;span style="color:#a5d6ff">12&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="استراتيجية-الإنهاء-المخصصةتحدد-استراتيجية-الإنهاء-متى-تنتهي-المحادثة-إليك-كلمة-مخصصة-تبحث-عن-الكلمة-الأساسية-مكتملة">استراتيجية الإنهاء المخصصةتحدد استراتيجية الإنهاء متى تنتهي المحادثة. إليك كلمة مخصصة تبحث عن الكلمة الأساسية &amp;ldquo;مكتملة&amp;rdquo;:&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ApprovalTerminationStrategy&lt;/span> : TerminationStrategy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt; ShouldAgentTerminateAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Agent agent,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IReadOnlyList&amp;lt;ChatMessageContent&amp;gt; history,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CancellationToken cancellationToken = &lt;span style="color:#ff7b72">default&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">bool&lt;/span> isComplete = history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Last()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Content?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Contains(&lt;span style="color:#a5d6ff">&amp;#34;COMPLETE&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase) ?? &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task.FromResult(isComplete);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="تشغيل-المحادثة">تشغيل المحادثة&lt;/h3>
&lt;p>ابدأ المحادثة برسالة مستخدم واسمح للوكلاء بالتعاون:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>chat.AddChatMessage(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessageContent(AuthorRole.User,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Write a 300-word technical overview of gRPC vs REST for microservices.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (ChatMessageContent message &lt;span style="color:#ff7b72">in&lt;/span> chat.InvokeAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[{message.AuthorName}]: {message.Content}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;---&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>سيبدو التدفق كما يلي:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>الكاتب&lt;/strong> ينتج مسودة&lt;/li>
&lt;li>&lt;strong>المراجع&lt;/strong> يقدم الملاحظات&lt;/li>
&lt;li>&lt;strong>الكاتب&lt;/strong> يراجع بناءً على التعليقات&lt;/li>
&lt;li>&lt;strong>المراجع&lt;/strong> يقول &amp;ldquo;تمت الموافقة عليه&amp;rdquo;&lt;/li>
&lt;li>&lt;strong>المحرر&lt;/strong> يصقل ويقول &amp;ldquo;مكتمل&amp;rdquo;&lt;/li>
&lt;li>تنتهي المحادثة&lt;/li>
&lt;/ol>
&lt;p>يستمر هذا ذهابًا وإيابًا تلقائيًا حتى يتم استيفاء شرط الإنهاء أو الوصول إلى &lt;code>MaximumIterations&lt;/code>.&lt;/p>
&lt;h2 id="الإضافات-والأدوات">الإضافات والأدوات&lt;/h2>
&lt;p>يصبح الوكلاء أقوياء حقًا عندما يمكنهم التفاعل مع الأنظمة الخارجية. يدعم Agent Framework ثلاث آليات تمديد رئيسية.&lt;/p>
&lt;h3 id="الوظائف-الأصلية-مكونات-kernel-الإضافية">الوظائف الأصلية (مكونات Kernel الإضافية)&lt;/h3>
&lt;p>يمكنك منح الوكلاء إمكانية الوصول إلى أساليب C# كأدوات. سوف يقوم الوكيل باستدعاء هذه الوظائف عندما يحدد أن هناك حاجة إليها:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ContentToolsPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;word_count&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Counts the number of words in the provided text.&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> WordCount([Description(&lt;span style="color:#a5d6ff">&amp;#34;The text to count words in&amp;#34;&lt;/span>)] &lt;span style="color:#ff7b72">string&lt;/span> text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> text.Split(&lt;span style="color:#a5d6ff">&amp;#39; &amp;#39;&lt;/span>, StringSplitOptions.RemoveEmptyEntries).Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;check_readability&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Calculates a readability score for the given text.&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> CheckReadability([Description(&lt;span style="color:#a5d6ff">&amp;#34;The text to analyze&amp;#34;&lt;/span>)] &lt;span style="color:#ff7b72">string&lt;/span> text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> words = text.Split(&lt;span style="color:#a5d6ff">&amp;#39; &amp;#39;&lt;/span>, StringSplitOptions.RemoveEmptyEntries).Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sentences = text.Split([&lt;span style="color:#a5d6ff">&amp;#39;.&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;!&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;?&amp;#39;&lt;/span>], StringSplitOptions.RemoveEmptyEntries).Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (sentences == &lt;span style="color:#a5d6ff">0&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unable to calculate — no sentences found.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">double&lt;/span> avgWordsPerSentence = (&lt;span style="color:#ff7b72">double&lt;/span>)words / sentences;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> avgWordsPerSentence &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt; &lt;span style="color:#a5d6ff">15&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Easy to read (avg {avgWordsPerSentence:F1} words/sentence)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt; &lt;span style="color:#a5d6ff">25&lt;/span> =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Moderate difficulty (avg {avgWordsPerSentence:F1} words/sentence)&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Difficult to read (avg {avgWordsPerSentence:F1} words/sentence). Consider shorter sentences.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>قم بتسجيل الإضافة على النواة قبل إنشاء الوكيل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromType&amp;lt;ContentToolsPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent analyst = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;ContentAnalyst&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;Analyze content using available tools. Report word count and readability.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Kernel = kernel,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Arguments = &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> PromptExecutionSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="بروتوكول-السياق-النموذجي-mcp">بروتوكول السياق النموذجي (MCP)&lt;/h3>
&lt;p>يعد MCP معيارًا مفتوحًا لربط نماذج الذكاء الاصطناعي بالأدوات الخارجية ومصادر البيانات. يدعم Agent Framework MCP، مما يعني أن وكلاءك يمكنهم استخدام الأدوات التي يكشفها أي خادم متوافق مع MCP. وهذا يفتح الباب أمام أنظمة الملفات وقواعد البيانات وواجهات برمجة التطبيقات والمزيد — كل ذلك من خلال واجهة موحدة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Example: Adding an MCP server for file operations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kernel.Plugins.AddFromMcpServer(&lt;span style="color:#a5d6ff">&amp;#34;filesystem&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Uri(&lt;span style="color:#a5d6ff">&amp;#34;http://localhost:3000/mcp&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعد هذا أمرًا مثيرًا بشكل خاص لأنه يعني أن عملائك لا يقتصرون على ما تقوم بإنشائه - بل يمكنهم الاستفادة من النظام البيئي لأدوات MCP التي يطورها الآخرون ويشاركونها.&lt;/p>
&lt;h2 id="مثال-من-العالم-الحقيقي-خط-أنابيب-مراجعة-المحتوى">مثال من العالم الحقيقي: خط أنابيب مراجعة المحتوى&lt;/h2>
&lt;p>دعونا نجمع كل شيء معًا في سيناريو عملي. تخيل أنك تقوم بإنشاء أداة داخلية تعمل على أتمتة مراجعة المحتوى لفريق التوثيق. يتكون خط الأنابيب من أربع مراحل:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>الباحث&lt;/strong> — يجمع المعلومات الفنية ذات الصلة&lt;/li>
&lt;li>&lt;strong>الكاتب&lt;/strong> — يُنتج مسودة بناءً على البحث&lt;/li>
&lt;li>&lt;strong>المراجع&lt;/strong> — يتحقق من الدقة والاكتمال&lt;/li>
&lt;li>&lt;strong>الناشر&lt;/strong> — يقوم بتنسيق المخرجات النهائية وإعدادها&lt;/li>
&lt;/ol>
&lt;p>وهنا تنفيذ مكثف:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Azure.Identity&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Build the kernel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> credentials: &lt;span style="color:#ff7b72">new&lt;/span> DefaultAzureCredential()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Kernel kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Define specialized agents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent researcher = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical researcher. Given a topic, identify the key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> concepts, recent developments, and important details that should
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> be covered. Output a structured research brief.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent writer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical writer. Using the research brief provided,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> write a comprehensive, well-structured article. Include code
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> examples &lt;span style="color:#ff7b72">where&lt;/span> relevant. Target audience: experienced developers.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent reviewer = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a senior technical reviewer. Check the article &lt;span style="color:#ff7b72">for&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Technical accuracy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Completeness relative to the research brief
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Code correctness
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Clarity and structure
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> If everything looks good, respond with APPROVED.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Otherwise, provide specific feedback &lt;span style="color:#ff7b72">for&lt;/span> revision.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ChatCompletionAgent publisher = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;Publisher&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a content publisher. Once the article &lt;span style="color:#ff7b72">is&lt;/span> approved,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> format it with proper markdown, &lt;span style="color:#ff7b72">add&lt;/span> a summary at the top,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and ensure consistent formatting throughout.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> End your response with COMPLETE.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the group chat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>AgentGroupChat chat = &lt;span style="color:#ff7b72">new&lt;/span>(researcher, writer, reviewer, publisher)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecutionSettings = &lt;span style="color:#ff7b72">new&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SelectionStrategy = &lt;span style="color:#ff7b72">new&lt;/span> SequentialSelectionStrategy(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> ApprovalTerminationStrategy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Agents = [publisher],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaximumIterations = &lt;span style="color:#a5d6ff">15&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Start the pipeline&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chat.AddChatMessage(&lt;span style="color:#ff7b72">new&lt;/span> ChatMessageContent(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthorRole.User,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Create a technical article about implementing health checks in ASP.NET Core microservices.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (ChatMessageContent message &lt;span style="color:#ff7b72">in&lt;/span> chat.InvokeAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.ForegroundColor = message.AuthorName &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Researcher&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Cyan,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Writer&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Green,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Reviewer&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Yellow,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Publisher&amp;#34;&lt;/span> =&amp;gt; ConsoleColor.Magenta,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; ConsoleColor.White
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;\n{&amp;#39;=&amp;#39;new string(&amp;#39;=&amp;#39;, 60)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[{message.AuthorName}]&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#39;=&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">60&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.ResetColor();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ينتج هذا المسار مقالة مدروسة ومكتوبة ومراجعة ومنسقة بالكامل - كل ذلك من خلال تعاون الوكيل. يركز كل وكيل على ما يفعله بشكل أفضل، ويقوم &lt;code>AgentGroupChat&lt;/code> بتنسيق سير العمل.&lt;/p>
&lt;h2 id="أفضل-الممارسات">أفضل الممارسات&lt;/h2>
&lt;p>بعد إنشاء العديد من الأنظمة متعددة الوكلاء، إليك الأنماط والممارسات التي وجدتها الأكثر قيمة.&lt;/p>
&lt;h3 id="معالجة-الأخطاء">معالجة الأخطاء&lt;/h3>
&lt;p>قم دائمًا بتعيين &lt;code>MaximumIterations&lt;/code> على إستراتيجية الإنهاء الخاصة بك. وبدون ذلك، يمكن للوكلاء الدخول في حلقات لا نهائية - خاصة عندما يستمر المراجع في العثور على المشكلات ويستمر الكاتب في المراجعة دون تحسين.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> ApprovalTerminationStrategy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaximumIterations = &lt;span style="color:#a5d6ff">12&lt;/span> &lt;span style="color:#8b949e;font-style:italic">// Safety net&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>قم بلف استدعاءات وكيلك في كتل محاولة الالتقاط. حدود معدل واجهة برمجة التطبيقات (API)، ومشكلات الشبكة، وأخطاء النماذج كلها حقائق لأنظمة الإنتاج:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> chat.InvokeAsync(cancellationToken))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Process messages&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">catch&lt;/span> (HttpOperationException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Implement retry with exponential backoff&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(TimeSpan.FromSeconds(&lt;span style="color:#a5d6ff">30&lt;/span>), cancellationToken);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="إمكانية-الملاحظةيتكامل-agent-framework-مع-opentelemetry-مما-يعني-أنه-يمكنك-تتبع-كل-تفاعل-للوكيل-واستدعاء-أداة-واستخدام-الرمز-المميز-يعد-هذا-أمرا-ضروريا-لتصحيح-أخطاء-سير-العمل-متعدد-الوكلاء-حيث-لا-يكون-من-الواضح-دائما-الوكيل-الذي-تسبب-في-المشكلة">إمكانية الملاحظةيتكامل Agent Framework مع OpenTelemetry، مما يعني أنه يمكنك تتبع كل تفاعل للوكيل، واستدعاء أداة، واستخدام الرمز المميز. يعد هذا أمرًا ضروريًا لتصحيح أخطاء سير العمل متعدد الوكلاء حيث لا يكون من الواضح دائمًا الوكيل الذي تسبب في المشكلة.&lt;/h3>
&lt;p>قم بإعداد القياس عن بعد الأساسي عن طريق إضافة حزم القياس عن بعد Semantic Kernel وتكوين المصدر المفضل لديك (Application Insights، Jaeger، وما إلى ذلك):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddOpenTelemetry()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTracing(tracing =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tracing.AddSource(&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.SemanticKernel*&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tracing.AddOtlpExporter();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="إدارة-التكاليف">إدارة التكاليف&lt;/h3>
&lt;p>تعمل الأنظمة متعددة الوكلاء على مضاعفة تكاليف واجهة برمجة التطبيقات (API) الخاصة بك - كل دور وكيل هو عبارة عن استدعاء لواجهة برمجة التطبيقات (API)، ويمكن أن تولد الدردشات الجماعية العديد من الأدوار. بعض الاستراتيجيات لإبقاء التكاليف تحت السيطرة:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>استخدم نماذج أرخص للوكلاء الأبسط&lt;/strong>: لا يحتاج كل وكيل إلى GPT-4o. قد يعمل المراجع بشكل جيد مع نموذج أصغر، بينما يحتاج الكاتب فقط إلى الضارب الثقيل.&lt;/li>
&lt;li>&lt;strong>السجل الحدي&lt;/strong>: استخدم &lt;code>ReducedHistoryCount&lt;/code> في إعدادات التنفيذ لديك لتحديد مقدار سياق المحادثة الذي يتلقاه كل وكيل.&lt;/li>
&lt;li>&lt;strong>تعيين حدود تكرار صارمة&lt;/strong>: منع المحادثات الهاربة ذات قيم &lt;code>MaximumIterations&lt;/code> المعقولة.&lt;/li>
&lt;li>&lt;strong>التخزين المؤقت عندما يكون ذلك ممكنًا&lt;/strong>: إذا أجرى الوكيل نفس البحث بشكل متكرر، فقم بتخزين النتائج مؤقتًا في مكون إضافي.&lt;/li>
&lt;/ul>
&lt;h3 id="تصميم-الوكيل">تصميم الوكيل&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>حافظ على تركيز التعليمات&lt;/strong>: يجب أن يتحمل كل وكيل مسؤولية واحدة واضحة. تؤدي التعليمات الواسعة إلى أداء متواضع في جميع المهام.&lt;/li>
&lt;li>&lt;strong>كن واضحًا بشأن تنسيق المخرجات&lt;/strong>: أخبر الوكلاء بكيفية تنظيم ردودهم بالضبط. وهذا يجعل التحليل المصب موثوقًا به.&lt;/li>
&lt;li>&lt;strong>استخدام الكلمات الرئيسية للإنهاء&lt;/strong>: حدد الإشارات الواضحة (مثل &amp;ldquo;تمت الموافقة عليه&amp;rdquo; أو &amp;ldquo;مكتمل&amp;rdquo;) التي يستخدمها الوكلاء للإشارة إلى الانتهاء. وهذا يجعل استراتيجيات الإنهاء بسيطة ويمكن التنبؤ بها.&lt;/li>
&lt;/ul>
&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>تمثل أنظمة الذكاء الاصطناعي متعددة الوكلاء تحولًا أساسيًا في كيفية بناء التطبيقات الذكية. فبدلاً من النضال مع موجه واحد للتعامل مع كل شيء، يمكننا تحليل المشكلات إلى أدوار متخصصة والسماح للعملاء بالتعاون.&lt;/p>
&lt;p>يجعل Microsoft Agent Framework هذا الأمر عمليًا لمطوري .NET. التجريدات نظيفة - الوكلاء، والمحادثات الجماعية، واستراتيجيات الاختيار والإنهاء - وهي تتشكل بشكل طبيعي. بالاشتراك مع النظام البيئي الإضافي لـ Semantic Kernel واستضافة نماذج Azure، لديك مجموعة كاملة لبناء أنظمة متعددة الوكلاء على مستوى الإنتاج.&lt;/p>
&lt;p>لا يزال إطار العمل يتطور (العديد من الحزم قيد المعاينة)، ولكن الأنماط الأساسية ثابتة والاتجاه واضح. إذا كنت تقوم بإنشاء تطبيقات مدعومة بالذكاء الاصطناعي في .NET، فهذا هو الوقت المناسب لبدء تجربة بنيات متعددة الوكلاء.&lt;/p>
&lt;p>ابدأ صغيرًا - أنشئ وكيلين يتعاونان في مهمة بسيطة. بمجرد ملاحظة تحسن الجودة مقارنة بأساليب الوكيل الفردي، ستحتاج إلى تحليل كل شيء إلى فرق وكلاء.&lt;/p>
&lt;p>ترميز سعيد!&lt;/p></content:encoded><category>AI</category><category>.NET</category><category>Agent Framework</category><category>Semantic Kernel</category></item><item><title>عرض HTML الخام في Blazor باستخدام MarkupString</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-markup-string-raw-html/</link><pubDate>Sat, 22 Nov 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-markup-string-raw-html/</guid><description>قم بعرض محتوى HTML الأولي في مكونات Blazor بأمان باستخدام MarkupString بدلاً من النص الذي تم تجاوزه.</description><content:encoded>&lt;p>في أحد الأيام كنت أقوم ببناء مكون يحتاج إلى تقديم بعض HTML الذي يأتي من نظام إدارة المحتوى (CMS). كانت لدي سلسلة HTML في متغير وقمت بإسقاطها في القالب مثل &lt;code>@myHtml&lt;/code>. وبالطبع، أفلت Blazor من كل شيء وقدم العلامات الفعلية كنص على الصفحة. ليس ما أردت.&lt;/p>
&lt;p>#المشكلة&lt;/p>
&lt;p>افتراضيًا، يقوم Blazor بتشفير أي سلسلة تعرضها في قالب. لذلك إذا كان لديك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> content = &lt;span style="color:#a5d6ff">&amp;#34;&amp;lt;strong&amp;gt;Hello&amp;lt;/strong&amp;gt; &amp;lt;em&amp;gt;world&amp;lt;/em&amp;gt;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وأنت تفعل هذا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@content&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ستشاهد النص الحرفي &lt;code>&amp;lt;strong&amp;gt;Hello&amp;lt;/strong&amp;gt; &amp;lt;em&amp;gt;world&amp;lt;/em&amp;gt;&lt;/code> على الصفحة بدلاً من &lt;strong>Hello&lt;/strong> &lt;em>world&lt;/em>. يقوم Blazor بهذا عن قصد لمنع هجمات XSS، وهو السلوك الافتراضي الصحيح.&lt;/p>
&lt;h1 id="الحل-markupstring">الحل: MarkupString&lt;/h1>
&lt;p>إذا كنت تريد فعليًا تقديم HTML خام، فيمكنك لف السلسلة في &lt;code>MarkupString&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@((MarkupString)content)&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وهذا كل شيء! الآن سوف يقوم Blazor بعرض HTML كعلامة فعلية. يمكنك أيضًا إسنادها إلى متغير:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> rawHtml = &lt;span style="color:#a5d6ff">&amp;#34;&amp;lt;strong&amp;gt;Hello&amp;lt;/strong&amp;gt; &amp;lt;em&amp;gt;world&amp;lt;/em&amp;gt;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> MarkupString HtmlContent =&amp;gt; (MarkupString)rawHtml;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@HtmlContent&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>#مثال من العالم الحقيقي&lt;/p>
&lt;p>كنت أقوم بسحب محتوى المدونة من واجهة برمجة التطبيقات (API) وأحتاج إلى عرضه في مكون معاينة. يحتوي المحتوى على جميع أنواع HTML - العناوين، وكتل التعليمات البرمجية، والروابط، والصور. إليك ما بدا عليه الأمر تقريبًا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@inject HttpClient Http
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@if (article &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;article&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h1&amp;gt;@article.Title&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;content&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>((MarkupString)article.HtmlBody)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/article&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> ArticleId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> ArticleDto? article;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> article = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;ArticleDto&amp;gt;(&lt;span style="color:#a5d6ff">$&amp;#34;api/articles/{ArticleId}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعمل على أكمل وجه. يتم عرض HTML من واجهة برمجة التطبيقات (API) كعلامة فعلية.&lt;/p>
&lt;h1 id="كن-حذرا-مع-المحتوى-غير-الموثوق-به">كن حذرا مع المحتوى غير الموثوق به&lt;/h1>
&lt;p>هذا أمر مهم: &lt;code>MarkupString&lt;/code> &lt;strong>لا&lt;/strong>** يقوم بتطهير HTML. فهو يعرض كل ما تقدمه له، بما في ذلك علامات &lt;code>&amp;lt;script&amp;gt;&lt;/code>. لذا، إذا كان المحتوى يأتي من مدخلات المستخدم أو من مصدر غير موثوق به، فأنت بحاجة إلى تطهيره أولاً.&lt;/p>
&lt;p>لا يوجد مطهر HTML مدمج في Blazor، ولكن يمكنك استخدام مكتبة مثل &lt;a href="https://github.com/mganss/HtmlSanitizer">HtmlSanitizer&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Ganss.Xss&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> HtmlSanitizer sanitizer = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> MarkupString SafeHtml(&lt;span style="color:#ff7b72">string&lt;/span> html)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> clean = sanitizer.Sanitize(html);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> (MarkupString)clean;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;@SafeHtml(untrustedContent)&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يؤدي هذا إلى استبعاد العناصر الخطيرة مثل معالجات &lt;code>&amp;lt;script&amp;gt;&lt;/code> و&lt;code>onclick&lt;/code> والأشياء الأخرى التي لا تريد عرضها من المحتوى المقدم من المستخدم.&lt;/p>
&lt;p>#متى يتم استخدامه&lt;/p>
&lt;p>أستخدم &lt;code>MarkupString&lt;/code> من أجل:&lt;/p>
&lt;ul>
&lt;li>محتوى CMS أو تخفيض السعر الذي تم تحويله إلى جانب خادم HTML&lt;/li>
&lt;li>إخراج محرر النص الغني&lt;/li>
&lt;li>معاينة قوالب البريد الإلكتروني&lt;/li>
&lt;li>أي HTML تم إنشاؤه مسبقًا من مصادر موثوقة&lt;/li>
&lt;/ul>
&lt;p>بالنسبة لأي شيء يأتي من مدخلات المستخدم، قم دائمًا بالتعقيم أولاً. أفضل آمنة من آسف.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! لا تتردد في الاتصال بي على أي وسائل التواصل الاجتماعي على **@emimontesdeoca **.&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.markupstring">هيكل سلسلة العلامات&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/security/content-security-policy">منع Blazor XSS&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>C#</category></item><item><title>ما الجديد في EF Core 9: الميزات التي تحتاج إلى معرفتها</title><link>https://emimontesdeoca.github.io/ar/posts/whats-new-ef-core-9/</link><pubDate>Tue, 18 Nov 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/whats-new-ef-core-9/</guid><description>نظرة شاملة على الميزات الأكثر تأثيرًا في Entity Framework Core 9 - بدءًا من تحسينات LINQ والعمليات المجمعة وحتى أعمدة JSON ودعم تجميع AOT.</description><content:encoded>&lt;p>تم شحن Entity Framework Core 9 جنبًا إلى جنب مع .NET 9 في نوفمبر 2024، وبعد قضاء قدر كبير من الوقت في العمل معه عبر العديد من المشاريع، يمكنني القول إنه أحد الإصدارات الأكثر أهمية منذ فترة. ليس لأنه يعيد اختراع العجلة، ولكن لأنه يصقل المجالات التي تسببت فيها EF Core تاريخيًا في أكبر قدر من الاحتكاك - ترجمة الاستعلامات والأداء والعمل باستخدام أنماط البيانات الحديثة.&lt;/p>
&lt;p>في هذا المنشور، سأستعرض الميزات التي كان لها التأثير الأكبر على عملي اليومي. إذا كنت لا تزال تستخدم EF Core 8 (أو حتى 7)، فمن المفترض أن يمنحك هذا صورة واضحة عما ينتظرك على الجانب الآخر من الترقية.&lt;/p>
&lt;h2 id="ef-core-9-في-النظام-البيئي-net-9">EF Core 9 في النظام البيئي .NET 9&lt;/h2>
&lt;p>يستهدف EF Core 9 الإصدارين .NET 8 و.NET 9، مما يعني أنك لا تحتاج بالضرورة إلى ترقية تطبيقك بالكامل إلى .NET 9 للاستفادة من معظم هذه الميزات. ومع ذلك، فإن بعض AOT وتحسينات الأداء مقترنة بإحكام مع تغييرات وقت تشغيل .NET 9، لذلك ستحصل على أقصى استفادة منه من خلال المضي قدمًا.&lt;/p>
&lt;p>يتبع الإصدار الإيقاع الفردي/الزوجي الذي أنشأته Microsoft: الإصدارات ذات الأرقام الفردية (مثل .NET 9) هي دعم قياسي على المدى (STS) مع 18 شهرًا من الدعم، في حين أن الإصدارات الزوجية (مثل .NET 8) هي دعم طويل الأجل (LTS). ضع ذلك في الاعتبار عند التخطيط للجدول الزمني للترقية.&lt;/p>
&lt;h2 id="تحسينات-الترجمة-في-linq">تحسينات الترجمة في LINQ&lt;/h2>
&lt;p>هذا هو المكان الذي سيشعر فيه معظم المطورين بالفرق على الفور. لقد قطع EF Core 9 خطوات كبيرة في ترجمة تعبيرات LINQ إلى SQL وهو أمر منطقي بالفعل.&lt;/p>
&lt;h3 id="مجموعة-أفضل-بالترجمات">مجموعة أفضل بالترجمات&lt;/h3>
&lt;p>إذا سبق لك أن كتبت استعلام &lt;code>GroupBy&lt;/code> في EF Core وانتهى الأمر بتحذيرات تقييم من جانب العميل أو SQL غريبة، فأنت تعرف الألم. يتعامل EF Core 9 مع مجموعة أوسع بكثير من سيناريوهات &lt;code>GroupBy&lt;/code> مباشرة في SQL.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> salesByCategory = &lt;span style="color:#ff7b72">await&lt;/span> context.Products
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(p =&amp;gt; p.Category.Name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(g =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Category = g.Key,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TotalRevenue = g.Sum(p =&amp;gt; p.Price * p.UnitsSold),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AveragePrice = g.Average(p =&amp;gt; p.Price),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ProductCount = g.Count()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(x =&amp;gt; x.TotalRevenue)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في الإصدارات السابقة، كانت الاستعلامات التي تتضمن تجميعات عبر خصائص التنقل داخل &lt;code>GroupBy&lt;/code> ترجع أحيانًا إلى تقييم العميل. يترجم EF Core 9 هذا الأمر بشكل واضح إلى استعلام SQL واحد باستخدام &lt;code>GROUP BY&lt;/code>، &lt;code>SUM&lt;/code>، &lt;code>AVG&lt;/code>، و &lt;code>COUNT&lt;/code>.&lt;/p>
&lt;h3 id="الإسقاطات-المعقدة-والاستعلامات-الفرعية">الإسقاطات المعقدة والاستعلامات الفرعية&lt;/h3>
&lt;p>حصلت الاستعلامات الفرعية المتداخلة والإسقاطات المعقدة أيضًا على ترقية جدية. النظر في شيء من هذا القبيل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> orderSummaries = &lt;span style="color:#ff7b72">await&lt;/span> context.Customers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(c =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> CustomerSummaryDto
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = c.FullName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TotalOrders = c.Orders.Count(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MostRecentOrder = c.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(o =&amp;gt; o.OrderDate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(o =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> OrderBriefDto
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Id = o.Id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = o.OrderDate,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Total = o.LineItems.Sum(li =&amp;gt; li.Quantity * li.UnitPrice)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TopCategory = c.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SelectMany(o =&amp;gt; o.LineItems)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(li =&amp;gt; li.Product.Category.Name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(g =&amp;gt; g.Count())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(g =&amp;gt; g.Key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكن لـ EF Core 9 الآن ترجمة هذا التعبير بالكامل إلى SQL دون إجراء تقييم من جانب العميل. يستخدم الاستعلام الذي تم إنشاؤه استعلامات فرعية مترابطة وروابط جانبية حيثما يكون ذلك مناسبًا، كما أن خطة SQL أكثر كفاءة بكثير مما تنتجه الإصدارات السابقة.&lt;/p>
&lt;h3 id="المجموعات-البدائية-ذات-المعلمات">المجموعات البدائية ذات المعلمات&lt;/h3>
&lt;p>أحد التحسينات البارزة في LINQ هو القدرة على تمرير مجموعات من القيم الأولية مباشرة إلى الاستعلامات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> statusFilter = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; { &lt;span style="color:#a5d6ff">&amp;#34;Active&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Pending&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Review&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> filteredOrders = &lt;span style="color:#ff7b72">await&lt;/span> context.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(o =&amp;gt; statusFilter.Contains(o.Status))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في EF Core 8، تمت ترجمة ذلك باستخدام عبارات &lt;code>IN&lt;/code> ذات القيم المضمنة، مما يعني أنه لا يمكن إعادة استخدام ذاكرة التخزين المؤقت لخطة الاستعلام عند تغيير القائمة. يقوم EF Core 9 بتحديد معلمات هذه المجموعات بشكل صحيح، وإرسالها كمعلمة منظمة. يعد هذا أمرًا مهمًا للتخزين المؤقت لخطة الاستعلام على SQL Server وPostgreSQL.## العمليات المجمعة - تنفيذ التحديث وتنفيذ الحذف&lt;/p>
&lt;p>تم تقديم &lt;code>ExecuteUpdate&lt;/code> و&lt;code>ExecuteDelete&lt;/code> في EF Core 7، لكن EF Core 9 يوسع ما يمكنك فعله بهما بطرق مفيدة.&lt;/p>
&lt;h3 id="المزيد-من-تعبيرات-التحديث-المعقدة">المزيد من تعبيرات التحديث المعقدة&lt;/h3>
&lt;p>يمكنك الآن استخدام تعبيرات أكثر تعقيدًا في &lt;code>ExecuteUpdate&lt;/code>، بما في ذلك الإشارات إلى الجداول الأخرى من خلال خصائص التنقل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> context.Products
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(p =&amp;gt; p.Category.IsDiscontinued)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ExecuteUpdateAsync(setters =&amp;gt; setters
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SetProperty(p =&amp;gt; p.IsAvailable, &lt;span style="color:#79c0ff">false&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SetProperty(p =&amp;gt; p.DiscontinuedDate, DateTimeOffset.UtcNow)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .SetProperty(p =&amp;gt; p.Price, p =&amp;gt; p.Price * &lt;span style="color:#a5d6ff">0.5&lt;/span>m));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يؤدي هذا إلى إنشاء عبارة &lt;code>UPDATE&lt;/code> واحدة مع &lt;code>JOIN&lt;/code> إلى جدول الفئات - لا حاجة لتحميل الكيانات في الذاكرة، ولا يوجد حمل لتتبع التغيير.&lt;/p>
&lt;h3 id="عمليات-الحذف-المشروطة-المجمعة-باستخدام-الاستعلامات-الفرعية">عمليات الحذف المشروطة المجمعة باستخدام الاستعلامات الفرعية&lt;/h3>
&lt;p>أصبحت الآن عمليات الحذف المجمعة باستخدام مرشحات الاستعلام الفرعي مدعومة بشكل كامل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> context.AuditLogs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(log =&amp;gt; log.CreatedAt &amp;lt; DateTime.UtcNow.AddYears(-&lt;span style="color:#a5d6ff">2&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(log =&amp;gt; !context.ProtectedRecords
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Any(pr =&amp;gt; pr.AuditLogId == log.Id))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ExecuteDeleteAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يُترجم هذا إلى &lt;code>DELETE&lt;/code> مع استعلام فرعي &lt;code>NOT EXISTS&lt;/code>، وهو بالضبط ما ستكتبه يدويًا. لم يتم تحميل أي كيانات، ولا توجد رحلات ذهابًا وإيابًا.&lt;/p>
&lt;h2 id="تحسينات-عمود-json">تحسينات عمود JSON&lt;/h2>
&lt;p>لقد كانت أعمدة JSON واحدة من أكثر الميزات إثارة في إصدارات EF Core الأخيرة، ويأخذها EF Core 9 إلى أبعد من ذلك.&lt;/p>
&lt;h3 id="الاستعلام-داخل-json">الاستعلام داخل JSON&lt;/h3>
&lt;p>يمكنك الآن تصفية البيانات وعرضها من داخل أعمدة JSON مع دعم أفضل للترجمة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Order&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> CustomerName { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ShippingAddress Address { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Stored as JSON&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;OrderNote&amp;gt; Notes { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Stored as JSON&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ShippingAddress&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Street { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> City { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> State { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> ZipCode { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Country { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">OrderNote&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime CreatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Text { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Author { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>التكوين في &lt;code>DbContext&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnModelCreating(ModelBuilder modelBuilder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> modelBuilder.Entity&amp;lt;Order&amp;gt;(builder =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.OwnsOne(o =&amp;gt; o.Address, ab =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ab.ToJson();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.OwnsMany(o =&amp;gt; o.Notes, nb =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> nb.ToJson();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكنك الآن الاستعلام مباشرة إلى JSON:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> newYorkOrders = &lt;span style="color:#ff7b72">await&lt;/span> context.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(o =&amp;gt; o.Address.State == &lt;span style="color:#a5d6ff">&amp;#34;NY&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(o =&amp;gt; o.Notes.Count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(o =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> o.CustomerName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> o.Address.City,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LatestNote = o.Notes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(n =&amp;gt; n.CreatedAt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(n =&amp;gt; n.Text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يقوم EF Core 9 بإنشاء استدعاءات &lt;code>JSON_VALUE&lt;/code> و &lt;code>JSON_QUERY&lt;/code> مناسبة على SQL Server (أو ما يعادله على مقدمي الخدمات الآخرين)، وتغطي الترجمة نطاقًا أوسع بكثير من عمليات LINQ على عناصر JSON أكثر من ذي قبل.&lt;/p>
&lt;h3 id="تحديث-خصائص-json">تحديث خصائص JSON&lt;/h3>
&lt;p>كانت إحدى نقاط الاحتكاك في EF Core 8 هي أن تحديث خاصية واحدة داخل عمود JSON قد يؤدي إلى إعادة كتابة مستند JSON بالكامل. يعمل EF Core 9 على تحسين ذلك من خلال تتبع تغييرات أكثر دقة للأنواع المعينة بـ JSON، مما يؤدي إلى إنشاء المزيد من التحديثات المستهدفة عندما يكون ذلك ممكنًا.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> order = &lt;span style="color:#ff7b72">await&lt;/span> context.Orders.FindAsync(orderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>order.Address.ZipCode = &lt;span style="color:#a5d6ff">&amp;#34;10001&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> context.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بالنسبة للموفرين المدعومين، يمكن أن يؤدي ذلك إلى إنشاء تعديل JSON أكثر استهدافًا بدلاً من إعادة كتابة الكائن الثنائي الكبير بالكامل.&lt;/p>
&lt;h2 id="الأنواع-المعقدة--كائنات-القيمة-بدون-هوية">الأنواع المعقدة — كائنات القيمة بدون هوية&lt;/h2>
&lt;p>تعد الأنواع المعقدة إحدى الميزات التي كان الممارسون في مجال التصميم المبني على المجال ينتظرونها. على عكس الأنواع المملوكة، فإن الأنواع المعقدة ليس لها هوية - فهي كائنات ذات قيمة خالصة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[ComplexType]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Money&lt;/span>(&lt;span style="color:#ff7b72">decimal&lt;/span> Amount, &lt;span style="color:#ff7b72">string&lt;/span> Currency);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[ComplexType]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">DateRange&lt;/span>(DateTime Start, DateTime End);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Project&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Money Budget { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateRange Timeline { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يتم تخزينها كأعمدة مسطحة في الجدول الأصلي - &lt;code>Budget_Amount&lt;/code>، &lt;code>Budget_Currency&lt;/code>، &lt;code>Timeline_Start&lt;/code>، &lt;code>Timeline_End&lt;/code> - دون الحاجة إلى جدول منفصل أو أي نوع من المفاتيح.&lt;/p>
&lt;p>الفرق الرئيسي عن الأنواع المملوكة: تتم مقارنة الأنواع المعقدة من حيث القيمة، وليس من خلال المرجع. يعتبر مثيلان &lt;code>Money&lt;/code> بنفس &lt;code>Amount&lt;/code> و &lt;code>Currency&lt;/code> متساويين، بغض النظر عن الكيان الذي ينتمون إليه.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> expensiveProjects = &lt;span style="color:#ff7b72">await&lt;/span> context.Projects
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(p =&amp;gt; p.Budget.Amount &amp;gt; &lt;span style="color:#a5d6ff">100_000&lt;/span>m &amp;amp;&amp;amp; p.Budget.Currency == &lt;span style="color:#a5d6ff">&amp;#34;USD&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderByDescending(p =&amp;gt; p.Budget.Amount)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ويترجم هذا مباشرة إلى التصفية على الأعمدة المسطحة - نظيفة وفعالة، وهي بالضبط ما تتوقعه.&lt;/p>
&lt;h2 id="دعم-التسلسل-الهرمي-لخادم-sql">دعم التسلسل الهرمي لخادم SQL&lt;/h2>
&lt;p>إذا سبق لك التعامل مع البيانات الهرمية في SQL Server - المخططات التنظيمية وأشجار الفئات وأنظمة الملفات - فأنت تعلم أن &lt;code>HierarchyId&lt;/code> هو النوع المضمن لهذا الغرض. يقدم EF Core 9 دعمًا من الدرجة الأولى له.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Employee&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Title { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> HierarchyId PathFromCeo { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكنك الآن الاستعلام عن العلاقات الهرمية مباشرة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> managerId = HierarchyId.Parse(&lt;span style="color:#a5d6ff">&amp;#34;/1/3/&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// All direct and indirect reports&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> allReports = &lt;span style="color:#ff7b72">await&lt;/span> context.Employees
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(e =&amp;gt; e.PathFromCeo.IsDescendantOf(managerId))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(e =&amp;gt; e.PathFromCeo != managerId) &lt;span style="color:#8b949e;font-style:italic">// exclude the manager&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderBy(e =&amp;gt; e.PathFromCeo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Direct reports only (one level down)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> directReports = &lt;span style="color:#ff7b72">await&lt;/span> context.Employees
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(e =&amp;gt; e.PathFromCeo.GetAncestor(&lt;span style="color:#a5d6ff">1&lt;/span>) == managerId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Get an employee&amp;#39;s depth in the hierarchy&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> employeesWithDepth = &lt;span style="color:#ff7b72">await&lt;/span> context.Employees
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(e =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.Name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.Title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Level = e.PathFromCeo.GetLevel()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderBy(e =&amp;gt; e.Level)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```كل&lt;/span> &lt;span style="color:#f85149">هذه&lt;/span> &lt;span style="color:#f85149">تترجم&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">أساليب&lt;/span> &lt;span style="color:#f85149">`&lt;/span>HierarchyId&lt;span style="color:#f85149">`&lt;/span> &lt;span style="color:#f85149">الأصلية&lt;/span> &lt;span style="color:#f85149">لـ&lt;/span> SQL Server. &lt;span style="color:#f85149">إذا&lt;/span> &lt;span style="color:#f85149">كنت&lt;/span> &lt;span style="color:#f85149">تقوم&lt;/span> &lt;span style="color:#f85149">بتنفيذ&lt;/span> &lt;span style="color:#f85149">هياكل&lt;/span> &lt;span style="color:#f85149">شجرية&lt;/span> &lt;span style="color:#f85149">ذات&lt;/span> &lt;span style="color:#f85149">مرجعية&lt;/span> &lt;span style="color:#f85149">ذاتية&lt;/span> &lt;span style="color:#f85149">للمفاتيح&lt;/span> &lt;span style="color:#f85149">الخارجية&lt;/span> &lt;span style="color:#f85149">و&lt;/span>CTEs &lt;span style="color:#f85149">العودية،&lt;/span> &lt;span style="color:#f85149">فهذا&lt;/span> &lt;span style="color:#f85149">نهج&lt;/span> &lt;span style="color:#f85149">أكثر&lt;/span> &lt;span style="color:#f85149">نظافة&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> &lt;span style="color:#f85149">النماذج&lt;/span> &lt;span style="color:#f85149">المجمعة&lt;/span> &lt;span style="color:#f85149">ودعم&lt;/span> AOT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">سيقدر&lt;/span> &lt;span style="color:#f85149">المطورون&lt;/span> &lt;span style="color:#f85149">المهتمون&lt;/span> &lt;span style="color:#f85149">بالأداء&lt;/span> &lt;span style="color:#f85149">الاستثمار&lt;/span> &lt;span style="color:#f85149">المستمر&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">النماذج&lt;/span> &lt;span style="color:#f85149">المجمعة&lt;/span> &lt;span style="color:#f85149">ودعم&lt;/span> &lt;span style="color:#f85149">التجميع&lt;/span> &lt;span style="color:#f85149">المسبق&lt;/span> (AOT).
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> &lt;span style="color:#f85149">النماذج&lt;/span> &lt;span style="color:#f85149">المجمعة&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">تقوم&lt;/span> &lt;span style="color:#f85149">النماذج&lt;/span> &lt;span style="color:#f85149">المجمعة&lt;/span> &lt;span style="color:#f85149">بإنشاء&lt;/span> &lt;span style="color:#f85149">البيانات&lt;/span> &lt;span style="color:#f85149">التعريفية&lt;/span> &lt;span style="color:#f85149">للنموذج&lt;/span> &lt;span style="color:#f85149">مسبقًا&lt;/span> &lt;span style="color:#f85149">والتي&lt;/span> &lt;span style="color:#f85149">تقوم&lt;/span> EF Core &lt;span style="color:#f85149">ببنائها&lt;/span> &lt;span style="color:#f85149">عادةً&lt;/span> &lt;span style="color:#f85149">عند&lt;/span> &lt;span style="color:#f85149">بدء&lt;/span> &lt;span style="color:#f85149">التشغيل&lt;/span>. &lt;span style="color:#f85149">بالنسبة&lt;/span> &lt;span style="color:#f85149">للنماذج&lt;/span> &lt;span style="color:#f85149">الكبيرة&lt;/span> (&lt;span style="color:#f85149">فكر&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">مئات&lt;/span> &lt;span style="color:#f85149">الكيانات&lt;/span>)&lt;span style="color:#f85149">،&lt;/span> &lt;span style="color:#f85149">يمكن&lt;/span> &lt;span style="color:#f85149">أن&lt;/span> &lt;span style="color:#f85149">يؤدي&lt;/span> &lt;span style="color:#f85149">ذلك&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">تقليل&lt;/span> &lt;span style="color:#f85149">وقت&lt;/span> &lt;span style="color:#f85149">البدء&lt;/span> &lt;span style="color:#f85149">البارد&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">كبير&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>bash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet ef dbcontext optimize --output-dir CompiledModels --&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">MyApp.CompiledModels&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ثم قم بتوصيله:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddDbContext&amp;lt;AppDbContext&amp;gt;(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .UseSqlServer(connectionString)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .UseModel(MyApp.CompiledModels.AppDbContextModel.Instance));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في EF Core 9، تكون النماذج المجمعة أكثر اكتمالاً - فهي تدعم المزيد من ميزات رسم الخرائط وتنتج مخرجات أصغر. بالنسبة للنموذج الذي يحتوي على حوالي 400 كيان، يمكن أن ينخفض ​​وقت بدء التشغيل من عدة ثوانٍ إلى شبه فوري.&lt;/p>
&lt;h3 id="تقدم-تجميع-aot">تقدم تجميع AOT&lt;/h3>
&lt;p>لا يزال دعم AOT الأصلي الكامل لـ EF Core قيد التقدم، لكن EF Core 9 يحقق خطوات كبيرة. تمت إعادة هيكلة العديد من مسارات التعليمات البرمجية ذات الانعكاس الثقيل لتكون ملائمة للتشذيب، وتعد النماذج المجمعة جزءًا أساسيًا من قصة AOT. إذا كنت تستهدف سيناريوهات مثل وظائف Azure أو الخدمات الصغيرة حيث تكون البداية الباردة مهمة، فإن هذه التحسينات ذات صلة مباشرة.&lt;/p>
&lt;h2 id="تحديثات-موفر-قاعدة-بيانات-cosmos">تحديثات موفر قاعدة بيانات Cosmos&lt;/h2>
&lt;p>يستمر موفر Azure Cosmos DB في النضج باستخدام EF Core 9. بعض التحسينات الملحوظة:&lt;/p>
&lt;h3 id="التعامل-مع-مفتاح-التقسيم">التعامل مع مفتاح التقسيم&lt;/h3>
&lt;p>يدعم الموفر الآن مفاتيح الأقسام الهرمية ويتعامل مع عوامل تصفية مفاتيح الأقسام بشكل أكثر ذكاءً:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TenantDocument&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> TenantId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Partition key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Region { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } &lt;span style="color:#8b949e;font-style:italic">// Sub-partition key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Content { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// This query now correctly uses the partition key for routing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> docs = &lt;span style="color:#ff7b72">await&lt;/span> context.TenantDocuments
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(d =&amp;gt; d.TenantId == &lt;span style="color:#a5d6ff">&amp;#34;tenant-42&amp;#34;&lt;/span> &amp;amp;&amp;amp; d.Region == &lt;span style="color:#a5d6ff">&amp;#34;us-east&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(d =&amp;gt; d.Content.Contains(&lt;span style="color:#a5d6ff">&amp;#34;important&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="تحسين-ترجمة-linq-إلى-nosql">تحسين ترجمة LINQ إلى NoSQL&lt;/h3>
&lt;p>يتم الآن ترجمة المزيد من عمليات LINQ إلى لهجة SQL الخاصة بـ Cosmos DB، بما في ذلك دعم أفضل لـ &lt;code>Contains&lt;/code>، &lt;code>Any&lt;/code>، وعمليات المصفوفة المتداخلة، والوظائف الرياضية. يتم الآن التعامل مع الاستعلامات التي كانت ترجع سابقًا إلى تقييم العميل من جانب الخادم.&lt;/p>
&lt;h3 id="دعم-البحث-عن-المتجهات">دعم البحث عن المتجهات&lt;/h3>
&lt;p>يقدم EF Core 9 دعمًا مبكرًا للبحث عن تشابه المتجهات باستخدام Cosmos DB، وهو أمر مفيد إذا كنت تقوم بإنشاء تطبيقات تتكامل مع عمليات التضمين أو البحث المعتمد على الذكاء الاصطناعي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">await&lt;/span> context.Documents
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OrderBy(d =&amp;gt; EF.Functions.VectorDistance(d.Embedding, queryVector))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Take(&lt;span style="color:#a5d6ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="تحسينات-الهجرة">تحسينات الهجرة&lt;/h2>
&lt;p>حصلت عمليات الترحيل على بعض التحسينات في جودة الحياة مما يجعل العمل معهم أقل إيلامًا في بيئات الفريق.&lt;/p>
&lt;h3 id="الجداول-الزمنية-في-الهجرات">الجداول الزمنية في الهجرات&lt;/h3>
&lt;p>تتعامل عمليات الترحيل الآن مع تكوين الجدول الزمني بشكل أكثر رشاقة، مع الدعم المناسب لأعمدة الفترة وتسمية جدول المحفوظات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnModelCreating(ModelBuilder modelBuilder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> modelBuilder.Entity&amp;lt;Employee&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToTable(&lt;span style="color:#a5d6ff">&amp;#34;Employees&amp;#34;&lt;/span>, b =&amp;gt; b.IsTemporal(t =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.HasPeriodStart(&lt;span style="color:#a5d6ff">&amp;#34;ValidFrom&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.HasPeriodEnd(&lt;span style="color:#a5d6ff">&amp;#34;ValidTo&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.UseHistoryTable(&lt;span style="color:#a5d6ff">&amp;#34;EmployeeHistory&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="نصوص-عاجزة">نصوص عاجزة&lt;/h3>
&lt;p>ينتج الأمر &lt;code>Script-Migration&lt;/code> (ومكافئه لواجهة سطر الأوامر) نصوصًا برمجية أفضل بشكل افتراضي، مع معالجة محسنة لحالات الحافة حول تغييرات المخطط التي تعتمد على البيانات الموجودة في حالات معينة.&lt;/p>
&lt;h3 id="حزم-الهجرة">حزم الهجرة&lt;/h3>
&lt;p>تعد حزم الترحيل، التي تجمع عمليات الترحيل الخاصة بك في ملف مستقل قابل للتنفيذ للنشر، أكثر موثوقية في EF Core 9 مع الإبلاغ عن الأخطاء بشكل أفضل ومنطق إعادة المحاولة في حالات الفشل العابر.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet ef migrations bundle --self-contained -r linux-x64
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ينتج عن ذلك ملف ثنائي يمكنك تشغيله في مسار CI/CD الخاص بك دون الحاجة إلى تثبيت .NET SDK على هدف النشر الخاص بك.&lt;/p>
&lt;h2 id="معايير-الأداءفيما-يلي-بعض-المعايير-التقريبية-من-الاختبار-الخاص-بي-هذه-من-مشروع-يضم-ما-يقرب-من-200-كيان-تعمل-مقابل-sql-server-2022-ويتم-قياسها-باستخدام-benchmarkdotnet-ستختلف-أرقامك-لكن-التحسينات-النسبية-يجب-أن-تكون-متشابهة">معايير الأداءفيما يلي بعض المعايير التقريبية من الاختبار الخاص بي. هذه من مشروع يضم ما يقرب من 200 كيان، تعمل مقابل SQL Server 2022، ويتم قياسها باستخدام BenchmarkDotNet. ستختلف أرقامك، لكن التحسينات النسبية يجب أن تكون متشابهة.&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>السيناريو&lt;/th>
&lt;th>إي أف كور 8&lt;/th>
&lt;th>إي أف كور 9&lt;/th>
&lt;th>تحسين&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>بناء النموذج (البداية الباردة)&lt;/td>
&lt;td>1,850 مللي ثانية&lt;/td>
&lt;td>320 مللي ثانية&lt;/td>
&lt;td>~5.8x أسرع (مترجمة)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>استعلام بسيط (كيان واحد بواسطة PK)&lt;/td>
&lt;td>0.42 مللي ثانية&lt;/td>
&lt;td>0.38 مللي ثانية&lt;/td>
&lt;td>~10% أسرع&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>استعلام معقد (صلات + تجميع)&lt;/td>
&lt;td>3.1 مللي ثانية&lt;/td>
&lt;td>2.4 مللي ثانية&lt;/td>
&lt;td>~23% أسرع&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>تحديث مجمع (10 آلاف صف)&lt;/td>
&lt;td>145 مللي ثانية&lt;/td>
&lt;td>118 مللي ثانية&lt;/td>
&lt;td>~19% أسرع&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>استعلام عمود JSON&lt;/td>
&lt;td>2.8 مللي ثانية&lt;/td>
&lt;td>1.9 مللي ثانية&lt;/td>
&lt;td>~32% أسرع&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>حفظ التغييرات (100 كيان)&lt;/td>
&lt;td>48 مللي ثانية&lt;/td>
&lt;td>41 مللي ثانية&lt;/td>
&lt;td>~15% أسرع&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>يعد تحسين النموذج المجمّع هو الأكثر دراماتيكية، ولكن التحسينات الثابتة في جميع المجالات تضيف المزيد - خاصة في السيناريوهات عالية الإنتاجية حيث تقوم بتنفيذ آلاف الاستعلامات في الثانية.&lt;/p>
&lt;h2 id="الترقية-من-ef-core-8">الترقية من EF Core 8&lt;/h2>
&lt;p>إذا كنت تستخدم EF Core 8، فإن مسار الترقية يكون سلسًا نسبيًا. فيما يلي قائمة مرجعية:&lt;/p>
&lt;p>**1. تحديث الحزم الخاصة بك: **&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.EntityFrameworkCore&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.EntityFrameworkCore.SqlServer&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;PackageReference&lt;/span> Include=&lt;span style="color:#a5d6ff">&amp;#34;Microsoft.EntityFrameworkCore.Tools&amp;#34;&lt;/span> Version=&lt;span style="color:#a5d6ff">&amp;#34;9.0.0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>**2. تحقق من وجود تغييرات عاجلة. ** قائمة EF Core 9 قصيرة نسبيًا مقارنة ببعض الإصدارات السابقة. أبرزها:&lt;/p>
&lt;ul>
&lt;li>تمت إزالة بعض واجهات برمجة التطبيقات (APIs) القديمة سابقًا&lt;/li>
&lt;li>تغييرات في كيفية ترجمة استعلامات &lt;code>GroupBy&lt;/code> معينة (تنتقل الآن إلى جانب الخادم، مما يغير السلوك إذا كنت تعتمد على تقييم العميل)&lt;/li>
&lt;li>تغييرات طفيفة في مخرجات سقالات الهجرة&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>3. أعد إنشاء النماذج المجمعة&lt;/strong> إذا كنت تستخدمها. تم تغيير التنسيق، لذا لن تعمل النماذج القديمة المجمعة مع EF Core 9.&lt;/p>
&lt;p>**4. قم بتشغيل مجموعة الاختبار الخاصة بك. ** انتبه بشكل خاص للاستعلامات التي تم تقييمها مسبقًا على العميل - ربما يتم تقييمها الآن على الخادم، وهو ما يكون عادةً أفضل ولكنه قد يؤدي إلى ظهور اختلافات في البيانات.&lt;/p>
&lt;p>&lt;strong>5. تحقق من استعلامات Cosmos DB&lt;/strong> إذا كنت تستخدم هذا الموفر. تعني الترجمات المحسنة أن بعض الاستعلامات سيتم تنفيذها بشكل مختلف (عادةً ما يكون أسرع)، ولكن من المفيد التحقق من تطابق النتائج.&lt;/p>
&lt;p>يبدو الحد الأدنى من الترقية لمشروع نموذجي كما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.EntityFrameworkCore --version 9.0.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 9.0.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إذا تم تجميع كل شيء واجتياز الاختبارات، فمن المحتمل أنك في حالة جيدة. إذا واجهت مشكلات، فإن وثائق التغييرات العاجلة في EF Core 9 تحتوي على إرشادات مفصلة للترحيل لكل تغيير.&lt;/p>
&lt;h2 id="الختام">الختام&lt;/h2>
&lt;p>إن EF Core 9 ليس إصدارًا ثوريًا، بل هو إصدار تطوري، وهذا بالضبط ما كان يجب أن يكون عليه. تحسينات LINQ وحدها تبرر الترقية لمعظم المشاريع، والميزات مثل تحسينات أعمدة JSON، والأنواع المعقدة، و&lt;code>HierarchyId&lt;/code> تدعم الأنماط المفتوحة التي كانت في السابق غير ملائمة أو مستحيلة.&lt;/p>
&lt;p>إذا اضطررت إلى اختيار الميزات الثلاثة التي كان لها التأثير الأكبر على مشاريعي:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>المجموعات الأولية ذات المعلمات&lt;/strong> — لأن كفاءة ذاكرة التخزين المؤقت لخطة الاستعلام مهمة على نطاق واسع&lt;/li>
&lt;li>&lt;strong>تحسينات عمود JSON&lt;/strong> — لأن نمط المستند العلائقي المختلط مفيد بشكل لا يصدق&lt;/li>
&lt;li>&lt;strong>النماذج المجمعة&lt;/strong> — لأن وقت بدء التشغيل يؤثر بشكل مباشر على إنتاجية المطور وسرعة النشرلقد كان فريق EF Core يسير على مسار قوي منذ EF Core 5، ويواصل الإصدار 9 هذا الاتجاه. إذا كنت مشتركًا بالفعل في EF Core 8، فإن الترقية تكون منخفضة المخاطر ومكافأة عالية. إذا كنت تستخدم شيئًا أقدم، فليس هناك وقت أفضل للقيام بهذه القفزة.&lt;/li>
&lt;/ol>
&lt;p>برمجة سعيدة - واستعلام سعيد.&lt;/p></content:encoded><category>.NET</category><category>Entity Framework</category><category>Database</category></item><item><title>البدء باستخدام النواة الدلالية: تنسيق الذكاء الاصطناعي في لغة C#</title><link>https://emimontesdeoca.github.io/ar/posts/getting-started-semantic-kernel/</link><pubDate>Sun, 05 Oct 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/getting-started-semantic-kernel/</guid><description>تعرف على كيفية استخدام Semantic Kernel من Microsoft لإنشاء تطبيقات مدعومة بالذكاء الاصطناعي في لغة C# - بدءًا من المكونات الإضافية والمخططات وحتى استدعاء الذاكرة والوظائف.</description><content:encoded>&lt;p>إذا كنت تقوم ببناء تطبيقات .NET وتشاهد مشهد الذكاء الاصطناعي يتطور، فربما تساءلت: *ما هي أفضل طريقة لدمج نماذج اللغة الكبيرة في مشاريع C# الخاصة بي دون تحويل قاعدة التعليمات البرمجية الخاصة بي إلى معكرونة؟ * هذه هي بالضبط المشكلة التي تحلها Semantic Kernel من Microsoft، وبعد قضاء العام الماضي في بناء تطبيقات الإنتاج باستخدامها، يمكنني أن أخبرك أنها أصبحت واحدة من أهم الأدوات في مجموعة أدوات المطور الخاصة بي.&lt;/p>
&lt;p>في هذا المنشور، سأرشدك إلى كل ما تحتاجه للبدء في استخدام Semantic Kernel - بدءًا من فهم المفاهيم الأساسية وحتى إنشاء مساعد الذكاء الاصطناعي في العالم الحقيقي. سواء كنت تغمس أصابع قدميك في تطوير الذكاء الاصطناعي أو تبحث عن طريقة منظمة لتنسيق مكالمات LLM في تطبيقات .NET الموجودة لديك، فإن هذا الدليل يلبي احتياجاتك.&lt;/p>
&lt;h2 id="ما-هي-النواة-الدلالية">ما هي النواة الدلالية؟&lt;/h2>
&lt;p>Semantic Kernel (SK) عبارة عن SDK مفتوح المصدر من Microsoft يعمل بمثابة &lt;strong>طبقة تنسيق&lt;/strong> بين رمز التطبيق الخاص بك ونماذج اللغات الكبيرة مثل GPT-4o أو Azure OpenAI أو خدمات الذكاء الاصطناعي الأخرى. فكر في الأمر على أنه برنامج وسيط خفيف الوزن يتيح لك الجمع بين كود C# التقليدي وإمكانيات الذكاء الاصطناعي بطريقة نظيفة وقابلة للتركيب.&lt;/p>
&lt;p>ولكن لماذا لا نتصل بـ OpenAI API مباشرة؟ يمكنك فعل ذلك بالتأكيد، ولا بأس بذلك بالنسبة لحالات الاستخدام البسيطة. لكن في اللحظة التي تحتاج فيها إلى:&lt;/p>
&lt;ul>
&lt;li>دع الذكاء الاصطناعي يقرر &lt;strong>الوظائف&lt;/strong> التي سيتم الاتصال بها بناءً على مدخلات المستخدم&lt;/li>
&lt;li>ادمج &lt;strong>مكالمات الذكاء الاصطناعي المتعددة&lt;/strong> مع التعليمات البرمجية التقليدية في مسار واحد&lt;/li>
&lt;li>أضف &lt;strong>الذاكرة والسياق&lt;/strong> حتى يتذكر الذكاء الاصطناعي التفاعلات السابقة&lt;/li>
&lt;li>قم ببناء &lt;strong>وكلاء متعددي الخطوات&lt;/strong> الذين يقومون بالتفكير من خلال المهام المعقدة&lt;/li>
&lt;/ul>
&lt;p>&amp;hellip;ستجد نفسك تعيد اختراع العجلة. يمنحك Semantic Kernel كل هذا خارج الصندوق، مع دعم .NET من الدرجة الأولى، وتكامل حقن التبعية، وبنية المكونات الإضافية التي تبدو طبيعية لأي مطور C#.&lt;/p>
&lt;p>يتواجد المشروع على GitHub ضمن مستودع &lt;code>microsoft/semantic-kernel&lt;/code> ويحتوي على SDKs لـ C# وPython وJava. يعد C# SDK هو الأكثر نضجًا وهو الذي سنركز عليه هنا.&lt;/p>
&lt;h2 id="المفاهيم-الأساسية">المفاهيم الأساسية&lt;/h2>
&lt;p>قبل أن نكتب أي كود، دعونا نفهم اللبنات الأساسية.&lt;/p>
&lt;h3 id="النواة">النواة&lt;/h3>
&lt;p>&lt;code>Kernel&lt;/code> هو الكائن المركزي في Semantic Kernel. إنه المنسق - الشيء الذي يربط بين خدمات الذكاء الاصطناعي والمكونات الإضافية والتكوين. يمكنك إنشاء واحدة، وتسجيل خدماتك ومكوناتك الإضافية عليها، ثم استخدامها لتنفيذ المطالبات أو استدعاء الوظائف. إذا كنت معتادًا على حقن التبعية في ASP.NET Core، فستشعر أن Kernel مألوف جدًا - فهي في الأساس حاوية خدمة ذات قوى خارقة للذكاء الاصطناعي.&lt;/p>
&lt;h3 id="الإضافات-والوظائف">الإضافات والوظائف&lt;/h3>
&lt;p>&lt;strong>المكون الإضافي&lt;/strong> عبارة عن مجموعة من &lt;strong>الوظائف&lt;/strong> ذات الصلة التي يمكن أن يستدعيها Kernel. الوظائف تأتي في نكهتين:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>الوظائف السريعة&lt;/strong> — يتم تعريفها على أنها قوالب لغة طبيعية يتم إرسالها إلى LLM&lt;/li>
&lt;li>&lt;strong>الوظائف الأصلية&lt;/strong> — أساليب C# العادية المزينة بالسمات التي يمكن لـ Kernel اكتشافها واستدعاءها&lt;/li>
&lt;/ul>
&lt;p>على سبيل المثال، قد يكون لديك &lt;code>WeatherPlugin&lt;/code> مع وظيفة أصلية &lt;code>GetCurrentWeather(string city)&lt;/code> ووظيفة سريعة تلخص بيانات الطقس بطريقة سهلة.### موصلات الذكاء الاصطناعي&lt;/p>
&lt;p>الموصلات هي الطريقة التي تتحدث بها Semantic Kernel مع خدمات الذكاء الاصطناعي. الأكثر شيوعا هي:&lt;/p>
&lt;ul>
&lt;li>&lt;code>AzureOpenAIChatCompletion&lt;/code> — لخدمة Azure OpenAI&lt;/li>
&lt;li>&lt;code>OpenAIChatCompletion&lt;/code> — لواجهة برمجة تطبيقات OpenAI مباشرة&lt;/li>
&lt;li>تضمين موصلات للبحث عن المتجهات والذاكرة&lt;/li>
&lt;/ul>
&lt;p>تقوم بتسجيلها على Kernel عند بدء التشغيل، وكل شيء آخر يعمل.&lt;/p>
&lt;h2 id="إعداد-مشروعك">إعداد مشروعك&lt;/h2>
&lt;p>دعونا الحصول على أيدينا القذرة. ابدأ بإنشاء تطبيق وحدة تحكم جديد:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new console -n SemanticKernelDemo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd SemanticKernelDemo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>أضف الآن حزم Semantic Kernel NuGet:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إذا كنت تستخدم OpenAI مباشرةً بدلاً من Azure OpenAI:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لدعم الذاكرة والتضمينات (سنستخدم هذا لاحقًا):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.SemanticKernel.Plugins.Memory
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet add package Microsoft.Extensions.VectorData.Abstractions
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يجب أن يستهدف &lt;code>.csproj&lt;/code> الإصدار .NET 8 أو الأحدث. تستفيد أحدث إصدارات Semantic Kernel استفادة كاملة من ميزات .NET الحديثة.&lt;/p>
&lt;h2 id="نواتك-الأولى">نواتك الأولى&lt;/h2>
&lt;p>لنبدأ بأبسط مثال ممكن - إنشاء Kernel، وربطه بخدمة الذكاء الاصطناعي، وطرح سؤال عليه.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Build the kernel with Azure OpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Invoke a simple prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> kernel.InvokePromptAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Explain dependency injection in C# in three sentences.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إذا كنت تستخدم OpenAI مباشرة، فقم بتبديل تسجيل الخدمة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> modelId: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-openai-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا كل شيء. قم بتشغيله وستحصل على شرح موجز لحقن التبعية. ولكن هذا مجرد خدش السطح.&lt;/p>
&lt;h3 id="استخدام-القوالب-السريعة">استخدام القوالب السريعة&lt;/h3>
&lt;p>تتيح لك قوالب الموجهات تحديد معلمات المطالبات الخاصة بك باستخدام المتغيرات باستخدام بناء جملة نمط Handlebars:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> prompt = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a technical writer. Write a brief summary of {{&lt;span style="color:#f85149">$&lt;/span>topic}}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aimed at developers with {{&lt;span style="color:#f85149">$&lt;/span>experienceLevel}} experience.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Keep it under &lt;span style="color:#a5d6ff">200&lt;/span> words.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> function = kernel.CreateFunctionFromPrompt(prompt);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> arguments = &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&amp;#34;topic&amp;#34;] = &lt;span style="color:#a5d6ff">&amp;#34;gRPC in .NET&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&amp;#34;experienceLevel&amp;#34;] = &lt;span style="color:#a5d6ff">&amp;#34;intermediate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> kernel.InvokeAsync(function, arguments);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا هو المكان الذي يبدأ فيه Semantic Kernel في التألق - يمكنك تحديد قوالب المطالبة القابلة لإعادة الاستخدام، وإصدارها، وتكوينها في مسارات عمل أكبر.&lt;/p>
&lt;h2 id="الإضافات-والوظائف-الأصلية">الإضافات والوظائف الأصلية&lt;/h2>
&lt;p>المكونات الإضافية هي المكان الذي يقوم فيه Semantic Kernel بسد الفجوة بين الذكاء الاصطناعي ورمز C# الموجود لديك. الوظيفة الأصلية هي مجرد طريقة عادية تعرضها لـ Kernel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.ComponentModel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TimePlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;get_current_time&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Gets the current date and time in UTC&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetCurrentTime()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> DateTime.UtcNow.ToString(&lt;span style="color:#a5d6ff">&amp;#34;yyyy-MM-dd HH:mm:ss UTC&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;get_time_in_timezone&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Gets the current time in a specific timezone&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetTimeInTimezone(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The IANA timezone identifier, e.g. &amp;#39;America/New_York&amp;#39;&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> timezone)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> tz = TimeZoneInfo.FindSystemTimeZoneById(timezone);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> time = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> time.ToString(&lt;span style="color:#a5d6ff">&amp;#34;yyyy-MM-dd HH:mm:ss&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لاحظ سمات &lt;code>[KernelFunction]&lt;/code> و &lt;code>[Description]&lt;/code>. هذه الأمور بالغة الأهمية، فالأوصاف هي ما يقرأه الذكاء الاصطناعي لفهم متى وكيف يتم استدعاء وظائفك. الأوصاف الجيدة تُحدث فرقًا بين الذكاء الاصطناعي الذي يستخدم أدواتك بفعالية والذكاء الاصطناعي الذي يرتبك.&lt;/p>
&lt;p>سجل البرنامج المساعد على النواة الخاصة بك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;TimePlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكنك أيضًا إنشاء مكونات إضافية أكثر تعقيدًا تعمل على إدخال الخدمات. نظرًا لأن Semantic Kernel يتكامل مع &lt;code>Microsoft.Extensions.DependencyInjection&lt;/code>، فيمكن لمكوناتك الإضافية تلقي تبعيات المُنشئ تمامًا مثل أي خدمة أخرى:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">OrderPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IOrderRepository _repository;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> OrderPlugin(IOrderRepository repository)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _repository = repository;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;get_order_status&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Retrieves the status of an order by its ID&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetOrderStatus(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The order ID to look up&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> orderId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> order = &lt;span style="color:#ff7b72">await&lt;/span> _repository.GetByIdAsync(orderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> order &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? &lt;span style="color:#a5d6ff">$&amp;#34;No order found with ID {orderId}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : &lt;span style="color:#a5d6ff">$&amp;#34;Order {orderId}: {order.Status}, placed on {order.CreatedAt:d}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="استدعاء-الوظائف-والاستدعاء-التلقائي">استدعاء الوظائف والاستدعاء التلقائي&lt;/h2>
&lt;p>هذا هو المكان الذي تصبح فيه الأمور مثيرة للاهتمام حقًا. من خلال &lt;strong>استدعاء الوظائف&lt;/strong> (المعروف أيضًا باسم استدعاء الأدوات)، يمكنك السماح لنموذج الذكاء الاصطناعي بتحديد الوظائف المسجلة التي سيتم الاتصال بها بناءً على سياق المحادثة. لا ينفذ النموذج تعليمات برمجية — فهو يُرجع طلبًا منظمًا يقول &amp;ldquo;أريد استدعاء الوظيفة X باستخدام هذه الوسائط&amp;rdquo;، ويتولى Kernel التعامل مع الاستدعاء الفعلي.&lt;/p>
&lt;p>فيما يلي كيفية تمكين استدعاء الوظائف التلقائي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;TimePlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;WeatherPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Enable automatic function calling&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> settings = &lt;span style="color:#ff7b72">new&lt;/span> OpenAIPromptExecutionSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> kernel.InvokePromptAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;What time is it in Tokyo and what&amp;#39;s the weather like there?&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments(settings)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(result);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>باستخدام &lt;code>FunctionChoiceBehavior.Auto()&lt;/code>، سيقوم النواة بما يلي:&lt;/p>
&lt;ol>
&lt;li>أرسل مطالبتك إلى الذكاء الاصطناعي مع وصف جميع الوظائف المتاحة&lt;/li>
&lt;li>يقرر الذكاء الاصطناعي أنه بحاجة إلى الاتصال بـ &lt;code>get_time_in_timezone&lt;/code> و&lt;code>get_weather&lt;/code>&lt;/li>
&lt;li>يقوم Kernel تلقائيًا بتنفيذ هذه الوظائف&lt;/li>
&lt;li>يتم إرسال النتائج مرة أخرى إلى الذكاء الاصطناعي&lt;/li>
&lt;li>يقوم الذكاء الاصطناعي بتأليف استجابة لغة طبيعية باستخدام نتائج الوظيفةيمكن أن تحدث هذه الحلقة عدة مرات في استدعاء واحد - قد يستدعي الذكاء الاصطناعي عدة وظائف بالتسلسل لجمع كل المعلومات التي يحتاجها. يمكنك أيضًا استخدام &lt;code>FunctionChoiceBehavior.Required()&lt;/code> لإجبار الذكاء الاصطناعي على استدعاء وظيفة واحدة على الأقل، أو تقديم قائمة محددة من الوظائف المسموح له باستخدامها.&lt;/li>
&lt;/ol>
&lt;h3 id="استكمال-الدردشة-مع-التاريخ">استكمال الدردشة مع التاريخ&lt;/h3>
&lt;p>بالنسبة لتطبيقات المحادثة، ستحتاج إلى استخدام &lt;code>ChatCompletionService&lt;/code> مباشرة مع كائن &lt;code>ChatHistory&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatService = kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddSystemMessage(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a helpful developer assistant. You have access to tools
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">for&lt;/span> checking the time and weather. Be concise and friendly.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">while&lt;/span> (&lt;span style="color:#79c0ff">true&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;You: &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> input = Console.ReadLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(input)) &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(input);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> chatService.GetChatMessageContentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> executionSettings: settings,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kernel: kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddAssistantMessage(response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Assistant: {response.Content}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمنحك هذا برنامج chatbot تفاعلي بالكامل يحافظ على سجل المحادثات ويمكنه استدعاء المكونات الإضافية حسب الحاجة.&lt;/p>
&lt;h2 id="الذاكرة-والتضمينات">الذاكرة والتضمينات&lt;/h2>
&lt;p>أحد أقوى الأنماط في تطبيقات الذكاء الاصطناعي هو &lt;strong>الجيل المعزز للاسترجاع (RAG)&lt;/strong> — مما يتيح للذكاء الاصطناعي الوصول إلى بياناتك الخاصة عن طريق تضمينها في مساحة متجهة واسترداد الأجزاء ذات الصلة في وقت الاستعلام.&lt;/p>
&lt;p>يوفر Semantic Kernel تجريدات للعمل مع مخازن المتجهات والتضمينات. فيما يلي كيفية إعداد متجر متجهات في الذاكرة للتطوير:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.Extensions.VectorData&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.AzureOpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Embeddings&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#pragma&lt;/span> warning disable SKEXP0010
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Create an embedding generation service&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAITextEmbeddingGeneration(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;text-embedding-ada-002&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> embeddingService = kernel.GetRequiredService&amp;lt;ITextEmbeddingGenerationService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Generate embeddings for your documents&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> documents = &lt;span style="color:#ff7b72">new&lt;/span>[]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Semantic Kernel is an open-source SDK for AI orchestration.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Azure OpenAI provides enterprise-grade AI models.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Plugins in SK allow you to expose C# methods to AI models.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> embeddings = &lt;span style="color:#ff7b72">await&lt;/span> embeddingService.GenerateEmbeddingsAsync(documents);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بالنسبة لسيناريوهات الإنتاج، يمكنك تخزين هذه التضمينات في قاعدة بيانات متجهة مخصصة مثل Azure AI Search أو Qdrant أو Pinecone. يحتوي Kernel الدلالي على موصلات لكل هذه الروابط من خلال تجريدات &lt;code>Microsoft.Extensions.VectorData&lt;/code>.&lt;/p>
&lt;p>يبدو تدفق RAG النموذجي كما يلي:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>الاستيعاب&lt;/strong>: قم بتقطيع مستنداتك، وإنشاء التضمينات، وتخزينها في قاعدة بيانات متجهة&lt;/li>
&lt;li>&lt;strong>استرداد&lt;/strong>: عندما يطرح المستخدم سؤالاً، قم بتضمين الاستعلام وابحث عن المستندات الأكثر تشابهًا&lt;/li>
&lt;li>&lt;strong>إنشاء&lt;/strong>: قم بتمرير المستندات المستردة كسياق إلى LLM مع سؤال المستخدم&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[KernelFunction(&amp;#34;search_knowledge_base&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Description(&amp;#34;Searches the internal knowledge base for relevant information&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; SearchKnowledgeBase(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;The search query&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> query)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> queryEmbedding = &lt;span style="color:#ff7b72">await&lt;/span> _embeddingService.GenerateEmbeddingAsync(query);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> results = &lt;span style="color:#ff7b72">await&lt;/span> _vectorStore.SearchAsync(queryEmbedding, limit: &lt;span style="color:#a5d6ff">3&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n\n&amp;#34;&lt;/span>, results.Select(r =&amp;gt; r.Text));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>من خلال الكشف عن مسار RAG الخاص بك كوظيفة kernel، يمكن للذكاء الاصطناعي أن يقرر تلقائيًا متى يحتاج إلى البحث في قاعدة المعرفة الخاصة بك - مع الحفاظ على التنسيق نظيفًا والسماح للنموذج بالقيام بما يفعله على أفضل وجه.&lt;/p>
&lt;h2 id="المخططون-والوكلاء">المخططون والوكلاء&lt;/h2>
&lt;p>مع ازدياد تعقيد تطبيقات الذكاء الاصطناعي لديك، ستحتاج إلى الذكاء الاصطناعي &lt;strong>لتخطيط المهام متعددة الخطوات وتنفيذها&lt;/strong>. هذا هو المكان الذي يأتي فيه إطار عمل وكيل Semantic Kernel.&lt;/p>
&lt;h3 id="الأساسيات-وكيل-إكمال-الدردشة">الأساسيات: وكيل إكمال الدردشة&lt;/h3>
&lt;p>أبسط نوع وكيل يغلف نموذج إكمال الدردشة مع التعليمات والمكونات الإضافية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Agents&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#pragma&lt;/span> warning disable SKEXP0110
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> agent = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;DevAssistant&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a senior .NET developer assistant. Help users with code
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reviews, architecture decisions, and debugging. Always provide
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> code examples when relevant. Use your available tools to look up
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current information when needed.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Arguments = &lt;span style="color:#ff7b72">new&lt;/span> KernelArguments(settings)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddUserMessage(&lt;span style="color:#a5d6ff">&amp;#34;How should I structure a clean architecture project in .NET 8?&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> agent.InvokeAsync(history))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(message.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.Add(message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="التعاون-متعدد-الوكلاء">التعاون متعدد الوكلاء&lt;/h3>
&lt;p>تصبح الأمور قوية حقًا عندما يكون لديك &lt;strong>وكلاء متعددون يعملون معًا&lt;/strong>. يدعم Semantic Kernel أنماط الدردشة الجماعية حيث يتعاون الوكلاء ذوو التخصصات المختلفة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#pragma&lt;/span> warning disable SKEXP0110
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> codeReviewer = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;CodeReviewer&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You review C&lt;span style="color:#f85149">#&lt;/span> code &lt;span style="color:#ff7b72">for&lt;/span> bugs, performance issues, and best practices.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Be specific about what you find and suggest concrete fixes.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> securityAuditor = &lt;span style="color:#ff7b72">new&lt;/span> ChatCompletionAgent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = &lt;span style="color:#a5d6ff">&amp;#34;SecurityAuditor&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Instructions = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You focus exclusively &lt;span style="color:#ff7b72">on&lt;/span> security vulnerabilities &lt;span style="color:#ff7b72">in&lt;/span> code.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Look &lt;span style="color:#ff7b72">for&lt;/span> injection attacks, authentication issues, data exposure,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and OWASP Top &lt;span style="color:#a5d6ff">10&lt;/span> violations.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> Kernel = kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> groupChat = &lt;span style="color:#ff7b72">new&lt;/span> AgentGroupChat(codeReviewer, securityAuditor)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecutionSettings = &lt;span style="color:#ff7b72">new&lt;/span> AgentGroupChatSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TerminationStrategy = &lt;span style="color:#ff7b72">new&lt;/span> MaximumIterationTerminationStrategy(&lt;span style="color:#a5d6ff">4&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>groupChat.AddChatMessage(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ChatMessageContent(AuthorRole.User, &lt;span style="color:#a5d6ff">&amp;#34;Review this code: ...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> message &lt;span style="color:#ff7b72">in&lt;/span> groupChat.InvokeAsync())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[{message.AuthorName}]: {message.Content}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعد هذا النمط مفيدًا بشكل لا يصدق للمهام المعقدة التي تحتاج إلى مراعاة وجهات نظر أو مجالات خبرة مختلفة. يعمل كل وكيل بموجه النظام الخاص به ويمكن أن يكون لديه مجموعة خاصة به من المكونات الإضافية.&lt;/p>
&lt;h2 id="مثال-من-العالم-الحقيقي-بناء-مساعد-توثيق-المشروع">مثال من العالم الحقيقي: بناء مساعد توثيق المشروع&lt;/h2>
&lt;p>دعونا نربط كل شيء معًا بمثال عملي - مساعد الذكاء الاصطناعي الذي يساعد المطورين على فهم قاعدة التعليمات البرمجية من خلال قراءة الملفات والإجابة على الأسئلة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.ChatCompletion&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.SemanticKernel.Connectors.OpenAI&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.ComponentModel&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Define our plugins&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">FileSystemPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _rootPath;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> FileSystemPlugin(&lt;span style="color:#ff7b72">string&lt;/span> rootPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _rootPath = rootPath;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;read_file&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Reads the contents of a file from the project directory&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ReadFile(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Relative path to the file&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> fullPath = Path.Combine(_rootPath, path);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!File.Exists(fullPath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;File not found: {path}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> content = &lt;span style="color:#ff7b72">await&lt;/span> File.ReadAllTextAsync(fullPath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Truncate very large files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (content.Length &amp;gt; &lt;span style="color:#a5d6ff">8000&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content = content[..&lt;span style="color:#a5d6ff">8000&lt;/span>] + &lt;span style="color:#a5d6ff">&amp;#34;\n... [truncated]&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> content;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;list_files&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Lists files in a directory, optionally filtered by extension&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> ListFiles(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Relative directory path&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> directory,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;File extension filter like &amp;#39;.cs&amp;#39; or &amp;#39;.json&amp;#39;&amp;#34;)] &lt;span style="color:#ff7b72">string?&lt;/span> extension = &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> fullPath = Path.Combine(_rootPath, directory);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Directory.Exists(fullPath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;Directory not found: {directory}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> files = Directory.GetFiles(fullPath, &lt;span style="color:#a5d6ff">&amp;#34;*.*&amp;#34;&lt;/span>, SearchOption.AllDirectories)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(f =&amp;gt; Path.GetRelativePath(_rootPath, f))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Where(f =&amp;gt; extension &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span> || f.EndsWith(extension))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Take(&lt;span style="color:#a5d6ff">50&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n&amp;#34;&lt;/span>, files);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">DocumentationPlugin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [KernelFunction(&amp;#34;generate_summary&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Generates a structured markdown summary for documentation&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GenerateSummaryTemplate(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Name of the component&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> componentName,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Brief description&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> description,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Description(&amp;#34;Key responsibilities as comma-separated values&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> responsibilities)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> items = responsibilities.Split(&lt;span style="color:#a5d6ff">&amp;#39;,&amp;#39;&lt;/span>, StringSplitOptions.TrimEntries);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bullets = &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34;\n&amp;#34;&lt;/span>, items.Select(r =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;- {r}&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> ## {componentName}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> {description}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> ### Responsibilities
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> {bullets}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> ---
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> *Generated documentation — review and expand as needed.*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Wire it all up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = Kernel.CreateBuilder();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: &lt;span style="color:#a5d6ff">&amp;#34;https://your-resource.openai.azure.com/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: &lt;span style="color:#a5d6ff">&amp;#34;your-api-key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromObject(&lt;span style="color:#ff7b72">new&lt;/span> FileSystemPlugin(&lt;span style="color:#a5d6ff">&amp;#34;./src&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Plugins.AddFromType&amp;lt;DocumentationPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> kernel = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> chatService = kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> history = &lt;span style="color:#ff7b72">new&lt;/span> ChatHistory();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>history.AddSystemMessage(&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> You are a codebase documentation assistant. You help developers understand
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> projects &lt;span style="color:#ff7b72">by&lt;/span> reading source files and explaining architecture, patterns,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> and design decisions.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> When asked about code, use your tools to read the actual files rather
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> than guessing. Be specific and reference actual code when possible.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Generate documentation artifacts when asked.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&amp;#34;);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> settings = &lt;span style="color:#ff7b72">new&lt;/span> OpenAIPromptExecutionSettings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;Documentation Assistant ready. Ask me about your codebase!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(&lt;span style="color:#a5d6ff">&amp;#34;Type &amp;#39;exit&amp;#39; to quit.\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">while&lt;/span> (&lt;span style="color:#79c0ff">true&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Write(&lt;span style="color:#a5d6ff">&amp;#34;You: &amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> input = Console.ReadLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(input) || input.Equals(&lt;span style="color:#a5d6ff">&amp;#34;exit&amp;#34;&lt;/span>, StringComparison.OrdinalIgnoreCase))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddUserMessage(input);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> chatService.GetChatMessageContentAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> executionSettings: settings,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kernel: kernel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;\nAssistant: {response.Content}\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> history.AddAssistantMessage(response.Content ?? &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكن لهذا المساعد:- &lt;strong>إدراج الملفات وقراءتها&lt;/strong> من دليل مشروعك&lt;/p>
&lt;ul>
&lt;li>&lt;strong>أجب عن الأسئلة&lt;/strong> حول قاعدة التعليمات البرمجية من خلال قراءة ملفات المصدر الفعلية&lt;/li>
&lt;li>&lt;strong>إنشاء وثائق&lt;/strong> بتنسيق تخفيض السعر&lt;/li>
&lt;li>&lt;strong>حافظ على سياق المحادثة&lt;/strong> حتى تعمل أسئلة المتابعة بشكل طبيعي&lt;/li>
&lt;/ul>
&lt;p>يقرر الذكاء الاصطناعي تلقائيًا متى يتم الاتصال بـ &lt;code>read_file&lt;/code> أو &lt;code>list_files&lt;/code> أو &lt;code>generate_summary&lt;/code> بناءً على ما تطلبه. اسأله &amp;ldquo;ماذا تفعل خدمة OrderService؟&amp;rdquo; وسوف يقرأ الملف ويحلله ويشرحه. اطلب منه &amp;ldquo;إنشاء وثائق لوحدة المصادقة&amp;rdquo; وسوف يستكشف الملفات ويفهم البنية وينتج ملخصًا منسقًا.&lt;/p>
&lt;h2 id="نصائح-للإنتاج">نصائح للإنتاج&lt;/h2>
&lt;p>قبل أن تقوم بشحن تطبيق Semantic Kernel الخاص بك، تعلمت بعض الأشياء بالطريقة الصعبة:&lt;/p>
&lt;p>&lt;strong>استخدم حقن التبعية بشكل صحيح.&lt;/strong> في تطبيقات ASP.NET Core، قم بتسجيل Kernel والخدمات في حاوية DI الخاصة بك بدلاً من إنشائها في السطر:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddKernel()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddAzureOpenAIChatCompletion(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploymentName: &lt;span style="color:#a5d6ff">&amp;#34;gpt-4o&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> endpoint: configuration[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:Endpoint&amp;#34;&lt;/span>]!,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apiKey: configuration[&lt;span style="color:#a5d6ff">&amp;#34;AzureOpenAI:ApiKey&amp;#34;&lt;/span>]!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Plugins.AddFromType&amp;lt;TimePlugin&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddFromType&amp;lt;OrderPlugin&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>** التعامل مع الأخطاء بأمان. ** يمكن أن تفشل مكالمات LLM، أو تنقضي المهلة، أو ترجع نتائج غير متوقعة. قم بتجميع دعواتك في كتل محاولة الالتقاط وتنفيذ سياسات إعادة المحاولة باستخدام Polly أو ميزات المرونة المضمنة.&lt;/p>
&lt;p>&lt;strong>مراقبة استخدام الرمز المميز.&lt;/strong> كل مطالبة، وكل وصف وظيفة، وكل جزء من سجل الدردشة يستهلك الرموز المميزة. استخدم المرشحات لتسجيل الاستخدام وتتبعه:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>kernel.FunctionInvocationFilters.Add(&lt;span style="color:#ff7b72">new&lt;/span> LoggingFilter());
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>حافظ على دقة أوصاف وظيفتك.&lt;/strong> تؤدي الأوصاف الغامضة إلى استدعاء وظائف الذكاء الاصطناعي بشكل غير صحيح. اختبر أوصافك عن طريق طرح السؤال التالي: &amp;ldquo;إذا قرأت الوصف فقط، فهل سأعرف بالضبط متى وكيف أستخدم هذه الوظيفة؟&amp;rdquo;&lt;/p>
&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>Semantic Kernel هي إحدى تلك المكتبات التي تغير بشكل جذري طريقة تفكيرك في إنشاء التطبيقات. إنه ليس مجرد غلاف لواجهة برمجة التطبيقات - إنه إطار عمل للتنسيق يتيح لك إنشاء إمكانات الذكاء الاصطناعي باستخدام التعليمات البرمجية التقليدية بطريقة قابلة للصيانة والاختبار وجاهزة للإنتاج.&lt;/p>
&lt;p>أكثر ما أحبه فيه هو أنه يحترم النظام البيئي .NET. فهو يستخدم الأنماط التي تعرفها بالفعل - حقن التبعية، والسمات، وعدم المزامنة/الانتظار، والواجهات - ويوسعها لتشمل عالم الذكاء الاصطناعي. ليس عليك أن تتعلم نموذجًا جديدًا تمامًا؛ ما عليك سوى إضافة الذكاء الاصطناعي كقدرة أخرى في مجموعة أدواتك.&lt;/p>
&lt;p>إذا كنت تقوم بإنشاء تطبيقات .NET ولم تستكشف Semantic Kernel بعد، فهذا هو الوقت المناسب. تتميز مجموعة SDK بأنها مستقرة، والمجتمع نشط، والأنماط التي تتيحها - بدءًا من التنسيق السريع البسيط وحتى التعاون بين الوكلاء المتعددين - أصبحت مهارات أساسية للمطورين المعاصرين.&lt;/p>
&lt;p>ابدأ صغيرًا. قم بإنشاء Kernel، وقم بتسجيل مكون إضافي، وشاهد الذكاء الاصطناعي وهو يتصل بالرمز الخاص بك. بمجرد النقر على ذلك، ستبدأ في رؤية فرص لإضافة الذكاء في كل مكان في تطبيقاتك.&lt;/p>
&lt;p>تعد &lt;a href="https://learn.microsoft.com/semantic-kernel/overview/">الوثائق الرسمية&lt;/a> و&lt;a href="https://github.com/microsoft/semantic-kernel">مستودع GitHub&lt;/a> من الموارد الممتازة لمواصلة رحلتك. بناء سعيد!&lt;/p></content:encoded><category>AI</category><category>.NET</category><category>Semantic Kernel</category><category>Azure</category></item><item><title>وراثة المكونات في Blazor</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-inherit-components/</link><pubDate>Thu, 04 Sep 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-inherit-components/</guid><description>توسيع وإعادة استخدام مكونات Blazor من خلال الوراثة باستخدام ComponentBase والفئات الأساسية المشتركة.</description><content:encoded>&lt;p>كنت أقوم بإنشاء مشروع يحتوي على مجموعة من صفحات النماذج، وكان لكل واحدة منها نفس منطق حالة التحميل، ونفس معالجة الأخطاء، ونفس إشعارات الخبز المحمص. بدا كل هذا خطأً في النسخ واللصق، لذلك بحثت في وراثة المكونات في Blazor. تبين أن الأمر بسيط جدًا نظرًا لأن مكونات Blazor هي مجرد فئات C#.&lt;/p>
&lt;p>#الأساسيات&lt;/p>
&lt;p>يرث كل مكون من مكونات Blazor من &lt;code>ComponentBase&lt;/code> افتراضيًا. يمكنك إنشاء الفئة الأساسية الخاصة بك والتي تمتد &lt;code>ComponentBase&lt;/code> ومن ثم ترث مكوناتك منها.&lt;/p>
&lt;p>لنفترض أن معظم صفحاتنا تحتاج إلى حالة التحميل ومعالجة الأخطاء. يمكننا إنشاء فئة أساسية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PageBase&lt;/span> : ComponentBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsLoading { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> ErrorMessage { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task LoadDataAsync(Func&amp;lt;Task&amp;gt; action)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IsLoading = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ErrorMessage = &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> action();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ErrorMessage = ex.Message;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IsLoading = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="استخدام-الفئة-الأساسية">استخدام الفئة الأساسية&lt;/h1>
&lt;p>الآن في أي مكون من مكونات الصفحة، بدلاً من الوراثة من &lt;code>ComponentBase&lt;/code>، نرث من &lt;code>PageBase&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/users&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inherits PageBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@if (IsLoading)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;spinner&amp;#34;&lt;/span>&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (ErrorMessage &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;alert alert-danger&amp;#34;&lt;/span>&amp;gt;@ErrorMessage&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @foreach (&lt;span style="color:#ff7b72">var&lt;/span> user &lt;span style="color:#ff7b72">in&lt;/span> users)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;li&amp;gt;@user.Name&amp;lt;/li&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/ul&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;User&amp;gt; users = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> LoadDataAsync(&lt;span style="color:#ff7b72">async&lt;/span> () =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> users = &lt;span style="color:#ff7b72">await&lt;/span> Http.GetFromJsonAsync&amp;lt;List&amp;lt;User&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/users&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>التوجيه &lt;code>@inherits PageBase&lt;/code> هو المفتاح. يطلب من Blazor استخدام الفئة الأساسية لدينا بدلاً من الفئة الافتراضية &lt;code>ComponentBase&lt;/code>. الآن نحصل على &lt;code>IsLoading&lt;/code> و &lt;code>ErrorMessage&lt;/code> و &lt;code>LoadDataAsync()&lt;/code> مجانًا في كل صفحة ترث منه.&lt;/p>
&lt;h1 id="حقن-الخدمات-في-الفئة-الأساسية">حقن الخدمات في الفئة الأساسية&lt;/h1>
&lt;p>يمكنك أيضًا إدخال الخدمات في الفئة الأساسية بحيث تكون متاحة لجميع المكونات الفرعية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PageBase&lt;/span> : ComponentBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> NavigationManager Navigation { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">default&lt;/span>!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> IToastService Toast { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">default&lt;/span>!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsLoading { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> ErrorMessage { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> NavigateBack() =&amp;gt; Navigation.NavigateTo(&lt;span style="color:#a5d6ff">&amp;#34;javascript:history.back()&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShowSuccess(&lt;span style="color:#ff7b72">string&lt;/span> message) =&amp;gt; Toast.ShowSuccess(message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كل مكون يرث من &lt;code>PageBase&lt;/code> لديه الآن إمكانية الوصول إلى &lt;code>Navigation&lt;/code>، &lt;code>Toast&lt;/code>، &lt;code>NavigateBack()&lt;/code>، و &lt;code>ShowSuccess()&lt;/code> دون الحاجة إلى حقن أي شيء.&lt;/p>
&lt;h1 id="التعمق-أكثر-مع-الفئات-الأساسية-العامة">التعمق أكثر مع الفئات الأساسية العامة&lt;/h1>
&lt;p>يمكنك أيضًا إنشاء فئات أساسية عامة لأنماط CRUD الشائعة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CrudPageBase&lt;/span>&amp;lt;T&amp;gt; : PageBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> List&amp;lt;T&amp;gt; Items { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> T? SelectedItem { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; FetchItems();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> Task DeleteItem(T item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> LoadDataAsync(&lt;span style="color:#ff7b72">async&lt;/span> () =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Items = &lt;span style="color:#ff7b72">await&lt;/span> FetchItems();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnDelete(T item)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> LoadDataAsync(&lt;span style="color:#ff7b72">async&lt;/span> () =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> DeleteItem(item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Items = &lt;span style="color:#ff7b72">await&lt;/span> FetchItems();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ShowSuccess(&lt;span style="color:#a5d6ff">&amp;#34;Item deleted.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ثم تصبح صفحتك الفعلية نظيفة للغاية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inherits CrudPageBase&amp;lt;Product&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>* just the markup, all logic lives &lt;span style="color:#ff7b72">in&lt;/span> the &lt;span style="color:#ff7b72">base&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> *&lt;span style="color:#f85149">@&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt; FetchItems()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Http.GetFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task DeleteItem(Product item)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Http.DeleteAsync(&lt;span style="color:#a5d6ff">$&amp;#34;api/products/{item.Id}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>#متى تستخدمه ومتى لا تستخدمه&lt;/p>
&lt;p>يعد وراثة المكونات أمرًا رائعًا للسلوك المشترك مثل حالات التحميل أو معالجة الأخطاء أو عمليات التحقق من المصادقة أو أنماط CRUD. لكن لا تبالغ في استخدام التسلسلات الهرمية العميقة للوراثة - إذا وجدت نفسك تتعمق أكثر من مستويين، فمن الأفضل أن تكون في وضع أفضل مع التركيب (مثل نهج &lt;code>LoadingComponent&lt;/code> من منشور سابق).&lt;/p>
&lt;p>عادةً ما أحتفظ بها في فئة أساسية واحدة لكل &amp;ldquo;نوع&amp;rdquo; من الصفحة: &lt;code>PageBase&lt;/code> للصفحات العادية، &lt;code>FormPageBase&lt;/code> للنماذج، وهذا كل ما في الأمر.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! لا تتردد في الاتصال بي على أي وسائل التواصل الاجتماعي على **@emimontesdeoca **.&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/#inheritance">وراثة مكون ASP.NET Core Razor&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>C#</category></item><item><title>.NET Aspire: بناء التطبيقات السحابية الأصلية بالطريقة الصحيحة</title><link>https://emimontesdeoca.github.io/ar/posts/dotnet-aspire-cloud-native/</link><pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/dotnet-aspire-cloud-native/</guid><description>دليل متعمق لـ .NET Aspire - المجموعة المصممة لبناء تطبيقات يمكن ملاحظتها وجاهزة للإنتاج وموزعة في .NET.</description><content:encoded>&lt;p>إذا سبق لك إنشاء تطبيق موزع في .NET، فأنت تعرف التدريبات. يمكنك تشغيل واجهة برمجة تطبيقات الويب، وإضافة عامل في الخلفية، واستخدام Redis للتخزين المؤقت، وPostgreSQL للاستمرارية، وربما RabbitMQ للمراسلة - وفجأة تقضي وقتًا أطول في توصيل البنية التحتية بدلاً من كتابة منطق الأعمال. سلاسل الاتصال متناثرة عبر ملفات &lt;code>appsettings.json&lt;/code>، والفحوصات الصحية التي نسيت تهيئتها، وإمكانية المراقبة التي تعد دائمًا &amp;ldquo;مشكلة السباق التالي.&amp;rdquo;&lt;/p>
&lt;p>لقد كنت هناك. مرات أكثر مما أود أن أعترف به.&lt;/p>
&lt;p>هذه هي بالضبط المشكلة التي تم تصميم &lt;strong>.NET Aspire&lt;/strong> لحلها. بعد تشغيله في مرحلة الإنتاج لعدة أشهر، أريد أن أشارككم ما تعلمته - الأشياء الجيدة والعظيمة والصعبة.&lt;/p>
&lt;h2 id="ما-هو-net-aspire">ما هو .NET Aspire؟&lt;/h2>
&lt;p>إن .NET Aspire هو عبارة عن حزمة مدروسة لإنشاء تطبيقات يمكن ملاحظتها وجاهزة للإنتاج وموزعة باستخدام .NET. إنه ليس إطار عمل بالمعنى التقليدي — فهو لا يحل محل ASP.NET Core ولا يجبرك على اتباع نموذج برمجة جديد. وبدلاً من ذلك، فهو يرتكز على ما تعرفه بالفعل ويسد الفجوات التي كانت موجودة دائمًا عند إنشاء تطبيقات سحابية أصلية.&lt;/p>
&lt;p>في جوهرها، أسباير يمنحك أربعة أشياء:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>AppHost&lt;/strong> — مشروع يحدد هيكل التطبيق الموزع بالكامل. ما هي الخدمات الموجودة، وما الذي تعتمد عليه، وكيفية اتصالها.&lt;/li>
&lt;li>&lt;strong>افتراضيات الخدمة&lt;/strong> — مشروع مشترك يقوم بتكوين الاهتمامات الشاملة مثل فحوصات السلامة وسياسات المرونة وOpenTelemetry — مرة واحدة لجميع خدماتك.&lt;/li>
&lt;li>&lt;strong>المكونات&lt;/strong> — حزم NuGet التي توفر عمليات تكامل موحدة مع خدمات الدعم مثل Redis وPostgreSQL وRabbitMQ وAzure Storage والمزيد.&lt;/li>
&lt;li>&lt;strong>لوحة تحكم المطور&lt;/strong> — واجهة مستخدم في الوقت الفعلي تعرض السجلات والتتبعات والمقاييس لتطبيقك الموزع بالكامل أثناء التطوير المحلي.&lt;/li>
&lt;/ol>
&lt;p>الفلسفة بسيطة: إذا كان كل تطبيق سحابي .NET يحتاج إلى هذه الأشياء، فلماذا نقوم جميعًا بتنفيذها من الصفر في كل مرة؟&lt;/p>
&lt;h2 id="إعداد-مشروعك-الأول-أسباير">إعداد مشروعك الأول أسباير&lt;/h2>
&lt;p>البدء واضح ومباشر. ستحتاج إلى .NET 8 أو إصدار أحدث وتثبيت حمل عمل Aspire:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet workload update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet workload install aspire
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن أنشئ مشروعًا جديدًا لـ Aspire:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new aspire-starter -n MyCloudApp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يؤدي هذا إلى إنشاء حل بأربعة مشاريع:&lt;/p>
&lt;ul>
&lt;li>&lt;code>MyCloudApp.AppHost&lt;/code> — المنسق&lt;/li>
&lt;li>&lt;code>MyCloudApp.ServiceDefaults&lt;/code> — التكوين المشترك&lt;/li>
&lt;li>&lt;code>MyCloudApp.ApiService&lt;/code> — نموذج لواجهة برمجة تطبيقات الويب&lt;/li>
&lt;li>&lt;code>MyCloudApp.Web&lt;/code> — واجهة Blazor الأمامية&lt;/li>
&lt;/ul>
&lt;p>قم بتشغيل AppHost وسترى على الفور لوحة تحكم Aspire مفتوحة في متصفحك، وتعرض جميع خدماتك وسجلاتها وحالتها الصحية. لا يوجد ملف Docker Compose. لا توجد إدارة يدوية للمنافذ. إنه يعمل فقط.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet run --project MyCloudApp.AppHost
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تلك التجربة الأولى هي ما أذهلتني. في أقل من دقيقة، سيكون لديك تطبيق موزع منسق بالكامل مع إمكانية المراقبة.&lt;/p>
&lt;h2 id="نمط-apphost">نمط AppHost&lt;/h2>
&lt;p>AppHost هو المكان الذي يعيش فيه السحر. إنه تطبيق وحدة تحكم صغير يستخدم نمط البناء لتحديد طوبولوجيا تطبيقك الموزع - ما هي الموارد الموجودة وكيفية اتصال الخدمات بها.&lt;/p>
&lt;p>إليك ما يبدو عليه AppHost الواقعي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = DistributedApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Infrastructure resources&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithPgAdmin()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> redis = builder.AddRedis(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> rabbitmq = builder.AddRabbitMQ(&lt;span style="color:#a5d6ff">&amp;#34;messaging&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithManagementPlugin();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Application services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> catalogApi = builder.AddProject&amp;lt;Projects.CatalogApi&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;catalog-api&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(postgres)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(redis)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(rabbitmq);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> orderApi = builder.AddProject&amp;lt;Projects.OrderApi&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;order-api&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(postgres)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(rabbitmq);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> frontend = builder.AddProject&amp;lt;Projects.WebFrontend&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;frontend&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithExternalHttpEndpoints()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(catalogApi)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(orderApi);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Build().Run();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```اقرأ&lt;/span> &lt;span style="color:#f85149">هذا&lt;/span> &lt;span style="color:#f85149">الرمز&lt;/span> &lt;span style="color:#f85149">بصوت&lt;/span> &lt;span style="color:#f85149">عالٍ&lt;/span>. &lt;span style="color:#f85149">إنه&lt;/span> &lt;span style="color:#f85149">يوثق&lt;/span> &lt;span style="color:#f85149">نفسه&lt;/span> &lt;span style="color:#f85149">عمليا&lt;/span>. &lt;span style="color:#a5d6ff">&amp;#34;تشير واجهة برمجة تطبيقات الكتالوج إلى PostgreSQL وRedis وRabbitMQ. وتشير الواجهة الأمامية إلى واجهة برمجة تطبيقات الكتالوج وواجهة برمجة تطبيقات الطلب.&amp;#34;&lt;/span> &lt;span style="color:#f85149">هذه&lt;/span> &lt;span style="color:#f85149">هي&lt;/span> &lt;span style="color:#f85149">الهندسة&lt;/span> &lt;span style="color:#f85149">المعمارية&lt;/span> &lt;span style="color:#f85149">الخاصة&lt;/span> &lt;span style="color:#f85149">بك،&lt;/span> &lt;span style="color:#f85149">معبرًا&lt;/span> &lt;span style="color:#f85149">عنها&lt;/span> &lt;span style="color:#f85149">بالكود&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">بعض&lt;/span> &lt;span style="color:#f85149">الأشياء&lt;/span> &lt;span style="color:#f85149">الجديرة&lt;/span> &lt;span style="color:#f85149">بالملاحظة&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">`&lt;/span>WithReference&lt;span style="color:#f85149">`&lt;/span>** &lt;span style="color:#f85149">يقوم&lt;/span> &lt;span style="color:#f85149">برفع&lt;/span> &lt;span style="color:#f85149">الأحمال&lt;/span> &lt;span style="color:#f85149">الثقيلة&lt;/span>. &lt;span style="color:#f85149">يقوم&lt;/span> &lt;span style="color:#f85149">تلقائيًا&lt;/span> &lt;span style="color:#f85149">بإدخال&lt;/span> &lt;span style="color:#f85149">سلاسل&lt;/span> &lt;span style="color:#f85149">الاتصال&lt;/span> &lt;span style="color:#f85149">وعناوين&lt;/span> URL &lt;span style="color:#f85149">الخاصة&lt;/span> &lt;span style="color:#f85149">بالخدمة&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">المشروع&lt;/span> &lt;span style="color:#f85149">المستهلك&lt;/span> &lt;span style="color:#f85149">عبر&lt;/span> &lt;span style="color:#f85149">متغيرات&lt;/span> &lt;span style="color:#f85149">البيئة&lt;/span> &lt;span style="color:#f85149">والتكوين&lt;/span>. &lt;span style="color:#f85149">لا&lt;/span> &lt;span style="color:#f85149">تحتاج&lt;/span> &lt;span style="color:#f85149">خدماتك&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">معرفة&lt;/span> *&lt;span style="color:#f85149">مكان&lt;/span>* &lt;span style="color:#f85149">تشغيل&lt;/span> Redis &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">حيث&lt;/span> &lt;span style="color:#f85149">تتولى&lt;/span> Aspire &lt;span style="color:#f85149">ذلك&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">`&lt;/span>WithPgAdmin()&lt;span style="color:#f85149">`&lt;/span>** &lt;span style="color:#f85149">و&lt;/span> **&lt;span style="color:#f85149">`&lt;/span>WithManagementPlugin()&lt;span style="color:#f85149">`&lt;/span>** &lt;span style="color:#f85149">يقومان&lt;/span> &lt;span style="color:#f85149">بتدوير&lt;/span> &lt;span style="color:#f85149">واجهات&lt;/span> &lt;span style="color:#f85149">المستخدم&lt;/span> &lt;span style="color:#f85149">الإدارية&lt;/span> &lt;span style="color:#f85149">لـ&lt;/span> PostgreSQL &lt;span style="color:#f85149">و&lt;/span>RabbitMQ &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">جانب&lt;/span> &lt;span style="color:#f85149">الخدمات&lt;/span> &lt;span style="color:#f85149">الفعلية&lt;/span>. &lt;span style="color:#f85149">أثناء&lt;/span> &lt;span style="color:#f85149">التطوير،&lt;/span> &lt;span style="color:#f85149">هذه&lt;/span> &lt;span style="color:#f85149">لا&lt;/span> &lt;span style="color:#f85149">تقدر&lt;/span> &lt;span style="color:#f85149">بثمن&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">`&lt;/span>WithExternalHttpEndpoints()&lt;span style="color:#f85149">`&lt;/span>** &lt;span style="color:#f85149">يضع&lt;/span> &lt;span style="color:#f85149">علامة&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">الخدمة&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">أنها&lt;/span> &lt;span style="color:#f85149">يمكن&lt;/span> &lt;span style="color:#f85149">الوصول&lt;/span> &lt;span style="color:#f85149">إليها&lt;/span> &lt;span style="color:#f85149">خارجيًا،&lt;/span> &lt;span style="color:#f85149">وهو&lt;/span> &lt;span style="color:#f85149">أمر&lt;/span> &lt;span style="color:#f85149">مهم&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">وقت&lt;/span> &lt;span style="color:#f85149">النشر&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> &lt;span style="color:#f85149">تكوين&lt;/span> &lt;span style="color:#f85149">الموارد&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">يمكنك&lt;/span> &lt;span style="color:#f85149">تكوين&lt;/span> &lt;span style="color:#f85149">الموارد&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">خلال&lt;/span> &lt;span style="color:#f85149">التحكم&lt;/span> &lt;span style="color:#f85149">الدقيق&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithEnvironment(&lt;span style="color:#a5d6ff">&amp;#34;POSTGRES_MAX_CONNECTIONS&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;200&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithDataVolume(&lt;span style="color:#a5d6ff">&amp;#34;postgres-data&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> redis = builder.AddRedis(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithDataVolume(&lt;span style="color:#a5d6ff">&amp;#34;redis-data&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تضمن أحجام البيانات بقاء بيانات التطوير المحلية الخاصة بك بعد إعادة تشغيل الحاوية. تفاصيل صغيرة، تحسن كبير في نوعية الحياة.&lt;/p>
&lt;h2 id="افتراضيات-الخدمة-البطل-المجهول">افتراضيات الخدمة: البطل المجهول&lt;/h2>
&lt;p>مشروع &lt;code>ServiceDefaults&lt;/code> هو الجزء الأكثر استخفافًا في أسباير. إنها مكتبة مشتركة تشير إليها كل خدمة في الحل الخاص بك، وتقوم بتكوين جميع الاهتمامات الشاملة التي قد تنساها أو تنفذها بشكل غير متسق.&lt;/p>
&lt;p>إليك ما يبدو عليه &lt;code>Extensions.cs&lt;/code> النموذجي في ServiceDefaults:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Extensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IHostApplicationBuilder AddServiceDefaults(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IHostApplicationBuilder builder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.ConfigureOpenTelemetry();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.AddDefaultHealthChecks();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.AddServiceDiscovery();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.ConfigureHttpClientDefaults(http =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> http.AddStandardResilienceHandler();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> http.AddServiceDiscovery();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IHostApplicationBuilder ConfigureOpenTelemetry(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IHostApplicationBuilder builder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Logging.AddOpenTelemetry(logging =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logging.IncludeFormattedMessage = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logging.IncludeScopes = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.AddOpenTelemetry()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithMetrics(metrics =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> metrics.AddAspNetCoreInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddHttpClientInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddRuntimeInstrumentation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTracing(tracing =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tracing.AddAspNetCoreInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddHttpClientInstrumentation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddGrpcClientInstrumentation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.AddOpenTelemetryExporters();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IHostApplicationBuilder AddDefaultHealthChecks(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IHostApplicationBuilder builder)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> builder.Services.AddHealthChecks()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddCheck(&lt;span style="color:#a5d6ff">&amp;#34;self&amp;#34;&lt;/span>, () =&amp;gt; HealthCheckResult.Healthy(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&amp;#34;live&amp;#34;]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ثم في &lt;code>Program.cs&lt;/code> لكل خدمة، سطر واحد يفعل كل شيء:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddServiceDefaults();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Your service-specific configuration...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDefaultEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تمنحك هذه المكالمة الفردية &lt;code>AddServiceDefaults()&lt;/code> ما يلي:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OpenTelemetry&lt;/strong> مع التسجيل المنظم والمقاييس والتتبع الموزع&lt;/li>
&lt;li>&lt;strong>فحوصات السلامة&lt;/strong> مع نقاط النهاية الحيوية والاستعداد&lt;/li>
&lt;li>&lt;strong>اكتشاف الخدمة&lt;/strong> حتى تتمكن الخدمات من العثور على بعضها البعض بالاسم&lt;/li>
&lt;li>&lt;strong>سياسات المرونة&lt;/strong> في جميع مكالمات HTTP الصادرة (إعادة المحاولة، وقواطع الدائرة، والمهلات)&lt;/li>
&lt;/ul>
&lt;p>هذه هي الأشياء التي تفصل بين العرض التوضيحي &amp;ldquo;يعمل على جهازي&amp;rdquo; وبين النظام الجاهز للإنتاج. وأسباير تجعلها الخيار الافتراضي، وليس فكرة لاحقة.&lt;/p>
&lt;h2 id="مكونات-أسباير">مكونات أسباير&lt;/h2>
&lt;p>مكونات Aspire هي حزم NuGet التي تعمل على توحيد كيفية اتصال خدماتك بالبنية التحتية الداعمة. إنها أكثر من مجرد مكتبات عملاء - فهي تشمل عمليات التحقق من السلامة والتسجيل والتتبع والمرونة القابلة للتكوين خارج الصندوق.&lt;/p>
&lt;h3 id="ريديس">ريديس&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddServiceDefaults();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddRedisDistributedCache(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا كل شيء. تأتي سلسلة الاتصال من AppHost عبر &lt;code>WithReference&lt;/code>. يسجل المكون &lt;code>IDistributedCache&lt;/code> مدعومًا بواسطة Redis، مع فحوصات السلامة وأجهزة OpenTelemetry التي تم توصيلها بالفعل.&lt;/p>
&lt;p>يمكنك أيضًا استخدام Redis للتخزين المؤقت للمخرجات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddRedisOutputCache(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="postgresql-مع-entity-framework-core">PostgreSQL مع Entity Framework Core&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddNpgsqlDbContext&amp;lt;CatalogDbContext&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>, settings =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> settings.DisableRetry = &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يؤدي هذا إلى تسجيل &lt;code>DbContext&lt;/code> الخاص بك من خلال اتصال بقاعدة بيانات &lt;code>catalogdb&lt;/code> المحددة في AppHost. ويتضمن تجميع الاتصالات والفحوصات الصحية وسياسات إعادة المحاولة.&lt;/p>
&lt;h3 id="رابيتmq">رابيتMQ&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.AddRabbitMQClient(&lt;span style="color:#a5d6ff">&amp;#34;messaging&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تسجيل &lt;code>IConnection&lt;/code> من مكتبة عملاء RabbitMQ، تم تكوينها بالكامل وفحص صحتها.&lt;/p>
&lt;h3 id="تكاملات-أزور">تكاملات أزور&lt;/h3>
&lt;p>تمتلك Aspire أيضًا مكونات من الدرجة الأولى لخدمات Azure:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Azure Blob Storage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureBlobClient(&lt;span style="color:#a5d6ff">&amp;#34;blobs&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Azure Service Bus&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureServiceBusClient(&lt;span style="color:#a5d6ff">&amp;#34;servicebus&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Azure Key Vault&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddAzureKeyVaultClient(&lt;span style="color:#a5d6ff">&amp;#34;secrets&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```النمط&lt;/span> &lt;span style="color:#f85149">دائمًا&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">نفسه&lt;/span>: &lt;span style="color:#f85149">سطر&lt;/span> &lt;span style="color:#f85149">واحد&lt;/span> &lt;span style="color:#f85149">في&lt;/span> AppHost &lt;span style="color:#f85149">لتحديد&lt;/span> &lt;span style="color:#f85149">المورد،&lt;/span> &lt;span style="color:#f85149">وسطر&lt;/span> &lt;span style="color:#f85149">واحد&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الخدمة&lt;/span> &lt;span style="color:#f85149">المستهلكة&lt;/span> &lt;span style="color:#f85149">لاستخدامه&lt;/span>. &lt;span style="color:#f85149">تتدفق&lt;/span> &lt;span style="color:#f85149">تفاصيل&lt;/span> &lt;span style="color:#f85149">الاتصال&lt;/span> &lt;span style="color:#f85149">تلقائيًا&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> &lt;span style="color:#f85149">لوحة&lt;/span> &lt;span style="color:#f85149">تحكم&lt;/span> &lt;span style="color:#f85149">المطور&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">تعد&lt;/span> &lt;span style="color:#f85149">لوحة&lt;/span> &lt;span style="color:#f85149">معلومات&lt;/span> Aspire &lt;span style="color:#f85149">واحدة&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">تلك&lt;/span> &lt;span style="color:#f85149">الميزات&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تبدو&lt;/span> &lt;span style="color:#f85149">وكأنها&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">الأشياء&lt;/span> &lt;span style="color:#f85149">الرائعة&lt;/span> &lt;span style="color:#f85149">حتى&lt;/span> &lt;span style="color:#f85149">تستخدمها&lt;/span> &lt;span style="color:#f85149">فعليًا&lt;/span> - &lt;span style="color:#f85149">فلا&lt;/span> &lt;span style="color:#f85149">يمكنك&lt;/span> &lt;span style="color:#f85149">تخيل&lt;/span> &lt;span style="color:#f85149">العمل&lt;/span> &lt;span style="color:#f85149">بدونها&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">عند&lt;/span> &lt;span style="color:#f85149">تشغيل&lt;/span> AppHost &lt;span style="color:#f85149">محليًا،&lt;/span> &lt;span style="color:#f85149">يتم&lt;/span> &lt;span style="color:#f85149">تشغيل&lt;/span> &lt;span style="color:#f85149">لوحة&lt;/span> &lt;span style="color:#f85149">المعلومات&lt;/span> &lt;span style="color:#f85149">وتمنحك&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">نظرة&lt;/span> &lt;span style="color:#f85149">عامة&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">الموارد&lt;/span>** &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">نظرة&lt;/span> &lt;span style="color:#f85149">سريعة&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">جميع&lt;/span> &lt;span style="color:#f85149">خدماتك&lt;/span> &lt;span style="color:#f85149">وبنيتك&lt;/span> &lt;span style="color:#f85149">التحتية،&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">مؤشرات&lt;/span> &lt;span style="color:#f85149">الحالة&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">السجلات&lt;/span> &lt;span style="color:#f85149">المنظمة&lt;/span>** &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">سجل&lt;/span> &lt;span style="color:#f85149">يتدفق&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الوقت&lt;/span> &lt;span style="color:#f85149">الفعلي&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">كل&lt;/span> &lt;span style="color:#f85149">خدمة،&lt;/span> &lt;span style="color:#f85149">قابل&lt;/span> &lt;span style="color:#f85149">للتصفية&lt;/span> &lt;span style="color:#f85149">والبحث&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">الآثار&lt;/span> &lt;span style="color:#f85149">الموزعة&lt;/span>** &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">آثار&lt;/span> &lt;span style="color:#f85149">الطلب&lt;/span> &lt;span style="color:#f85149">الشاملة&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تغطي&lt;/span> &lt;span style="color:#f85149">خدمات&lt;/span> &lt;span style="color:#f85149">متعددة،&lt;/span> &lt;span style="color:#f85149">والتي&lt;/span> &lt;span style="color:#f85149">يتم&lt;/span> &lt;span style="color:#f85149">تصورها&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">شكل&lt;/span> &lt;span style="color:#f85149">مخططات&lt;/span> &lt;span style="color:#f85149">توضيحية&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">المقاييس&lt;/span>** &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">معدلات&lt;/span> &lt;span style="color:#f85149">طلبات&lt;/span> HTTP&lt;span style="color:#f85149">،&lt;/span> &lt;span style="color:#f85149">ومعدلات&lt;/span> &lt;span style="color:#f85149">الخطأ،&lt;/span> &lt;span style="color:#f85149">وزمن&lt;/span> &lt;span style="color:#f85149">الاستجابة،&lt;/span> &lt;span style="color:#f85149">والمقاييس&lt;/span> &lt;span style="color:#f85149">المخصصة&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">الوقت&lt;/span> &lt;span style="color:#f85149">الفعلي&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- **&lt;span style="color:#f85149">سجلات&lt;/span> &lt;span style="color:#f85149">وحدة&lt;/span> &lt;span style="color:#f85149">التحكم&lt;/span>** &lt;span style="color:#f85149">—&lt;/span> stdout/stderr &lt;span style="color:#f85149">الأولي&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">كل&lt;/span> &lt;span style="color:#f85149">حاوية&lt;/span> &lt;span style="color:#f85149">ومشروع&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">التتبع&lt;/span> &lt;span style="color:#f85149">الموزع&lt;/span> &lt;span style="color:#f85149">له&lt;/span> &lt;span style="color:#f85149">قيمة&lt;/span> &lt;span style="color:#f85149">خاصة&lt;/span>. &lt;span style="color:#f85149">عندما&lt;/span> &lt;span style="color:#f85149">يصل&lt;/span> &lt;span style="color:#f85149">طلب&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">الواجهة&lt;/span> &lt;span style="color:#f85149">الأمامية&lt;/span> &lt;span style="color:#f85149">لديك،&lt;/span> &lt;span style="color:#f85149">يتدفق&lt;/span> &lt;span style="color:#f85149">عبر&lt;/span> &lt;span style="color:#f85149">واجهة&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">تطبيقات&lt;/span> &lt;span style="color:#f85149">الكتالوج،&lt;/span> &lt;span style="color:#f85149">ويلمس&lt;/span> Redis &lt;span style="color:#f85149">و&lt;/span>PostgreSQL - &lt;span style="color:#f85149">ترى&lt;/span> &lt;span style="color:#f85149">السلسلة&lt;/span> &lt;span style="color:#f85149">بأكملها&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">توقيت&lt;/span> &lt;span style="color:#f85149">كل&lt;/span> &lt;span style="color:#f85149">قفزة&lt;/span>. &lt;span style="color:#f85149">لا&lt;/span> &lt;span style="color:#f85149">مزيد&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">التخمين&lt;/span> &lt;span style="color:#f85149">أين&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">عنق&lt;/span> &lt;span style="color:#f85149">الزجاجة&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">لقد&lt;/span> &lt;span style="color:#f85149">وجدت&lt;/span> &lt;span style="color:#f85149">لوحة&lt;/span> &lt;span style="color:#f85149">المعلومات&lt;/span> &lt;span style="color:#f85149">مفيدة&lt;/span> &lt;span style="color:#f85149">للغاية&lt;/span> &lt;span style="color:#f85149">أثناء&lt;/span> &lt;span style="color:#f85149">تصحيح&lt;/span> &lt;span style="color:#f85149">الأخطاء&lt;/span>. &lt;span style="color:#f85149">بدلاً&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">ترك&lt;/span> &lt;span style="color:#f85149">نوافذ&lt;/span> &lt;span style="color:#f85149">طرفية&lt;/span> &lt;span style="color:#f85149">متعددة&lt;/span> &lt;span style="color:#f85149">أو&lt;/span> &lt;span style="color:#f85149">القفز&lt;/span> &lt;span style="color:#f85149">بين&lt;/span> &lt;span style="color:#f85149">ملفات&lt;/span> &lt;span style="color:#f85149">السجل،&lt;/span> &lt;span style="color:#f85149">يكون&lt;/span> &lt;span style="color:#f85149">كل&lt;/span> &lt;span style="color:#f85149">شيء&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">مكان&lt;/span> &lt;span style="color:#f85149">واحد&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">معرفات&lt;/span> &lt;span style="color:#f85149">الارتباط&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تربط&lt;/span> &lt;span style="color:#f85149">الأحداث&lt;/span> &lt;span style="color:#f85149">ذات&lt;/span> &lt;span style="color:#f85149">الصلة&lt;/span> &lt;span style="color:#f85149">عبر&lt;/span> &lt;span style="color:#f85149">الخدمات&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">لوحة&lt;/span> &lt;span style="color:#f85149">المعلومات&lt;/span> &lt;span style="color:#f85149">متاحة&lt;/span> &lt;span style="color:#f85149">أيضًا&lt;/span> &lt;span style="color:#f85149">كحاوية&lt;/span> &lt;span style="color:#f85149">مستقلة،&lt;/span> &lt;span style="color:#f85149">مما&lt;/span> &lt;span style="color:#f85149">يعني&lt;/span> &lt;span style="color:#f85149">أنه&lt;/span> &lt;span style="color:#f85149">يمكنك&lt;/span> &lt;span style="color:#f85149">استخدامها&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">البيئات&lt;/span> &lt;span style="color:#f85149">المرحلية&lt;/span> &lt;span style="color:#f85149">أو&lt;/span> &lt;span style="color:#f85149">خطوط&lt;/span> &lt;span style="color:#f85149">أنابيب&lt;/span> CI:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>bash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker run --rm -p &lt;span style="color:#a5d6ff">18888&lt;/span>:&lt;span style="color:#a5d6ff">18888&lt;/span> \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mcr.microsoft.com/dotnet/aspire-dashboard:latest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="النشر">النشر&lt;/h2>
&lt;p>أسباير تساعد أثناء التطوير، لكن ماذا عن الإنتاج؟ هذا هو المكان الذي تصبح فيه الأمور عملية.&lt;/p>
&lt;h3 id="تطبيقات-حاوية-azure">تطبيقات حاوية Azure&lt;/h3>
&lt;p>مسار النشر الأكثر وضوحًا هو Azure Container Apps (ACA)، الذي يتمتع بدعم Aspire من الدرجة الأولى. يمكنك النشر مباشرة باستخدام Azure Developer CLI:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>azd init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>azd up
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يكتشف الأمر &lt;code>azd init&lt;/code> Aspire AppHost الخاص بك ويقوم بإنشاء البنية التحتية الضرورية كرمز. &lt;code>azd up&lt;/code> يوفر كل شيء — سجل الحاوية، وتطبيقات الحاوية، وقواعد البيانات، ومثيلات Redis — استنادًا إلى طوبولوجيا AppHost لديك.&lt;/p>
&lt;p>يصبح AppHost الخاص بك بشكل أساسي بيان النشر الخاص بك. نفس الكود الذي يحدد &amp;ldquo;catalog-api يعتمد على PostgreSQL وRedis&amp;rdquo; هو الذي يحرك عملية توفير البنية التحتية.&lt;/p>
&lt;h3 id="كوبيرنيت">كوبيرنيت&lt;/h3>
&lt;p>بالنسبة لعمليات نشر Kubernetes، لا تقوم Aspire بإنشاء البيانات مباشرة، ولكن طوبولوجيا AppHost الخاصة بك تتوافق بشكل واضح مع موارد Kubernetes. يمكن لأداة المجتمع &lt;code>aspirate&lt;/code> (Aspir8) إنشاء مخططات Helm أو بيانات Kubernetes من AppHost الخاص بك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet tool install -g aspirate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aspirate generate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aspirate apply
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="اعتبارات-النشر">اعتبارات النشر&lt;/h3>
&lt;p>بعض الأشياء التي يجب وضعها في الاعتبار:- &lt;strong>تتغير سلاسل الاتصال بين البيئات.&lt;/strong> محليًا، يقوم Aspire بتدوير الحاويات وإدخال سلاسل الاتصال تلقائيًا. في الإنتاج، ستشير إلى الخدمات المُدارة. يستخدم Aspire تكوين .NET القياسي، لذا تعمل متغيرات البيئة وAzure Key Vault كما هو متوقع.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>لا يعمل AppHost في مرحلة الإنتاج.&lt;/strong> إنه أداة تنسيق للتطوير والنشر. في مرحلة الإنتاج، تعمل خدماتك بشكل مستقل، ويتم تكوينها من خلال متغيرات البيئة والمنسقين.&lt;/li>
&lt;li>&lt;strong>تتحول موارد البنية التحتية إلى خدمات مُدارة.&lt;/strong> تصبح حاوية PostgreSQL المحلية الخاصة بك هي قاعدة بيانات Azure لـ PostgreSQL. تصبح حاوية Redis المحلية لديك عبارة عن Azure Cache لـ Redis. كود الاستهلاك لا يتغير&lt;/li>
&lt;/ul>
&lt;h2 id="نصائح-من-العالم-الحقيقي">نصائح من العالم الحقيقي&lt;/h2>
&lt;p>بعد تشغيل Aspire في الإنتاج لفترة من الوقت، إليكم الدروس التي وفرت لنا الوقت:&lt;/p>
&lt;h3 id="1-استخدم-عمليات-فحص-دورة-حياة-الموارد-المخصصة">1. استخدم عمليات فحص دورة حياة الموارد المخصصة&lt;/h3>
&lt;p>لا تعتمد فقط على بدء تشغيل الحاوية لتحديد ما إذا كان المورد جاهزًا أم لا. قد يقبل PostgreSQL اتصالات TCP قبل أن يصبح جاهزًا فعليًا لخدمة الاستعلامات.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithHealthCheck();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكن لـ Aspire إجراء فحوصات صحية على الموارد والاحتفاظ بالخدمات التابعة حتى تصبح جاهزة بالفعل.&lt;/p>
&lt;h3 id="2-استخراج-الأنماط-الشائعة-إلى-الامتدادات">2. استخراج الأنماط الشائعة إلى الامتدادات&lt;/h3>
&lt;p>إذا كانت هناك خدمات متعددة تشترك في تكوينات مماثلة، فقم بإنشاء طرق ملحقة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AppHostExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IResourceBuilder&amp;lt;ProjectResource&amp;gt; AddWorkerService(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> IDistributedApplicationBuilder builder,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IResourceBuilder&amp;lt;IResourceWithConnectionString&amp;gt; db,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IResourceBuilder&amp;lt;IResourceWithConnectionString&amp;gt; messaging)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> builder.AddProject&amp;lt;Projects.WorkerService&amp;gt;(name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(messaging)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReplicas(&lt;span style="color:#a5d6ff">3&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-الاستفادة-من-النسخ-المتماثلة-لاختبار-التحميل">3. الاستفادة من النسخ المتماثلة لاختبار التحميل&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> catalogApi = builder.AddProject&amp;lt;Projects.CatalogApi&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;catalog-api&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(postgres)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReference(redis)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithReplicas(&lt;span style="color:#a5d6ff">5&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>WithReplicas&lt;/code> يقوم بتدوير مثيلات متعددة للخدمة محليًا. يعد هذا أمرًا رائعًا لاختبار موازنة التحميل وأخطاء التزامن وسلوك التخزين المؤقت الموزع دون النشر إلى مجموعة.&lt;/p>
&lt;h3 id="4-استخدم-المعلمات-للقيم-الحساسة">4. استخدم المعلمات للقيم الحساسة&lt;/h3>
&lt;p>لا تقم بتشفير بيانات الاعتماد، حتى بالنسبة للتنمية المحلية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> dbPassword = builder.AddParameter(&lt;span style="color:#a5d6ff">&amp;#34;db-password&amp;#34;&lt;/span>, secret: &lt;span style="color:#79c0ff">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> postgres = builder.AddPostgres(&lt;span style="color:#a5d6ff">&amp;#34;postgres&amp;#34;&lt;/span>, password: dbPassword)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddDatabase(&lt;span style="color:#a5d6ff">&amp;#34;catalogdb&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>عند التشغيل محليًا، سيطالبك Aspire بالقيمة أو يقرأها من أسرار المستخدم. في CI/CD، يأتي من متغيرات البيئة.&lt;/p>
&lt;h3 id="5-اختبارات-التكامل-تصبح-تافهة">5. اختبارات التكامل تصبح تافهة&lt;/h3>
&lt;p>تتضمن Aspire حزمة اختبار تجعل اختبار التكامل للتطبيقات الموزعة أمرًا سهلاً بشكل ملحوظ:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Fact]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task CatalogApiReturnsProducts()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> appHost = &lt;span style="color:#ff7b72">await&lt;/span> DistributedApplicationTestingBuilder
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .CreateAsync&amp;lt;Projects.MyCloudApp_AppHost&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> app = &lt;span style="color:#ff7b72">await&lt;/span> appHost.BuildAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> app.StartAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpClient = app.CreateHttpClient(&lt;span style="color:#a5d6ff">&amp;#34;catalog-api&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> httpClient.GetAsync(&lt;span style="color:#a5d6ff">&amp;#34;/api/products&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response.EnsureSuccessStatusCode();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> products = &lt;span style="color:#ff7b72">await&lt;/span> response.Content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ReadFromJsonAsync&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.NotEmpty(products);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يؤدي ذلك إلى تشغيل تطبيقك الموزع &lt;em>بالكامل&lt;/em> - بما في ذلك قواعد البيانات ووسطاء الرسائل - وإجراء اختبارك ضده، ثم تدميره. اختبارات التكامل الحقيقية مقابل البنية التحتية الحقيقية، في مسار CI الخاص بك. لا يسخر.&lt;/p>
&lt;h3 id="6-مراقبة-استخدام-الموارد-محليا">6. مراقبة استخدام الموارد محليًا&lt;/h3>
&lt;p>عند تشغيل حاويات متعددة محليًا، راقب استهلاك الموارد. يمكن لمثيل PostgreSQL وRedis وRabbitMQ المزود بواجهة مستخدم الإدارة أن يستهلك بسهولة 2-3 جيجابايت من ذاكرة الوصول العشوائي. إذا كنت تستخدم جهازًا مقيدًا، ففكر في استخدام تكوينات موارد أخف:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> redis = builder.AddRedis(&lt;span style="color:#a5d6ff">&amp;#34;cache&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithContainerRuntimeArgs(&lt;span style="color:#a5d6ff">&amp;#34;--memory=256m&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>لقد غيّر .NET Aspire بشكل أساسي طريقة إنشاء التطبيقات الموزعة. ليس لأنه يقدم مفاهيم ثورية - فالفحوصات الصحية، وقياس OpenTelemetry، وتنسيق الحاويات ليست جديدة. ولكن لأنها تجعلها &lt;em>افتراضية&lt;/em>. فهو يأخذ مئات القرارات الصغيرة التي يتعين عليك اتخاذها عادةً، وينفذها بافتراضيات معقولة، ويتيح لك تجاوزها عند الحاجة.نمط AppHost، في رأيي، هو أكبر فوز. إن التعبير عن طوبولوجيا التطبيق الموزعة على شكل تعليمات برمجية - غير متناثرة عبر ملفات Docker Compose، وبيانات Kubernetes، ومستندات README - يجعل النظام مفهومًا. يمكن لأعضاء الفريق الجدد فتح &lt;code>Program.cs&lt;/code> في AppHost وفهم البنية بأكملها في دقائق.&lt;/p>
&lt;p>إذا كنت تقوم بإنشاء تطبيقات موزعة باستخدام .NET، فامنح Aspire نظرة جادة. ابدأ بالقالب &lt;code>aspire-starter&lt;/code>، واستكشف لوحة المعلومات، واستخدم المكونات تدريجيًا حسب حاجتك إليها. ليس عليك أن تبذل كل ما في وسعك في اليوم الأول، فشركة Aspire هي شركة إضافية من حيث تصميمها.&lt;/p>
&lt;p>لقد ولت أيام قضاء أول سباق سريع في توصيل البنية الأساسية للبنية الأساسية. اسمح لشركة Aspire بالتعامل مع أعمال السباكة حتى تتمكن من التركيز على ما يهم فعليًا: تطبيقك.&lt;/p></content:encoded><category>.NET</category><category>Azure</category><category>Cloud</category><category>Docker</category></item><item><title>المصادقة والترخيص في Blazor: دليل عملي</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-authentication-authorization/</link><pubDate>Sat, 12 Jul 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-authentication-authorization/</guid><description>دليل عملي لتنفيذ المصادقة والترخيص في تطبيقات Blazor - بدءًا من هوية ASP.NET وحتى OAuth، والوصول المستند إلى الدور، وتأمين المكونات.</description><content:encoded>&lt;p>إذا كنت قد عملت مع ASP.NET MVC أو Razor Pages، فمن المحتمل أن يكون لديك نموذج عقلي لكيفية عمل المصادقة: تعترض البرامج الوسيطة الطلب، وتتحقق من ملف تعريف الارتباط أو الرمز المميز، وتملأ &lt;code>HttpContext.User&lt;/code>، وأنت خارج السباقات. يقوم Blazor بتغيير هذا النموذج العقلي بطرق دقيقة ولكنها مهمة - وإذا لم تفهم هذه الاختلافات مبكرًا، فسوف ينتهي بك الأمر إلى تصحيح مشكلات المصادقة التي تبدو مستحيلة.&lt;/p>
&lt;p>في هذا المنشور، أريد أن أتعرف على كيفية عمل المصادقة والتفويض فعليًا في Blazor، والذي يغطي نماذج استضافة الخادم وWebAssembly. سننتقل من الأساسيات إلى موفري الخدمات المخصصين وبروتوكول OAuth الخارجي والمزالق التي رأيتها حتى مطوري .NET ذوي الخبرة.&lt;/p>
&lt;h2 id="لماذا-تختلف-المصادقة-في-blazor">لماذا تختلف المصادقة في Blazor&lt;/h2>
&lt;p>في ASP.NET التقليدي، كل تفاعل للمستخدم هو طلب HTTP. يتحقق الخادم من صحة بيانات الاعتماد، ويعين ملف تعريف الارتباط، وكل طلب لاحق يحمل ملف تعريف الارتباط هذا. خط أنابيب المصادقة خطي ويمكن التنبؤ به.&lt;/p>
&lt;p>يعمل Blazor Server عبر اتصال SignalR المستمر. بعد أن يقوم طلب HTTP الأولي بتحميل الصفحة، تحدث جميع التفاعلات اللاحقة عبر WebSockets. لا يوجد طلب HTTP جديد لكل نقرة على الزر، لذلك لا يتم إعادة تنفيذ البرامج الوسيطة في كل تفاعل. يتوفر &lt;code>HttpContext&lt;/code> أثناء الاتصال الأولي، ولكن الاعتماد عليه طوال عمر الدائرة يعد بمثابة وصفة للأخطاء.&lt;/p>
&lt;p>يعمل Blazor WebAssembly بالكامل في المتصفح. لا يوجد &lt;code>HttpContext&lt;/code> من جانب الخادم على الإطلاق. يجب جلب حالة المصادقة من واجهة برمجة التطبيقات (API)، وتخزينها من جانب العميل، وإدارتها من خلال الرموز المميزة - عادةً JWTs. يثق الخادم بالعميل فقط فيما يتعلق بالتحقق من صحة الرمز المميز.&lt;/p>
&lt;p>وهذا يعني أن Blazor يحتاج إلى تجريد خاص به لحالة المصادقة، وهو تجريد يعمل بغض النظر عن طراز الاستضافة. هذا التجريد هو &lt;code>AuthenticationStateProvider&lt;/code>.&lt;/p>
&lt;h2 id="حالة-المصادقة-المؤسسة">حالة المصادقة: المؤسسة&lt;/h2>
&lt;p>يقع &lt;code>AuthenticationStateProvider&lt;/code> في قلب نظام مصادقة Blazor. هذه فئة مجردة تكشف عن طريقة حاسمة واحدة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">abstract&lt;/span> Task&amp;lt;AuthenticationState&amp;gt; GetAuthenticationStateAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يلتف الكائن &lt;code>AuthenticationState&lt;/code> بـ &lt;code>ClaimsPrincipal&lt;/code> — وهو نفس نموذج الهوية المستخدم في .NET. لا تتصل المكونات بملفات تعريف الارتباط أو الرموز المميزة مباشرةً؛ يسألون &lt;code>AuthenticationStateProvider&lt;/code> عن الحالة الحالية.&lt;/p>
&lt;p>لجعل هذه الحالة متاحة لشجرة المكونات بأكملها، يوفر Blazor &lt;code>CascadingAuthenticationState&lt;/code>. عادةً ما تقوم بتغليف جهاز التوجيه الخاص بك به في &lt;code>App.razor&lt;/code> أو في تخطيطك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;CascadingAuthenticationState&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Router AppAssembly=&amp;#34;@typeof(App).Assembly&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Found Context=&amp;#34;routeData&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;AuthorizeRouteView RouteData=&amp;#34;@routeData&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DefaultLayout=&amp;#34;@typeof(MainLayout)&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;You&amp;#39;re not authorized to view this page.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/AuthorizeRouteView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Found&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotFound&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;LayoutView Layout=&amp;#34;@typeof(MainLayout)&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Page not found.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/LayoutView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotFound&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Router&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/CascadingAuthenticationState&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يقوم &lt;code>AuthorizeRouteView&lt;/code> بمهمة مزدوجة هنا: فهو يتحقق مما إذا كان المستخدم قد تمت مصادقته وتفويضه قبل عرض مكون الصفحة المطابق، ويوفر واجهة مستخدم احتياطية عندما لا يكون الأمر كذلك.&lt;/p>
&lt;p>في .NET 8 والإصدارات الأحدث مع نموذج Blazor الموحد، ستقوم بتكوين هذا في &lt;code>App.razor&lt;/code> وسيتعامل إطار العمل مع المعلمة المتتالية تلقائيًا عند استخدام &lt;code>AddCascadingAuthenticationState()&lt;/code> في تسجيل الخدمة الخاصة بك.&lt;/p>
&lt;h2 id="تكامل-هوية-aspnet">تكامل هوية ASP.NET&lt;/h2>
&lt;p>بالنسبة لمعظم المشاريع، لا تحتاج إلى إنشاء مصادقة من الصفر. تمنحك هوية ASP.NET إدارة المستخدم، وتجزئة كلمة المرور، والمصادقة الثنائية، وتأكيد الحساب خارج الصندوق.يبدأ إعداده باستخدام Blazor في &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddDbContext&amp;lt;ApplicationDbContext&amp;gt;(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.UseSqlServer(builder.Configuration.GetConnectionString(&lt;span style="color:#a5d6ff">&amp;#34;DefaultConnection&amp;#34;&lt;/span>)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddDefaultIdentity&amp;lt;IdentityUser&amp;gt;(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.SignIn.RequireConfirmedAccount = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.Password.RequireDigit = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.Password.RequiredLength = &lt;span style="color:#a5d6ff">8&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddRoles&amp;lt;IdentityRole&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEntityFrameworkStores&amp;lt;ApplicationDbContext&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddAuthentication(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.DefaultScheme = IdentityConstants.ApplicationScheme;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>باستخدام قالب Blazor Web App في .NET 8+، تستخدم واجهة مستخدم الهوية المدعومة مكونات Razor مباشرةً. يمكنك الحصول على صفحات تسجيل الدخول والتسجيل وإدارة الحساب التي تتكامل بشكل طبيعي مع بقية تطبيق Blazor الخاص بك - لم يعد هناك مزيج غريب من صفحات Razor ومكونات Blazor.&lt;/p>
&lt;p>يرث &lt;code>ApplicationDbContext&lt;/code> من &lt;code>IdentityDbContext&lt;/code> وستحتاج إلى تشغيل عمليات الترحيل لإنشاء جداول الهوية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ApplicationDbContext&lt;/span> : IdentityDbContext&amp;lt;IdentityUser&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ApplicationDbContext(DbContextOptions&amp;lt;ApplicationDbContext&amp;gt; options)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : &lt;span style="color:#ff7b72">base&lt;/span>(options) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet ef migrations add InitialIdentity
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet ef database update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="مكون-authorizeview">مكون AuthorizeView&lt;/h2>
&lt;p>بمجرد تدفق حالة المصادقة عبر شجرة المكونات الخاصة بك، يتيح لك &lt;code>AuthorizeView&lt;/code> عرض واجهة المستخدم بشكل مشروط:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Welcome, @context.User.Identity?.Name!&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/account/manage&amp;#34;&amp;gt;Manage Account&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form method=&amp;#34;post&amp;#34; action=&amp;#34;/account/logout&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34;&amp;gt;Log Out&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/account/login&amp;#34;&amp;gt;Log In&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/account/register&amp;#34;&amp;gt;Register&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تتيح لك المعلمة &lt;code>context&lt;/code> داخل &lt;code>&amp;lt;Authorized&amp;gt;&lt;/code> الوصول إلى &lt;code>AuthenticationState&lt;/code>، حتى تتمكن من فحص المطالبات والأدوار وهوية المستخدم مباشرة في الترميز الخاص بك.&lt;/p>
&lt;p>يمكنك أيضًا استخدام &lt;code>AuthorizeView&lt;/code> مع الأدوار والسياسات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView Roles=&amp;#34;Admin,Moderator&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button @onclick=&amp;#34;DeletePost&amp;#34;&amp;gt;Delete Post&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView Policy=&amp;#34;CanEditArticles&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button @onclick=&amp;#34;EditArticle&amp;#34;&amp;gt;Edit&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هناك شيء واحد يجب أخذه في الاعتبار: &lt;code>AuthorizeView&lt;/code> هو أحد اهتمامات واجهة المستخدم. فهو يخفي العناصر أو يظهرها، لكنه لا يحمي المنطق الأساسي. إذا كان بإمكان شخص ما الاتصال بنقطة نهاية واجهة برمجة التطبيقات (API) الخاصة بك أو استدعاء طريقتك مباشرة، فسيتجاوز &lt;code>AuthorizeView&lt;/code> بالكامل. قم دائمًا بفرض الترخيص على جانب الخادم أيضًا.&lt;/p>
&lt;h2 id="السمة-التفويض">السمة [التفويض]&lt;/h2>
&lt;p>لحماية صفحة بأكملها، قم بتطبيق السمة &lt;code>[Authorize]&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/admin/dashboard&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@attribute [Authorize(Roles = &amp;#34;Admin&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Admin Dashboard&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;Only administrators can see this page.&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>عندما ينتقل مستخدم غير مصادق إلى هذه الصفحة، يبدأ &lt;code>AuthorizeRouteView&lt;/code> ويعرض القالب &lt;code>&amp;lt;NotAuthorized&amp;gt;&lt;/code> الذي حددته مسبقًا. يمكنك إعادة التوجيه إلى صفحة تسجيل الدخول بدلاً من ذلك عن طريق التعامل مع حالة &lt;code>NotAuthorized&lt;/code> مع التنقل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-zed" data-lang="zed">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>NotAuthorized&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">@&lt;/span>if&lt;span style="color:#6e7681"> &lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>context.User.Identity&lt;span style="color:#ff7b72;font-weight:bold">?&lt;/span>.IsAuthenticated&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">??&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>true)&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>RedirectToLogin&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>}&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>else&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>{&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>You&lt;span style="color:#6e7681"> &lt;/span>don&lt;span style="color:#f85149">&amp;#39;&lt;/span>t&lt;span style="color:#6e7681"> &lt;/span>have&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">permission&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>to&lt;span style="color:#6e7681"> &lt;/span>access&lt;span style="color:#6e7681"> &lt;/span>this&lt;span style="color:#6e7681"> &lt;/span>page.&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>}&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>NotAuthorized&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>قد يبدو مكون &lt;code>RedirectToLogin&lt;/code> البسيط كما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject NavigationManager &lt;span style="color:#f0883e;font-weight:bold">Navigation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override void OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> returnUrl &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> Uri&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>EscapeDataString(&lt;span style="color:#f0883e;font-weight:bold">Navigation&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Uri);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f0883e;font-weight:bold">Navigation&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NavigateTo(&lt;span style="color:#ff7b72;font-weight:bold">$&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/account/login?returnUrl={returnUrl}&amp;#34;&lt;/span>, forceLoad: true);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعد &lt;code>forceLoad: true&lt;/code> مهمًا هنا - فأنت تريد تنقلًا فعليًا عبر HTTP حتى تتمكن البرامج الوسيطة للمصادقة من جانب الخادم من التعامل مع تدفق تسجيل الدخول بشكل صحيح.&lt;/p>
&lt;h2 id="التفويض-المستند-إلى-الدور-والسياسة">التفويض المستند إلى الدور والسياسة&lt;/h2>
&lt;p>الأدوار هي أبسط نموذج: قم بتعيين المستخدمين إلى مجموعات مثل &amp;ldquo;المسؤول&amp;rdquo; أو &amp;ldquo;المحرر&amp;rdquo;، ثم تحقق من العضوية. لكن السياسات تمنحك مرونة أكبر بكثير.&lt;/p>
&lt;p>تسجيل السياسات في &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddAuthorizationCore(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.AddPolicy(&lt;span style="color:#a5d6ff">&amp;#34;CanPublish&amp;#34;&lt;/span>, policy =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> policy.RequireClaim(&lt;span style="color:#a5d6ff">&amp;#34;Permission&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Publish&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.AddPolicy(&lt;span style="color:#a5d6ff">&amp;#34;MinimumAge&amp;#34;&lt;/span>, policy =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> policy.Requirements.Add(&lt;span style="color:#ff7b72">new&lt;/span> MinimumAgeRequirement(&lt;span style="color:#a5d6ff">18&lt;/span>)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.AddPolicy(&lt;span style="color:#a5d6ff">&amp;#34;PremiumUser&amp;#34;&lt;/span>, policy =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> policy.RequireRole(&lt;span style="color:#a5d6ff">&amp;#34;Premium&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .RequireClaim(&lt;span style="color:#a5d6ff">&amp;#34;Subscription&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Active&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>المتطلبات المخصصة تحتاج إلى معالج:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">MinimumAgeRequirement&lt;/span> : IAuthorizationRequirement
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> MinimumAge { &lt;span style="color:#ff7b72">get&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> MinimumAgeRequirement(&lt;span style="color:#ff7b72">int&lt;/span> minimumAge) =&amp;gt; MinimumAge = minimumAge;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">MinimumAgeHandler&lt;/span> : AuthorizationHandler&amp;lt;MinimumAgeRequirement&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> Task HandleRequirementAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthorizationHandlerContext context,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MinimumAgeRequirement requirement)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> dateOfBirthClaim = context.User.FindFirst(&lt;span style="color:#a5d6ff">&amp;#34;DateOfBirth&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (dateOfBirthClaim &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> dateOfBirth = DateOnly.Parse(dateOfBirthClaim.Value);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> age = DateOnly.FromDateTime(DateTime.Today).Year - dateOfBirth.Year;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (age &amp;gt;= requirement.MinimumAge)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Succeed(requirement);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تسجيل المعالج:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddSingleton&amp;lt;IAuthorizationHandler, MinimumAgeHandler&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في المكونات، يمكنك أيضًا التحقق من الترخيص برمجيًا عندما تحتاج إلى منطق ديناميكي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject IAuthorizationService AuthorizationService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject AuthenticationStateProvider AuthStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> canPublish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> authState &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await AuthStateProvider&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetAuthenticationStateAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await AuthorizationService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>AuthorizeAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> authState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>User, &lt;span style="color:#a5d6ff">&amp;#34;CanPublish&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> canPublish &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> result&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Succeeded;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="موفري-oauth-الخارجيين">موفري OAuth الخارجيين&lt;/h2>
&lt;p>يعد دعم &amp;ldquo;تسجيل الدخول باستخدام Google&amp;rdquo; أو &amp;ldquo;تسجيل الدخول باستخدام GitHub&amp;rdquo; أمرًا مباشرًا باستخدام البرنامج الوسيط لمصادقة ASP.NET. تم تكوينها من جانب الخادم نظرًا لأن تدفق OAuth يتطلب عمليات إعادة توجيه HTTP.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddAuthentication()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddGoogle(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientId = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Google:ClientId&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientSecret = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Google:ClientSecret&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.Scope.Add(&lt;span style="color:#a5d6ff">&amp;#34;profile&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddMicrosoftAccount(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientId = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Microsoft:ClientId&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientSecret = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:Microsoft:ClientSecret&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddGitHub(options =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientId = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:GitHub:ClientId&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options.ClientSecret = builder.Configuration[&lt;span style="color:#a5d6ff">&amp;#34;Auth:GitHub:ClientSecret&amp;#34;&lt;/span>]!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بالنسبة إلى GitHub، ستحتاج إلى حزمة &lt;code>AspNet.Security.OAuth.GitHub&lt;/code> NuGet، لأنها غير مضمنة في مكتبات ASP.NET الافتراضية.&lt;/p>
&lt;p>توفر واجهة مستخدم تسجيل الدخول بعد ذلك روابط تؤدي إلى التحدي الخارجي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/account/external-login&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h2&amp;gt;Sign in with an external provider&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;form method=&amp;#34;post&amp;#34; action=&amp;#34;/api/auth/external-login&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; name=&amp;#34;provider&amp;#34; value=&amp;#34;Google&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Sign in with Google
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; name=&amp;#34;provider&amp;#34; value=&amp;#34;Microsoft&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Sign in with Microsoft
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; name=&amp;#34;provider&amp;#34; value=&amp;#34;GitHub&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Sign in with GitHub
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تقوم نقطة نهاية واجهة برمجة التطبيقات (API) بتشغيل التحدي وتتعامل مع رد الاتصال:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/api/auth/external-login&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string&lt;/span> provider, HttpContext context) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> properties = &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationProperties
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RedirectUri = &lt;span style="color:#a5d6ff">&amp;#34;/api/auth/external-callback&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Challenge(properties, [provider]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تتطلب المصادقة الخارجية في Blazor دائمًا التنقل الكامل للصفحة - لا يمكنك إكمال إعادة توجيه OAuth داخل دائرة SignalR أو تطبيق WebAssembly دون المرور عبر الخادم.&lt;/p>
&lt;h2 id="المصادقة-المستندة-إلى-الرمز-المميز-في-blazor-webassemblyيتم-تشغيل-blazor-webassembly-على-العميل-لذا-لا-يتم-تطبيق-المصادقة-المستندة-إلى-ملفات-تعريف-الارتباط-بنفس-الطريقة-بدلا-من-ذلك-عادة-ما-تستخدم-jwts-المخزنة-في-الذاكرة-والمرفقة-بطلبات-http-الصادرة">المصادقة المستندة إلى الرمز المميز في Blazor WebAssemblyيتم تشغيل Blazor WebAssembly على العميل، لذا لا يتم تطبيق المصادقة المستندة إلى ملفات تعريف الارتباط بنفس الطريقة. بدلاً من ذلك، عادةً ما تستخدم JWTs المخزنة في الذاكرة والمرفقة بطلبات HTTP الصادرة.&lt;/h2>
&lt;p>يوفر إطار العمل &lt;code>AuthorizationMessageHandler&lt;/code> لإرفاق الرموز المميزة تلقائيًا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddHttpClient(&lt;span style="color:#a5d6ff">&amp;#34;API&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client =&amp;gt; client.BaseAddress = &lt;span style="color:#ff7b72">new&lt;/span> Uri(&lt;span style="color:#a5d6ff">&amp;#34;https://api.example.com&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddHttpMessageHandler&amp;lt;AuthorizationMessageHandler&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped(sp =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sp.GetRequiredService&amp;lt;IHttpClientFactory&amp;gt;().CreateClient(&lt;span style="color:#a5d6ff">&amp;#34;API&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بالنسبة لتطبيقات Blazor WASM المستقلة التي تقوم بالمصادقة مقابل واجهة برمجة التطبيقات الخاصة بها، فسوف تقوم بتنفيذ &lt;code>AuthenticationStateProvider&lt;/code> المخصص الذي يوزع JWT:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">JwtAuthenticationStateProvider&lt;/span> : AuthenticationStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILocalStorageService _localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> JwtAuthenticationStateProvider(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILocalStorageService localStorage,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _localStorage = localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;AuthenticationState&amp;gt; GetAuthenticationStateAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> token = &lt;span style="color:#ff7b72">await&lt;/span> _localStorage.GetItemAsync&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;authToken&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(token))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient.DefaultRequestHeaders.Authorization =
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationHeaderValue(&lt;span style="color:#a5d6ff">&amp;#34;Bearer&amp;#34;&lt;/span>, token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> claims = ParseClaimsFromJwt(token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> identity = &lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity(claims, &lt;span style="color:#a5d6ff">&amp;#34;jwt&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(identity));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> NotifyAuthStateChanged()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> IEnumerable&amp;lt;Claim&amp;gt; ParseClaimsFromJwt(&lt;span style="color:#ff7b72">string&lt;/span> jwt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> payload = jwt.Split(&lt;span style="color:#a5d6ff">&amp;#39;.&amp;#39;&lt;/span>)[&lt;span style="color:#a5d6ff">1&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> padded = payload.Length % &lt;span style="color:#a5d6ff">4&lt;/span> &lt;span style="color:#ff7b72">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">2&lt;/span> =&amp;gt; payload + &lt;span style="color:#a5d6ff">&amp;#34;==&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">3&lt;/span> =&amp;gt; payload + &lt;span style="color:#a5d6ff">&amp;#34;=&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ =&amp;gt; payload
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> bytes = Convert.FromBase64String(padded);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> json = JsonSerializer.Deserialize&amp;lt;Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, JsonElement&amp;gt;&amp;gt;(bytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> json?.Select(kvp =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> Claim(kvp.Key, kvp.Value.ToString()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ?? Enumerable.Empty&amp;lt;Claim&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد تسجيل الدخول بنجاح، يمكنك تخزين الرمز المميز وإخطار حالة المصادقة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AuthService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILocalStorageService _localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> JwtAuthenticationStateProvider _authStateProvider;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> AuthService(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> HttpClient httpClient,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILocalStorageService localStorage,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthenticationStateProvider authStateProvider)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _localStorage = localStorage;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _authStateProvider = (JwtAuthenticationStateProvider)authStateProvider;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt; LoginAsync(&lt;span style="color:#ff7b72">string&lt;/span> email, &lt;span style="color:#ff7b72">string&lt;/span> password)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.PostAsJsonAsync(&lt;span style="color:#a5d6ff">&amp;#34;/api/auth/login&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> { Email = email, Password = password });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!response.IsSuccessStatusCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> response.Content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ReadFromJsonAsync&amp;lt;LoginResponse&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _localStorage.SetItemAsync(&lt;span style="color:#a5d6ff">&amp;#34;authToken&amp;#34;&lt;/span>, result!.Token);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _authStateProvider.NotifyAuthStateChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task LogoutAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> _localStorage.RemoveItemAsync(&lt;span style="color:#a5d6ff">&amp;#34;authToken&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _authStateProvider.NotifyAuthStateChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كلمة تحذير: تخزين JWTs في &lt;code>localStorage&lt;/code> يعرضهم لهجمات XSS. بالنسبة للتطبيقات ذات الأمان الأعلى، فكر في الاحتفاظ بالرموز المميزة في الذاكرة فقط واستخدام الرموز المميزة للتحديث، أو اعتماد نمط الواجهة الخلفية للواجهة (BFF) حيث يدير الخادم الرموز المميزة ويستخدم العميل ملفات تعريف ارتباط HTTP فقط.&lt;/p>
&lt;h2 id="خادم-blazor-مقابل-webassembly-الاعتبارات-الأمنية">خادم Blazor مقابل WebAssembly: الاعتبارات الأمنية&lt;/h2>
&lt;p>يغير نموذج الاستضافة وضع الأمان لديك بشكل أساسي.&lt;/p>
&lt;p>**يحتفظ &lt;strong>Blazor Server&lt;/strong> بكل منطق المكونات لديك على الخادم. يرى العميل فقط اختلافات HTML المقدمة عبر SignalR. هذا يعني:&lt;/p>
&lt;ul>
&lt;li>المنطق الحساس لا يترك الخادم أبدًا&lt;/li>
&lt;li>يمكنك الوصول إلى قواعد البيانات والخدمات الداخلية مباشرة من المكونات&lt;/li>
&lt;li>تأتي حالة المصادقة من &lt;code>HttpContext&lt;/code> للخادم عند الاتصال الأولي&lt;/li>
&lt;li>يمكن للدائرة أن تعيش أكثر من ملف تعريف ارتباط المصادقة - إذا انتهت صلاحية ملف تعريف الارتباط الخاص بالمستخدم، تظل الدائرة حية حتى يتم قطع اتصالها&lt;/li>
&lt;li>يجب عليك التعامل مع انقطاع الدائرة بأمان وإعادة التحقق من صحة حالة المصادقة عند إعادة الاتصال&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Blazor WebAssembly&lt;/strong> يعمل بالكامل في المتصفح. هذا يعني:&lt;/p>
&lt;ul>
&lt;li>كل كود المكون الخاص بك قابل للتنزيل والفحص&lt;/li>
&lt;li>لا تضع أبدًا أسرارًا أو سلاسل اتصال أو منطق عمل حساس في مكونات WASM&lt;/li>
&lt;li>يتم فرض المصادقة فقط على العميل لتجربة المستخدم؛ يجب أن يحدث التنفيذ الحقيقي في طبقة API الخاصة بك&lt;/li>
&lt;li>إدارة الرمز المميز هي مسؤوليتك&lt;/li>
&lt;li>فكر في استخدام النموذج المستضاف حيث يتعامل مشروع الخادم مع المصادقة ويخدم تطبيق WASM&lt;/li>
&lt;/ul>
&lt;p>أحد الأنماط التي أوصي بها لتطبيقات WebAssembly هو التعامل مع كل مكون كما لو كان &amp;ldquo;واجهة مستخدم غير موثوق بها&amp;rdquo; وكل نقطة نهاية لواجهة برمجة التطبيقات كما لو كان يتم استدعاؤها بواسطة عميل غير معروف. التحقق من صحة كل شيء من جانب الخادم بغض النظر عما يتحقق منه العميل.&lt;/p>
&lt;h2 id="إنشاء-authenticationstateprovider-مخصص">إنشاء AuthenticationStateProvider مخصص&lt;/h2>
&lt;p>في بعض الأحيان لا يتناسب الموفرون المضمنون مع بنيتك. ربما تقوم بالتكامل مع نظام مصادقة قديم، أو تحتاج إلى استطلاع تغييرات حالة المصادقة. فيما يلي موفر مخصص أكثر اكتمالاً لخادم Blazor:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomAuthStateProvider&lt;/span> : AuthenticationStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IHttpContextAccessor _httpContextAccessor;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IUserService _userService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomAuthStateProvider(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IHttpContextAccessor httpContextAccessor,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IUserService userService)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpContextAccessor = httpContextAccessor;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _userService = userService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;AuthenticationState&amp;gt; GetAuthenticationStateAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpContext = _httpContextAccessor.HttpContext;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (httpContext?.User?.Identity?.IsAuthenticated != &lt;span style="color:#79c0ff">true&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (userId &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> user = &lt;span style="color:#ff7b72">await&lt;/span> _userService.GetUserWithClaimsAsync(userId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (user &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity()));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> claims = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;Claim&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span>(ClaimTypes.NameIdentifier, user.Id),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span>(ClaimTypes.Name, user.DisplayName),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span>(ClaimTypes.Email, user.Email)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> claims.AddRange(user.Roles.Select(r =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> Claim(ClaimTypes.Role, r)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> claims.AddRange(user.Permissions.Select(p =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> Claim(&lt;span style="color:#a5d6ff">&amp;#34;Permission&amp;#34;&lt;/span>, p)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> identity = &lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity(claims, &lt;span style="color:#a5d6ff">&amp;#34;Custom&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(identity));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> MarkUserAsAuthenticated(&lt;span style="color:#ff7b72">string&lt;/span> userId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> MarkUserAsLoggedOut()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> anonymous = &lt;span style="color:#ff7b72">new&lt;/span> ClaimsPrincipal(&lt;span style="color:#ff7b72">new&lt;/span> ClaimsIdentity());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> authState = Task.FromResult(&lt;span style="color:#ff7b72">new&lt;/span> AuthenticationState(anonymous));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NotifyAuthenticationStateChanged(authState);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>سجله في &lt;code>Program.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;AuthenticationStateProvider, CustomAuthStateProvider&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;CustomAuthStateProvider&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الفكرة الأساسية هنا هي &lt;code>NotifyAuthenticationStateChanged&lt;/code> — يؤدي استدعاؤها إلى تشغيل المعلمة المتتالية للتحديث، والتي تعيد تقييم كل &lt;code>AuthorizeView&lt;/code> و&lt;code>AuthorizeRouteView&lt;/code> في شجرة المكونات الخاصة بك. هذه هي الطريقة التي تجعل واجهة المستخدم تتفاعل مع أحداث تسجيل الدخول/الخروج دون تحديث كامل للصفحة.&lt;/p>
&lt;h2 id="المخاطر-والحلول-الشائعة">المخاطر والحلول الشائعة&lt;/h2>
&lt;p>بعد العمل مع Blazor auth عبر العديد من المشاريع، إليك المشكلات التي أراها في أغلب الأحيان:&lt;/p>
&lt;h3 id="1-استخدام-httpcontext-في-مكونات-خادم-blazorhttpcontext-متاح-أثناء-طلب-http-الأولي-ولكنه-null-أو-قديم-أثناء-تفاعلات-signalr-لا-تقم-بإدخال-ihttpcontextaccessor-في-المكونات-التي-تعمل-بعد-العرض-الأولي">1. استخدام HttpContext في مكونات خادم Blazor&lt;code>HttpContext&lt;/code> متاح أثناء طلب HTTP الأولي، ولكنه &lt;code>null&lt;/code> أو قديم أثناء تفاعلات SignalR. لا تقم بإدخال &lt;code>IHttpContextAccessor&lt;/code> في المكونات التي تعمل بعد العرض الأولي.&lt;/h3>
&lt;p>&lt;strong>الحل:&lt;/strong> التقط ما تحتاجه من &lt;code>HttpContext&lt;/code> أثناء التهيئة وقم بتخزينه في خدمة محددة النطاق:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">UserContext&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> UserId { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> AccessToken { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// In a component that renders during the initial HTTP request:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject IHttpContextAccessor HttpContextAccessor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject UserContext UserContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> context = HttpContextAccessor.HttpContext;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UserContext.UserId = context?.User.FindFirstValue(ClaimTypes.NameIdentifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UserContext.AccessToken = context?.Request.Headers.Authorization
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToString().Replace(&lt;span style="color:#a5d6ff">&amp;#34;Bearer &amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-عدم-تحديث-حالة-المصادقة-بعد-تسجيل-الدخول">2. عدم تحديث حالة المصادقة بعد تسجيل الدخول&lt;/h3>
&lt;p>يمكنك استدعاء واجهة برمجة تطبيقات تسجيل الدخول الخاصة بك، وقد نجحت، ولكن واجهة المستخدم لا تزال تعرض &amp;ldquo;تسجيل الدخول&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>الحل:&lt;/strong> يجب عليك الاتصال بـ &lt;code>NotifyAuthenticationStateChanged&lt;/code> على &lt;code>AuthenticationStateProvider&lt;/code> بعد تغيير حالة المصادقة. لا يكتشف إطار العمل بطريقة سحرية أنه تم تخزين الرمز المميز أو تعيين ملف تعريف الارتباط.&lt;/p>
&lt;h3 id="3-سمة-الترخيص-لا-تعمل-على-المكونات">3. سمة الترخيص لا تعمل على المكونات&lt;/h3>
&lt;p>يمكنك إضافة &lt;code>[Authorize]&lt;/code> إلى أحد المكونات ولكنه لا يمنع المستخدمين غير المصادقين.&lt;/p>
&lt;p>&lt;strong>الحل:&lt;/strong> تأكد من أنك تستخدم &lt;code>AuthorizeRouteView&lt;/code> بدلاً من &lt;code>RouteView&lt;/code> العادي في &lt;code>App.razor&lt;/code>. يتجاهل المعيار &lt;code>RouteView&lt;/code> سمات الترخيص بالكامل.&lt;/p>
&lt;h3 id="4-العرض-المسبق-يكسر-حالة-المصادقة">4. العرض المسبق يكسر حالة المصادقة&lt;/h3>
&lt;p>أثناء العرض المسبق من جانب الخادم في Blazor WebAssembly، لا يوجد رمز مصادقة متاح. يتم عرض المكونات على أنها غير مصادقة، ثم تومض إلى حالة المصادقة بعد تحميل WASM.&lt;/p>
&lt;p>&lt;strong>الحل:&lt;/strong> إما تعطيل العرض المسبق للصفحات الحساسة للمصادقة باستخدام &lt;code>@rendermode InteractiveWebAssembly&lt;/code> (بدون العرض المسبق)، أو التعامل مع حالة التحميل بأمان:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>&amp;lt;AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Welcome back, @context.User.Identity?.Name&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;Authorizing&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/Authorizing&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;Sign in&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NotAuthorized&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/AuthorizeView&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="5-انتهاء-صلاحية-الرمز-المميز-في-الدوائر-طويلة-التشغيل">5. انتهاء صلاحية الرمز المميز في الدوائر طويلة التشغيل&lt;/h3>
&lt;p>يمكن لدوائر Blazor Server البقاء على قيد الحياة لساعات. إذا انتهت صلاحية الرمز المميز أو الجلسة، فسيظل المستخدم &amp;ldquo;مصادقًا عليه&amp;rdquo; في واجهة المستخدم ولكن تبدأ استدعاءات واجهة برمجة التطبيقات بالفشل.&lt;/p>
&lt;p>&lt;strong>الحل:&lt;/strong> قم بإجراء فحص دوري أو استخدم &lt;code>RevalidatingServerAuthenticationStateProvider&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">RevalidatingAuthStateProvider&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : RevalidatingServerAuthenticationStateProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IServiceScopeFactory _scopeFactory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> RevalidatingAuthStateProvider(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILoggerFactory loggerFactory,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IServiceScopeFactory scopeFactory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : &lt;span style="color:#ff7b72">base&lt;/span>(loggerFactory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _scopeFactory = scopeFactory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> TimeSpan RevalidationInterval =&amp;gt; TimeSpan.FromMinutes(&lt;span style="color:#a5d6ff">30&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt; ValidateAuthenticationStateAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthenticationState authenticationState, CancellationToken cancellationToken)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> scope = _scopeFactory.CreateAsyncScope();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> userManager = scope.ServiceProvider
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GetRequiredService&amp;lt;UserManager&amp;lt;IdentityUser&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> user = &lt;span style="color:#ff7b72">await&lt;/span> userManager.GetUserAsync(authenticationState.User);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> user &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يؤدي هذا إلى التحقق من أن المستخدم لا يزال موجودًا (وأن ختم الأمان الخاص به لم يتغير) كل 30 دقيقة.&lt;/p>
&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>تتطلب المصادقة والترخيص في Blazor تحولًا في التفكير من ASP.NET التقليدي للاستجابة للطلب. إن تجريد &lt;code>AuthenticationStateProvider&lt;/code> هو المفتاح لفهم كيفية تناسب كل شيء معًا - بمجرد استيعاب ذلك، يتبع الباقي بشكل طبيعي.&lt;/p>
&lt;p>بالنسبة لمعظم التطبيقات، ابدأ بهوية ASP.NET والقوالب المضمنة. إنهم يتعاملون مع الأعباء الثقيلة لإدارة المستخدم وتجزئة كلمة المرور وإنشاء الرمز المميز. طبقة من السياسات والتفويض المبني على المطالبات مع نمو متطلباتك. قم بإضافة موفري OAuth خارجيين عندما يتوقعهم المستخدمون.&lt;/p>
&lt;p>نموذج الاستضافة مهم: يمنحك Blazor Server وضعًا أمنيًا أكثر تقليدية حيث يبقى الكود على الخادم، بينما يدفعك WebAssembly نحو التفكير بواجهة برمجة التطبيقات أولاً حيث يكون العميل غير موثوق به حسب التصميم. ولا يعتبر أي منهما أكثر أمانًا بطبيعته، بل لديهم فقط نماذج تهديد مختلفة.&lt;/p>
&lt;p>أيًا كان النهج الذي تختاره، تذكر القاعدة الذهبية: &lt;strong>التفويض في واجهة المستخدم مخصص لتجربة المستخدم، أما التفويض على الخادم فهو للأمان.&lt;/strong> قم دائمًا بتنفيذ كليهما.&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Security</category><category>Web Development</category></item><item><title>JavaScript معزول في Blazor مع ملفات JS مجمعة</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-isolated-js/</link><pubDate>Wed, 18 Jun 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-isolated-js/</guid><description>استخدم ملفات JavaScript المجمعة في Blazor للحفاظ على نطاق منطق JS للمكونات الفردية.</description><content:encoded>&lt;p>إذا كنت قد عملت مع Blazor وJS Interop من قبل، فمن المحتمل أن ينتهي بك الأمر مع ملف &lt;code>app.js&lt;/code> ضخم مليء بالوظائف العشوائية لمكونات مختلفة. إنه يعمل، لكنه يصبح فوضويًا بسرعة. لحسن الحظ، هناك طريقة أكثر نظافة: ملفات JavaScript المجمعة.&lt;/p>
&lt;p>#الفكرة&lt;/p>
&lt;p>تمامًا مثل عزل CSS، يتيح لك Blazor وضع ملف &lt;code>.razor.js&lt;/code> بجوار المكون الخاص بك. يتم تحميل وحدة JavaScript عند الطلب، فقط عندما يحتاجها المكون فعليًا. لا توجد نصوص عالمية، ولا تلوث.&lt;/p>
&lt;h1 id="إعداده">إعداده&lt;/h1>
&lt;p>لنفترض أن لدينا مكون &lt;code>Clipboard.razor&lt;/code> الذي ينسخ النص إلى الحافظة. قم بإنشاء ملف يسمى &lt;code>Clipboard.razor.js&lt;/code> بجواره مباشرةً:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">export&lt;/span> &lt;span style="color:#ff7b72">function&lt;/span> copyToClipboard(text) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> navigator.clipboard.writeText(text).then(() =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console.log(&lt;span style="color:#a5d6ff">&amp;#34;Copied to clipboard!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لاحظ الكلمة الأساسية &lt;code>export&lt;/code> — وهذا أمر مهم. يقوم Blazor بتحميله كوحدة ES قياسية.&lt;/p>
&lt;h1 id="تحميل-الوحدة-في-blazor">تحميل الوحدة في Blazor&lt;/h1>
&lt;p>في المكون الخاص بك، يمكنك استخدام &lt;code>IJSRuntime&lt;/code> لاستيراد الوحدة. يتبع المسار الاصطلاح: &lt;code>./_content/{ASSEMBLY_NAME}/{COMPONENT_PATH}.razor.js&lt;/code> للمكتبات، أو المسار النسبي للمشروع الحالي فقط.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@inject IJSRuntime JS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@implements IAsyncDisposable
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button @onclick=&lt;span style="color:#a5d6ff">&amp;#34;Copy&amp;#34;&lt;/span>&amp;gt;Copy to clipboard&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Text { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> IJSObjectReference? module;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnAfterRenderAsync(&lt;span style="color:#ff7b72">bool&lt;/span> firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (firstRender)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> module = &lt;span style="color:#ff7b72">await&lt;/span> JS.InvokeAsync&amp;lt;IJSObjectReference&amp;gt;(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;import&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;./Components/Clipboard.razor.js&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task Copy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (module &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> module.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;copyToClipboard&amp;#34;&lt;/span>, Text);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask DisposeAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (module &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> module.DisposeAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعض الأشياء التي يجب ملاحظتها هنا:&lt;/p>
&lt;ul>
&lt;li>نقوم بتحميل الوحدة في &lt;code>OnAfterRenderAsync&lt;/code> لأن JS Interop غير متاح أثناء العرض المسبق من جانب الخادم&lt;/li>
&lt;li>نحتفظ بمرجع للوحدة مع &lt;code>IJSObjectReference&lt;/code>&lt;/li>
&lt;li>نقوم بتنفيذ &lt;code>IAsyncDisposable&lt;/code> لتنظيف الوحدة عند تدمير المكون&lt;/li>
&lt;/ul>
&lt;h1 id="لماذا-هذا-أفضل">لماذا هذا أفضل&lt;/h1>
&lt;p>من قبل، كنت أجمع كل شيء في ملف واحد &lt;code>wwwroot/js/app.js&lt;/code>. لقد نجح الأمر، ولكن العثور على الوظائف كان أمرًا صعبًا، وكل صفحة تحمل JavaScript لم تكن بحاجة إليها. مع ملفات JS المجمعة:&lt;/p>
&lt;ul>
&lt;li>يمتلك كل مكون جافا سكريبت الخاص به&lt;/li>
&lt;li>يتم تحميل الوحدات بتكاسل، فقط عند الحاجة إليها&lt;/li>
&lt;li>لا يوجد تعارض في أسماء الوظائف العامة&lt;/li>
&lt;li>سهولة الصيانة والحذف — عندما تحذف المكون، فإنك تحذف ملف JS معه&lt;/li>
&lt;/ul>
&lt;p>#مسكتك مع المسار&lt;/p>
&lt;p>يعتمد المسار الذي تمر به إلى &lt;code>import&lt;/code> على ما إذا كنت تستخدم تطبيق Blazor المستقل أو مكتبة Razor Class Library. بالنسبة لتطبيق Blazor العادي، يكون المسار متعلقًا بـ &lt;code>wwwroot&lt;/code>. يتم نسخ الملف &lt;code>.razor.js&lt;/code> هناك في وقت الإنشاء، لذا يمكنك الرجوع إليه من موقع المكون في المشروع.&lt;/p>
&lt;p>إذا كنت تحصل على 404 عند تحميل الوحدة، فتحقق جيدًا من المسار وتأكد من أن الملف ينتهي بـ &lt;code>.razor.js&lt;/code>، وليس فقط &lt;code>.js&lt;/code>.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! لا تتردد في الاتصال بي على أي وسائل التواصل الاجتماعي على **@emimontesdeoca **.&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/collocated-js">ASP.NET Core Blazor JavaScript مع JS المجمع&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>JavaScript</category></item><item><title>تفاعل Blazor في .NET 9 و.NET 10: دليل كامل</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-interactivity-dotnet-9-10/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-interactivity-dotnet-9-10/</guid><description>نظرة متعمقة على أوضاع عرض Blazor، وتدفق SSR، والتنقل المحسن، وميزات التفاعل الجديدة في .NET 9 و.NET 10.</description><content:encoded>&lt;p>إذا كنت تقوم بإنشاء تطبيقات الويب باستخدام Blazor على مدى السنوات القليلة الماضية، فأنت تعلم أن إطار العمل قد قطع شوطًا طويلًا. ما بدأ كاختيار بين Blazor Server وBlazor WebAssembly تطور إلى نموذج عرض موحد ومرن يتيح لك اختيار استراتيجية التفاعل الصحيحة لكل مكون في تطبيقك.&lt;/p>
&lt;p>مع .NET 8 حصلنا على التحول الأساسي — تقديم أوضاع العرض والعرض الثابت من جانب الخادم (SSR) كوضع افتراضي. والآن، يعتمد الإصداران .NET 9 و.NET 10 على هذا الأساس من خلال التحسينات التي تجعل تجربة المطور أكثر سلاسة وتجربة المستخدم النهائي أسرع.&lt;/p>
&lt;p>في هذا المنشور، أريد أن أطلعكم على الصورة الكاملة لتفاعل Blazor كما هي اليوم: كيف تعمل أوضاع العرض، وما يجلبه تدفق SSR إلى الطاولة، وكيف يغير التنقل المحسن والتعامل مع النماذج اللعبة، وما هو الجديد في أحدث الإصدارات. إذا كنت تخطط لمشروع Blazor جديد أو تفكر في الترقية، فهذا هو الدليل الذي كنت أتمنى لو كان لدي عندما بدأت البحث في كل هذا.&lt;/p>
&lt;h2 id="نظرة-سريعة-إلى-الوراء-كيف-وصلنا-إلى-هنا">نظرة سريعة إلى الوراء: كيف وصلنا إلى هنا&lt;/h2>
&lt;p>قبل .NET 8، كان عليك الالتزام بنموذج استضافة على مستوى المشروع. يعني Blazor Server أن كل شيء يعمل على الخادم عبر اتصال SignalR. يعني Blazor WebAssembly أن كل شيء يعمل في المتصفح. وكان لكل منهما مقايضات، وكان الاختلاط بينها مؤلما.&lt;/p>
&lt;p>لقد غير .NET 8 اللعبة من خلال تقديم قالب مشروع واحد — تطبيق Blazor Web App — الذي يوحد كلا النموذجين. المفهوم الأساسي هو &lt;strong>أوضاع العرض&lt;/strong>: عليك أن تقرر &lt;em>لكل مكون&lt;/em> كيفية عرضه ومكان حدوث التفاعل. أصبح الإعداد الافتراضي هو SSR الثابت، مما يعني عرض المكونات على الخادم وإرسال HTML عادي إلى المتصفح - بدون SignalR، ولا WebAssembly، فقط HTML سريع.&lt;/p>
&lt;p>قام .NET 9 بصقل هذه المفاهيم وتحسين تجربة المطورين وتحسين الأداء. يأخذ .NET 10 الأمر إلى أبعد من ذلك من خلال معالجة أفضل لإعادة الاتصال، وحالة المكون المستمرة عبر أوضاع العرض، وتحسينات على كيفية تسليم البرنامج النصي Blazor نفسه.&lt;/p>
&lt;p>دعونا كسر كل شيء إلى أسفل.&lt;/p>
&lt;h2 id="أوضاع-العرض-في-net-9">أوضاع العرض في .NET 9&lt;/h2>
&lt;p>في قلب Blazor الحديث يوجد مفهوم أوضاع العرض. هناك أربعة أوضاع يجب أن تعرفها:&lt;/p>
&lt;h3 id="1-ssr-الثابت-الافتراضي">1. SSR الثابت (الافتراضي)&lt;/h3>
&lt;p>عند إنشاء تطبيق Blazor Web App جديد، يتم عرض المكونات بشكل ثابت على الخادم بشكل افتراضي. يقوم الخادم بمعالجة مكون Razor، وإنشاء HTML، وإرساله إلى المتصفح. لا يوجد اتصال مستمر، ولا وقت تشغيل WebAssembly — مجرد طلب/استجابة تقليدية.&lt;/p>
&lt;p>يعد هذا مثاليًا للصفحات ذات المحتوى الثقيل، أو لوحات المعلومات التي تعرض البيانات في الغالب، أو أي صفحة لا تحتاج فيها إلى التفاعل في الوقت الفعلي.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Our Products&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> products)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;product-card&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Description&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;price&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> products &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> products &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ProductService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetAllAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يتم عرض هذا المكون على الخادم، وينتج HTML، وهذا كل شيء. لا يوجد اتصال مستمر. تحميل أولي سريع، رائع لتحسين محركات البحث، الحد الأدنى من استخدام موارد الخادم.&lt;/p>
&lt;h3 id="2-الخادم-التفاعليعندما-تحتاج-إلى-التفاعل-في-الوقت-الفعلي---التعامل-مع-نقرات-الأزرار-ومعالجة-إدخالات-المستخدم-وتحديث-واجهة-المستخدم-ديناميكيا---يمكنك-الاشتراك-في-وضع-الخادم-التفاعلي-يؤدي-هذا-إلى-إنشاء-اتصال-signalr-بين-المتصفح-والخادم-وتحدث-تحديثات-واجهة-المستخدم-عبر-هذا-الاتصال">2. الخادم التفاعليعندما تحتاج إلى التفاعل في الوقت الفعلي - التعامل مع نقرات الأزرار، ومعالجة إدخالات المستخدم، وتحديث واجهة المستخدم ديناميكيًا - يمكنك الاشتراك في وضع الخادم التفاعلي. يؤدي هذا إلى إنشاء اتصال SignalR بين المتصفح والخادم، وتحدث تحديثات واجهة المستخدم عبر هذا الاتصال.&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/counter&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@rendermode InteractiveServer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Counter&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;Current count: @currentCount&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button class=&amp;#34;btn btn-primary&amp;#34; @onclick=&amp;#34;IncrementCount&amp;#34;&amp;gt;Click me&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private int currentCount = 0;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void IncrementCount()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentCount++;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>التوجيه &lt;code>@rendermode InteractiveServer&lt;/code> هو كل ما يتطلبه الأمر. يتم عرض المكون في البداية على الخادم، ثم يتم إنشاء اتصال SignalR للتعامل مع التفاعلات اللاحقة. تعمل معالجات أحداث C# على الخادم، ويتم إرسال اختلافات واجهة المستخدم إلى المتصفح.&lt;/p>
&lt;p>&lt;strong>متى تستخدمه:&lt;/strong> عندما تحتاج إلى التفاعل، تحتاج مكوناتك إلى الوصول إلى الموارد من جانب الخادم (قواعد البيانات، وواجهات برمجة التطبيقات، وأنظمة الملفات)، وتريد تحميلًا أوليًا سريعًا دون انتظار تنزيل WebAssembly.&lt;/p>
&lt;h3 id="3-تجميع-الويب-التفاعلي">3. تجميع الويب التفاعلي&lt;/h3>
&lt;p>إذا كنت تريد التفاعل دون الحفاظ على اتصال الخادم، يقوم Interactive WebAssembly بتشغيل منطق المكون الخاص بك مباشرةً في المستعرض باستخدام وقت تشغيل .NET WebAssembly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/search&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveWebAssembly
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Product Search&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>input &lt;span style="color:#f85149">@&lt;/span>bind&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;searchTerm&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>bind:event&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;oninput&amp;#34;&lt;/span> placeholder&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Search products...&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (filteredProducts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ul&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> filteredProducts)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>ul&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string searchTerm &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> allProducts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private IEnumerable&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> filteredProducts &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> allProducts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Where(p &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> p&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Contains(searchTerm, StringComparison&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>OrdinalIgnoreCase));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> allProducts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await Http&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetFromJsonAsync&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Product&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;gt;&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#34;api/products&amp;#34;&lt;/span>) &lt;span style="color:#f85149">??&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>المقايضة: هناك تكلفة تنزيل أولية لوقت تشغيل .NET وتجميعاتك. ولكن بمجرد تحميله، يعمل المكون بالكامل في المتصفح دون الحاجة إلى رحلات ذهابًا وإيابًا للخادم لتفاعلات واجهة المستخدم.&lt;/p>
&lt;p>&lt;strong>متى يتم استخدامه:&lt;/strong> بالنسبة للمكونات التفاعلية للغاية حيث يكون زمن الاستجابة مهمًا (مثل: برامج تحرير النصوص الغنية، وأدوات الرسم، والتصفية في الوقت الفعلي)، أو عندما تريد تقليل تحميل الخادم، أو عندما تقوم بإنشاء تطبيق ويب تقدمي (PWA).&lt;/p>
&lt;h3 id="4-السيارات-التفاعلية">4. السيارات التفاعلية&lt;/h3>
&lt;p>هذه هي الأرضية الوسطى العملية وواحدة من ميزاتي المفضلة. يبدأ Interactive Auto بالعرض من جانب الخادم عبر SignalR للتحميل الأول، ثم يقوم بتنزيل وقت تشغيل WebAssembly في الخلفية بصمت. وفي الزيارات اللاحقة، يتم تشغيل المكون على WebAssembly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/dashboard&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveAuto
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Dashboard&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>DashboardWidget Title&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Sales&amp;#34;&lt;/span> Value&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@salesTotal&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>DashboardWidget Title&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Users&amp;#34;&lt;/span> Value&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@activeUsers&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;RefreshData&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Refresh&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private decimal salesTotal;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">int&lt;/span> activeUsers;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> await RefreshData();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task RefreshData()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> data &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await DashboardService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetSummaryAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> salesTotal &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> data&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>SalesTotal;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> activeUsers &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> data&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ActiveUsers;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمنحك هذا أفضل ما في كلا العالمين: العرض الأول السريع (بدون انتظار تنزيل WASM)، وفي النهاية يتم تشغيل المكون من جانب العميل. لا يلاحظ المستخدم الانتقال.&lt;/p>
&lt;p>&lt;strong>متى تستخدمه:&lt;/strong> عندما تريد التحميل الأولي السريع والتنفيذ النهائي من جانب العميل. إنه خيار افتراضي رائع للعديد من المكونات التفاعلية.&lt;/p>
&lt;h2 id="بث-ssr-أفضل-ما-في-العالمين">بث SSR: أفضل ما في العالمين&lt;/h2>
&lt;p>يعد Streaming SSR إحدى تلك الميزات التي تبدو بسيطة ولكنها تحدث فرقًا كبيرًا في الأداء المتصور. إليك الفكرة: بدلاً من انتظار تحميل جميع بياناتك قبل إرسال أي HTML، يرسل الخادم غلاف الصفحة على الفور ثم &lt;em>يبث&lt;/em> تحديثات المحتوى عندما تصبح البيانات متاحة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/reports&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>attribute [StreamRendering]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Monthly Reports&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (reports is null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;loading-spinner&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Loading reports&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Month&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Revenue&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Growth&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> report &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> reports)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>report&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Month&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>report&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Revenue&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@(report.Growth &amp;gt;= 0 ? &amp;#34;&lt;/span>text&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>success&lt;span style="color:#a5d6ff">&amp;#34; : &amp;#34;&lt;/span>text&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>danger&lt;span style="color:#a5d6ff">&amp;#34;)&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>report&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Growth&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;P1&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>MonthlyReport&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">?&lt;/span> reports;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">//&lt;/span> This might take a couple of seconds
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reports &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ReportService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetMonthlyReportsAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>مع السمة &lt;code>[StreamRendering]&lt;/code>، إليك ما يحدث:&lt;/p>
&lt;ol>
&lt;li>يرسل الخادم على الفور HTML باستخدام أداة التحميل الدوارة.&lt;/li>
&lt;li>&lt;code>OnInitializedAsync&lt;/code> يقوم بتشغيل وجلب البيانات.&lt;/li>
&lt;li>عند وصول البيانات، يقوم الخادم بتدفق HTML المحدث (الجدول) إلى المتصفح.&lt;/li>
&lt;li>يقوم المتصفح بتصحيح DOM دون إعادة تحميل الصفحة بالكامل.&lt;/li>
&lt;/ol>
&lt;p>يرى المستخدم الصفحة &lt;em>على الفور&lt;/em> مع مؤشر التحميل، ثم يملأ المحتوى. لا حاجة إلى إطار جافا سكريبت. لا يوجد اتصال WebSocket. مجرد استخدام ذكي لتدفق HTTP.&lt;strong>متى يتم استخدامها:&lt;/strong> أي صفحة SSR ثابتة تقوم بجلب البيانات أثناء العرض. صفحات قائمة المنتجات ولوحات المعلومات والتقارير - في أي مكان قد يستغرق تحميل البيانات الأولية أكثر من بضع مئات من المللي ثانية.&lt;/p>
&lt;p>&lt;strong>متى لا تستخدمه:&lt;/strong> إذا تم تحميل البيانات بسرعة كبيرة (أقل من 100 مللي ثانية)، فإن الحمل الزائد للبث لا يستحق ذلك. كما أن دفق SSR لا يمنحك تفاعلًا مستمرًا - لذلك، لا تزال بحاجة إلى وضع عرض تفاعلي.&lt;/p>
&lt;h2 id="تحسين-التنقل-ومعالجة-النماذج">تحسين التنقل ومعالجة النماذج&lt;/h2>
&lt;p>أحد التحسينات الدقيقة ولكن القوية في Blazor الحديث هو التنقل المحسن. افتراضيًا، يعترض Blazor نقرات الروابط الداخلية وعمليات إرسال النماذج، ويجلب محتوى الصفحة الجديد عبر &lt;code>fetch&lt;/code> ويصحح DOM بدلاً من القيام بالتنقل الكامل في المتصفح.&lt;/p>
&lt;p>وهذا يعني أن التنقل بين صفحات SSR الثابتة يبدو وكأنه منتجع صحي - لا يوجد فلاش كامل للصفحة، ويمكن الحفاظ على موضع التمرير، وتكون التجربة سلسة للغاية.&lt;/p>
&lt;h3 id="كيف-يعمل">كيف يعمل&lt;/h3>
&lt;p>عند تحميل البرنامج النصي Blazor (&lt;code>blazor.web.js&lt;/code>)، فإنه يعترض تلقائيًا النقرات على الروابط الداخلية. بدلاً من التنقل التقليدي في المتصفح، فإنه:&lt;/p>
&lt;ol>
&lt;li>يقدم طلب &lt;code>fetch&lt;/code> إلى عنوان URL المستهدف.&lt;/li>
&lt;li>يتلقى استجابة HTML.&lt;/li>
&lt;li>دمج المحتوى الجديد في DOM الحالي.&lt;/li>
&lt;li>يقوم بتحديث عنوان URL للمتصفح والتاريخ.&lt;/li>
&lt;/ol>
&lt;p>ليس عليك القيام بأي شيء لتمكين هذا - فهو قيد التشغيل افتراضيًا. ولكن يمكنك التحكم فيه:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&amp;lt;!-- Disable enhanced navigation for a specific link --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">a&lt;/span> href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/legacy-page&amp;#34;&lt;/span> data-enhance-nav&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;false&amp;#34;&lt;/span>&amp;gt;Legacy Page&amp;lt;/&lt;span style="color:#7ee787">a&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&amp;lt;!-- Force a full page reload for external-like behavior --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">a&lt;/span> href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/downloads/report.pdf&amp;#34;&lt;/span> data-enhance-nav&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;false&amp;#34;&lt;/span>&amp;gt;Download Report&amp;lt;/&lt;span style="color:#7ee787">a&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="معالجة-محسنة-للنماذج">معالجة محسنة للنماذج&lt;/h3>
&lt;p>النماذج تحصل على نفس المعاملة. عند استخدام &lt;code>EditForm&lt;/code> أو عنصر &lt;code>&amp;lt;form&amp;gt;&lt;/code> القياسي مع معالجة نموذج Blazor، يتم اعتراض عمليات الإرسال والتعامل معها عبر &lt;code>fetch&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/contact&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;EditForm Model=&amp;#34;contactModel&amp;#34; OnValidSubmit=&amp;#34;HandleSubmit&amp;#34; FormName=&amp;#34;contact&amp;#34; Enhance&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;DataAnnotationsValidator /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&amp;#34;mb-3&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label for=&amp;#34;name&amp;#34;&amp;gt;Name&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText id=&amp;#34;name&amp;#34; @bind-Value=&amp;#34;contactModel.Name&amp;#34; class=&amp;#34;form-control&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&amp;#34;() =&amp;gt; contactModel.Name&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&amp;#34;mb-3&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label for=&amp;#34;email&amp;#34;&amp;gt;Email&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText id=&amp;#34;email&amp;#34; @bind-Value=&amp;#34;contactModel.Email&amp;#34; class=&amp;#34;form-control&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&amp;#34;() =&amp;gt; contactModel.Email&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&amp;#34;mb-3&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label for=&amp;#34;message&amp;#34;&amp;gt;Message&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputTextArea id=&amp;#34;message&amp;#34; @bind-Value=&amp;#34;contactModel.Message&amp;#34; class=&amp;#34;form-control&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&amp;#34;() =&amp;gt; contactModel.Message&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&amp;#34;submit&amp;#34; class=&amp;#34;btn btn-primary&amp;#34;&amp;gt;Send&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/EditForm&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [SupplyParameterFromForm]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private ContactModel contactModel { get; set; } = new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task HandleSubmit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> await ContactService.SubmitAsync(contactModel);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> contactModel = new ContactModel();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>السمة &lt;code>Enhance&lt;/code> الموجودة على &lt;code>EditForm&lt;/code> تطلب من Blazor اعتراض إرسال النموذج. يتم نشر النموذج على الخادم، ويقوم الخادم بمعالجته وإعادة عرض الصفحة، ويتم بث HTML المحدث مرة أخرى - كل ذلك بدون التنقل الكامل للصفحة. إنها تبدو تفاعلية، ولكنها مقدمة بالكامل من خلال الخادم.&lt;/p>
&lt;p>لاحظ السمة &lt;code>[SupplyParameterFromForm]&lt;/code> - هذه هي الطريقة التي يربط بها Blazor بيانات النموذج المنشورة بالنموذج الخاص بك في سيناريوهات SSR الثابتة. إنه الجسر بين منشورات النماذج التقليدية ونموذج مكونات Blazor.&lt;/p>
&lt;h2 id="التفاعل-لكل-صفحة-ولكل-مكون">التفاعل لكل صفحة ولكل مكون&lt;/h2>
&lt;p>أحد أقوى جوانب النموذج الجديد هو أنه يمكنك مزج أوضاع العرض داخل تطبيق واحد. يمكن أن تكون صفحة قائمة منتجاتك عبارة عن SSR ثابت، ويمكن أن تكون عربة التسوق الخاصة بك خادمًا تفاعليًا، ويمكن أن تكون أداة تكوين منتجك عبارة عن Interactive WebAssembly - كل ذلك في نفس التطبيق.&lt;/p>
&lt;h3 id="ضبط-أوضاع-العرض-على-مستوى-المكون">ضبط أوضاع العرض على مستوى المكون&lt;/h3>
&lt;p>يمكنك ضبط وضع التجسيد مباشرةً على أحد المكونات باستخدام التوجيه &lt;code>@rendermode&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@* This component is interactive via Server *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@rendermode InteractiveServer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h3&amp;gt;Live Chat&amp;lt;/h3&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;!-- chat UI here --&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>أو يمكنك ضبطه عند استخدام مكون من أحد الوالدين:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@page &amp;#34;/product/{Id:int}&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;@product?.Name&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;p&amp;gt;@product?.Description&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;!-- This child component gets its own interactive render mode --&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;ProductConfigurator Product=&amp;#34;product&amp;#34; @rendermode=&amp;#34;InteractiveWebAssembly&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;!-- This stays static --&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;ProductReviews ProductId=&amp;#34;Id&amp;#34; /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter] public int Id { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private Product? product;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> product = await ProductService.GetByIdAsync(Id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في هذا المثال، الصفحة نفسها عبارة عن SSR ثابت. يعمل المكون &lt;code>ProductConfigurator&lt;/code> على WebAssembly من أجل تفاعل غني من جانب العميل. يظل المكون &lt;code>ProductReviews&lt;/code> ثابتًا لأنه يعرض البيانات فقط.&lt;/p>
&lt;h3 id="ضبط-أوضاع-العرض-على-مستوى-العالم">ضبط أوضاع العرض على مستوى العالم&lt;/h3>
&lt;p>إذا كنت تريد أن تكون جميع الصفحات تفاعلية بشكل افتراضي، فيمكنك ضبط وضع العرض على مستوى الجذر في &lt;code>App.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Routes&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">@&lt;/span>rendermode&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;InteractiveServer&amp;#34;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">```&lt;/span>&lt;span style="color:#f85149">وهذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يجعل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">كل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">صفحة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تفاعلية&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">عبر&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">عرض&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الخادم&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يزال&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بإمكان&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المكونات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الفردية&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تجاوز&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">هذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لزم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الأمر&lt;/span>.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">### قواعد مهمة يجب تذكرها
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">هناك&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بعض&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">القيود&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يجب&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">وضعها&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">في&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الاعتبار&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">عند&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مزج&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أوضاع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">العرض&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#f85149">لا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يمكن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يحتوي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المكون&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الفرعي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">على&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">وضع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">عرض&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;أكثر تفاعلية&amp;#34;&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">من&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المكون&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الأصلي&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">كان&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الأصل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ثابتًا،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فيمكن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يكون&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الأطفال&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ثابتين&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أو&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">متفاعلين&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ولكن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">كان&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أحد&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الوالدين&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">هو&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Interactive&lt;span style="color:#6e7681"> &lt;/span>Server&lt;span style="color:#f85149">،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فلا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يمكن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يكون&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الطفل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Interactive&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#d2a8ff;font-weight:bold">WebAssembly&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>(&lt;span style="color:#f85149">يجب&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يكون&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Server&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أو&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Auto).&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#f85149">لا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يمكن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">للمكونات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التفاعلية&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">استخدام&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الخدمات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المحددة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مباشرة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">من&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الأصول&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الثابتة&lt;/span>.&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تشغيل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أحد&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المكونات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">على&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>WebAssembly&lt;span style="color:#f85149">،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فلن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يتمكن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">من&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الوصول&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إلى&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مثيلات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>DbContext&lt;span style="color:#ff7b72;font-weight:bold">`&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">من&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">جانب&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الخادم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مباشرةً&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">—&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ستحتاج&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إلى&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">طبقة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">واجهة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">برمجة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التطبيقات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>(API).&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#f85149">لا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تنتقل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الحالة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تلقائيًا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بين&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أوضاع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التجسيد&lt;/span>.&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">عرض&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أحد&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المكونات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مسبقًا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">على&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الخادم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ثم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">قام&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بالتبديل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إلى&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>WebAssembly&lt;span style="color:#f85149">،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فستحتاج&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إلى&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التعامل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">استمرار&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الحالة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بشكل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">صريح&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المزيد&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">عن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">هذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">في&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">قسم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>.NET&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">10&lt;/span>.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">## ما الجديد في .NET 10
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">يواصل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>.NET&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">10&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">نهج&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التحسين&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التدريجي،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التركيز&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">على&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الموثوقية&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">وتجربة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المطور&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فيما&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يلي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أبرز&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مميزات&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تفاعل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Blazor:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#8b949e;font-style:italic">### تجربة إعادة الاتصال المحسنة
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">إذا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">كنت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">قد&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">استخدمت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">وضع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الخادم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التفاعلي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">في&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الإنتاج،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فمن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المحتمل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أنك&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">واجهت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تراكب&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إعادة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الاتصال&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تلك&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">اللحظة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ينقطع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فيها&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">اتصال&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>SignalR&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ويرى&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المستخدم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">رسالة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;جارٍ إعادة الاتصال...&amp;#34;&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">في&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>.NET&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">10&lt;/span>&lt;span style="color:#f85149">،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أصبحت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">هذه&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التجربة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أفضل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بشكل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ملحوظ&lt;/span>.&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#f85149">أصبح&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">منطق&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إعادة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الاتصال&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الآن&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أكثر&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ذكاءً&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فيما&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يتعلق&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بإعادة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المحاولة&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">بدلاً&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">من&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الفاصل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الزمني&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الثابت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لإعادة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المحاولة،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">فإنه&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">يستخدم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إستراتيجية&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التراجع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التي&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">تتكيف&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">مع&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الموقف&lt;/span>.&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">واجهة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">المستخدم&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أثناء&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">إعادة&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">الاتصال&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أصبحت&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أيضًا&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أكثر&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">صقلًا،&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">ولديك&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">روابط&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">أفضل&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">لتخصيص&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#f85149">التجربة&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">```&lt;/span>razor&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!&lt;/span>&lt;span style="color:#8b949e;font-style:italic">-- In your App.razor or layout --&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>id&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;components-reconnect-modal&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;reconnect-visible&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Connection&lt;span style="color:#6e7681"> &lt;/span>lost.&lt;span style="color:#6e7681"> &lt;/span>Attempting&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">to&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>reconnect...&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;spinner&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;reconnect-failed&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Could&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">not&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>reconnect&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">to&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>the&lt;span style="color:#6e7681"> &lt;/span>server.&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button&lt;span style="color:#6e7681"> &lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;location.reload()&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Reload&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;reconnect-rejected&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Your&lt;span style="color:#6e7681"> &lt;/span>session&lt;span style="color:#6e7681"> &lt;/span>has&lt;span style="color:#6e7681"> &lt;/span>expired.&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>a&lt;span style="color:#6e7681"> &lt;/span>href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#ff7b72">Return&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72">to&lt;/span>&lt;span style="color:#6e7681"> &lt;/span>Home&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>a&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#ff7b72">div&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يحاول إطار العمل الآن أيضًا إعادة الاتصال بقوة أكبر في حالات الفشل العابر، ويمكنه استعادة حالة الدائرة بشكل أكثر موثوقية. وهذا يعني عددًا أقل من لحظات &amp;ldquo;الرجاء إعادة تحميل الصفحة&amp;rdquo; للمستخدمين.&lt;/p>
&lt;h3 id="حالة-المكون-الثابتة-عبر-أوضاع-العرض">حالة المكون الثابتة عبر أوضاع العرض&lt;/h3>
&lt;p>هذه واحدة كبيرة. في .NET 9، عندما يتم عرض أحد المكونات مسبقًا على الخادم ثم ينتقل إلى WebAssembly (أو يقوم بالتبديل بين أوضاع العرض)، يتم فقدان حالة المكون. تتم إعادة تهيئة المكون بشكل فعال، مما قد يؤدي إلى عمليات جلب بيانات مكررة ووميض واجهة المستخدم.&lt;/p>
&lt;p>يعمل .NET 10 على تحسين خدمة &lt;code>PersistentComponentState&lt;/code> للعمل بسلاسة أكبر عبر انتقالات وضع العرض:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/weather&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveWebAssembly
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject PersistentComponentState ApplicationState
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Weather Forecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (forecasts is null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Loading&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> forecast &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> forecasts)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;forecast-card&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h4&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>forecast&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToShortDateString()&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h4&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>forecast&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Summary &lt;span style="color:#f85149">—&lt;/span> &lt;span style="color:#f85149">@&lt;/span>forecast&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>TemperatureC&lt;span style="color:#f85149">°&lt;/span>C&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>WeatherForecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">?&lt;/span> forecasts;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private PersistingComponentStateSubscription persistingSubscription;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override async Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> persistingSubscription &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> ApplicationState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>RegisterOnPersisting(PersistData);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>ApplicationState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>TryTakeFromJson&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>WeatherForecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;gt;&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;weather-forecasts&amp;#34;&lt;/span>, out &lt;span style="color:#ff7b72">var&lt;/span> restored))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">//&lt;/span> Data wasn&lt;span style="color:#a5d6ff">&amp;#39;t persisted from prerendering — fetch it&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> forecasts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await Http&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetFromJsonAsync&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>WeatherForecast&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;gt;&lt;/span>(&lt;span style="color:#a5d6ff">&amp;#34;api/weather&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> forecasts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> restored;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private Task PersistData()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ApplicationState&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>PersistAsJson(&lt;span style="color:#a5d6ff">&amp;#34;weather-forecasts&amp;#34;&lt;/span>, forecasts);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Task&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public void Dispose()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> persistingSubscription&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Dispose();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ومع التحسينات في .NET 10، يعمل هذا النمط بشكل أكثر موثوقية. تكون الحالة المتسلسلة أثناء العرض المسبق متاحة بشكل صحيح عند تهيئة المكون في وضعه التفاعلي، مما يؤدي إلى تجنب عمليات جلب البيانات المزدوجة والوميض القصير الذي قد يراه المستخدمون.&lt;/p>
&lt;h3 id="blazor-script-كأحد-أصول-الويب-الثابتة">Blazor Script كأحد أصول الويب الثابتة&lt;/h3>
&lt;p>في الإصدارات السابقة، تم تقديم ملف Blazor JavaScript (&lt;code>blazor.web.js&lt;/code>) من نقطة النهاية الداخلية لإطار العمل. في .NET 10، يتم تسليمه كأصل ويب ثابت. قد يبدو هذا تغييرًا بسيطًا، لكن له فوائد عملية:- &lt;strong>تخزين مؤقت أفضل:&lt;/strong> تحصل أصول الويب الثابتة على رؤوس ذاكرة تخزين مؤقت مناسبة وعناوين URL ذات بصمات أصابع، بحيث تقوم المتصفحات بتخزينها بشكل أكثر فعالية.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>متوافق مع CDN:&lt;/strong> نظرًا لأنه ملف ثابت عادي، يمكن لشبكات CDN تخزينه مؤقتًا وتقديمه من مواقع التخزين المؤقت.&lt;/li>
&lt;li>&lt;strong>الضغط:&lt;/strong> يتم تطبيق ضغط أصول الويب الثابت تلقائيًا، مما يؤدي إلى تقليل حجم البرنامج النصي على السلك.&lt;/li>
&lt;/ul>
&lt;p>لا تحتاج إلى تغيير كيفية الرجوع إليه — حيث يتعامل إطار العمل مع المسار المحدث تلقائيًا.&lt;/p>
&lt;h2 id="أفضل-الممارسات-اختيار-وضع-العرض-المناسب">أفضل الممارسات: اختيار وضع العرض المناسب&lt;/h2>
&lt;p>بعد العمل مع كل هذه الأوضاع عبر العديد من المشاريع، إليك إطار القرار الخاص بي:&lt;/p>
&lt;h3 id="ابدأ-بـ-ssr-الثابت">ابدأ بـ SSR الثابت&lt;/h3>
&lt;p>اجعل SSR الثابت هو الخيار الافتراضي. تتعلق معظم الصفحات في معظم التطبيقات بشكل أساسي بعرض البيانات. صفحات المنتج، ومنشورات المدونات، وملفات تعريف المستخدمين، وصفحات الإعدادات - لا تحتاج إلى تفاعل في الوقت الفعلي. يمنحك Static SSR أفضل أداء وأقل استخدام للموارد وأبسط نموذج عقلي.&lt;/p>
&lt;h3 id="أضف-التفاعل-عند-الحاجة-فقط">أضف التفاعل عند الحاجة فقط&lt;/h3>
&lt;p>حدد المكونات المحددة التي تحتاج إلى الاستجابة لتفاعلات المستخدم في الوقت الفعلي. زر &amp;ldquo;أعجبني&amp;rdquo;، وأداة الدردشة، وواجهة السحب والإفلات - كلها تحتاج إلى التفاعل. لكن الصفحة المحيطة بهم ربما لا تفعل ذلك.&lt;/p>
&lt;h3 id="استخدم-الوضع-التلقائي-التفاعلي-باعتباره-الوضع-التفاعلي-المفضل-لديك">استخدم الوضع التلقائي التفاعلي باعتباره الوضع التفاعلي المفضل لديك&lt;/h3>
&lt;p>عندما تحتاج إلى التفاعل، غالبًا ما يكون Interactive Auto هو الخيار الافتراضي الأفضل. فهو يوفر لك تحميلات أولية سريعة (عرض الخادم) مع التنفيذ النهائي من جانب العميل (WebAssembly). يحصل المستخدم على أفضل ما في كلا العالمين، وتكتب الكود الخاص بك مرة واحدة.&lt;/p>
&lt;h3 id="حجز-الخادم-التفاعلي-لحالات-محددة">حجز الخادم التفاعلي لحالات محددة&lt;/h3>
&lt;p>استخدم الخادم التفاعلي عندما:&lt;/p>
&lt;ul>
&lt;li>يحتاج المكون الخاص بك إلى الوصول المباشر إلى موارد الخادم (قواعد البيانات ونظام الملفات وواجهات برمجة التطبيقات الداخلية).&lt;/li>
&lt;li>يعد حجم تنزيل WebAssembly مصدر قلق ولا يمكنك استخدام Auto.&lt;/li>
&lt;li>أنت بحاجة إلى تشغيل المكون دائمًا على الخادم لأسباب أمنية (على سبيل المثال، معالجة البيانات الحساسة).&lt;/li>
&lt;/ul>
&lt;h3 id="استخدم-بث-ssr-بسخاء">استخدم بث SSR بسخاء&lt;/h3>
&lt;p>إذا كانت صفحات SSR الثابتة تجلب أي بيانات، أضف &lt;code>[StreamRendering]&lt;/code>. التكلفة ضئيلة والتحسن الملحوظ في الأداء كبير. يرى المستخدمون المحتوى يظهر بشكل تدريجي بدلاً من التحديق في صفحة فارغة.&lt;/p>
&lt;h3 id="تعامل-مع-انتقالات-الحالة-بعناية">تعامل مع انتقالات الحالة بعناية&lt;/h3>
&lt;p>إذا كنت تستخدم Interactive Auto أو تخلط العرض المسبق مع WebAssembly، فاستخدم دائمًا &lt;code>PersistentComponentState&lt;/code> لتجنب عمليات جلب البيانات المكررة. سوف يشكرك المستخدمون على عدم وجود محتوى وامض.&lt;/p>
&lt;h3 id="ضع-شجرة-المكونات-في-الاعتبار">ضع شجرة المكونات في الاعتبار&lt;/h3>
&lt;p>تذكر قواعد التسلسل الهرمي لوضع التجسيد. قم بتخطيط شجرة المكونات الخاصة بك بحيث تكون الحدود التفاعلية منطقية. النمط الشائع هو أن يكون لديك تخطيط ثابت مع &amp;ldquo;جزر&amp;rdquo; تفاعلية مضمنة عند الحاجة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>@* Layout: Static SSR *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NavMenu /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;UserMenu @rendermode=&amp;#34;InteractiveServer&amp;#34; /&amp;gt; @* Needs real-time auth state *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/header&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;main&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @Body
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/main&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;footer&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ChatWidget @rendermode=&amp;#34;InteractiveAuto&amp;#34; /&amp;gt; @* Rich interactivity *@
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/footer&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>يمثل نموذج التفاعل الخاص بـ Blazor في .NET 9 و.NET 10 أسلوبًا ناضجًا ومدروسًا جيدًا لبناء تطبيقات الويب. إن القدرة على اختيار أوضاع العرض لكل مكون، والتنقل المحسّن السلس، وتدفق SSR، والتحسينات المستمرة في إعادة الاتصال وإدارة الحالة تجعله خيارًا مقنعًا لمجموعة واسعة من التطبيقات.الفكرة الأساسية هي أن &lt;strong>التفاعل عبارة عن نطاق، وليس خيارًا ثنائيًا&lt;/strong>. يمكن أن يكون معظم تطبيقك ثابتًا. تحتاج بعض الأجزاء إلى تفاعل يحركه الخادم. قد يستفيد البعض من التشغيل في المتصفح. يتيح لك Blazor الآن اتخاذ هذا الاختيار بأدق التفاصيل - المكون الفردي - دون محاربة إطار العمل.&lt;/p>
&lt;p>إذا كنت تبدأ مشروعًا جديدًا، فإن نصيحتي بسيطة: أنشئ تطبيق Blazor Web App، وابدأ بـ SSR الثابت، وأضف &lt;code>[StreamRendering]&lt;/code> إلى الصفحات المليئة بالبيانات، وقم بترقية المكونات الفردية إلى الأوضاع التفاعلية فقط عندما تحتاج إليها. سينتهي بك الأمر بالحصول على تطبيق سريع وفعال وقابل للتوسع بشكل جيد.&lt;/p>
&lt;p>أتمنى لك حظًا سعيدًا في البرمجة، وكما هو الحال دائمًا، لا تتردد في التواصل معنا إذا كانت لديك أسئلة!&lt;/p></content:encoded><category>Blazor</category><category>.NET</category><category>Web Development</category></item><item><title>الحد الأدنى من واجهات برمجة التطبيقات في .NET: إنشاء واجهات برمجة تطبيقات HTTP خفيفة الوزن</title><link>https://emimontesdeoca.github.io/ar/posts/minimal-apis-dotnet/</link><pubDate>Thu, 10 Apr 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/minimal-apis-dotnet/</guid><description>دليل شامل لإنشاء واجهات برمجة تطبيقات HTTP نظيفة وسريعة باستخدام واجهات برمجة التطبيقات .NET Minimal - بدءًا من معالجات المسار وربط المعلمات وحتى عوامل التصفية وتكامل OpenAPI.</description><content:encoded>&lt;p>إذا كنت تقوم ببناء واجهات برمجة التطبيقات باستخدام ASP.NET Core لفترة من الوقت، فمن المحتمل أنك على دراية كبيرة بالنهج القائم على وحدة التحكم: قم بإنشاء فئة وحدة تحكم، وقم بتزيينها بالسمات، وإدخال خدماتك من خلال المنشئ، وربط مساراتك. إنه يعمل بشكل جيد - ولكن في بعض الأحيان يبدو الأمر وكأنك تكتب الكثير من الاحتفالات لما يرقى إلى &amp;ldquo;قبول هذا الطلب، القيام بشيء ما، الرد على الرد&amp;rdquo;.&lt;/p>
&lt;p>هذه هي بالضبط المشكلة التي تم تصميم Minimal APIs لحلها. تم تقديم الحد الأدنى من واجهات برمجة التطبيقات (APIs) في .NET 6، مما يتيح لك تحديد نقاط نهاية HTTP مع القليل جدًا من القواعد النموذجية. لا توجد وحدات تحكم، ولا سمات، ولا تلاعب بفئة بدء التشغيل - ما عليك سوى توجيه تعيينات المسار إلى المعالج بأسلوب نظيف وعملي.&lt;/p>
&lt;p>في هذا المنشور، أريد أن أطلعك على كل ما تحتاج إلى معرفته حول Minimal APIs: بدءًا من نقطة النهاية الأولى وحتى تنظيم التطبيقات الكبيرة، والتعامل مع المصادقة، والتحقق من الصحة، ومستندات OpenAPI، واعتبارات الأداء. دعونا نصل الى ذلك.&lt;/p>
&lt;h2 id="لماذا-الحد-الأدنى-من-واجهات-برمجة-التطبيقات">لماذا الحد الأدنى من واجهات برمجة التطبيقات؟&lt;/h2>
&lt;p>قبل أن نبدأ، دعونا نتحدث عن &lt;em>لماذا&lt;/em> تختار الحد الأدنى من واجهات برمجة التطبيقات بدلاً من النهج التقليدي القائم على وحدة التحكم.&lt;/p>
&lt;p>&lt;strong>وحدات التحكم&lt;/strong> رائعة عندما تحتاج إلى إطار عمل منظم ومحدد. إنها توفر لك ربط النماذج، والمرشحات، والتفاوض على المحتوى، والفصل الواضح بين الاهتمامات خارج الصندوق. بالنسبة لتطبيقات المؤسسات الكبيرة التي تضم العشرات من المطورين، يمكن أن يكون الاتساق الذي تفرضه وحدات التحكم مفيدًا حقًا.&lt;/p>
&lt;p>&lt;strong>الحد الأدنى من واجهات برمجة التطبيقات&lt;/strong>، من ناحية أخرى، يمكنك التألق عندما تريد:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>نموذج معياري أقل&lt;/strong> — لا توجد فئات لوحدات التحكم، ولا توجد سمات &lt;code>[ApiController]&lt;/code>، ولا يوجد تكوين منفصل لبدء التشغيل.&lt;/li>
&lt;li>&lt;strong>بدء تشغيل أسرع&lt;/strong> — عدد أقل من العمليات المستندة إلى الانعكاس في وقت التمهيد.&lt;/li>
&lt;li>&lt;strong>نموذج عقلي أبسط&lt;/strong> — يتم تعيين المسار مباشرةً إلى المعالج. هذا كل شيء.&lt;/li>
&lt;li>&lt;strong>متوافق مع الخدمات الصغيرة&lt;/strong> — عندما تحتوي واجهة برمجة التطبيقات لديك على 5 إلى 10 نقاط نهاية، قد يبدو إعداد وحدة التحكم الكاملة أمرًا مبالغًا فيه.&lt;/li>
&lt;/ul>
&lt;p>والخبر السار هو أن هذا ليس قرارًا إما أو. يمكنك مزج وحدات التحكم والحد الأدنى من واجهات برمجة التطبيقات في نفس المشروع. ولكن بمجرد أن تعتاد على الحد الأدنى من النهج، قد تجد نفسك تلجأ إليه في كثير من الأحيان أكثر مما تتوقع.&lt;/p>
&lt;h2 id="البدء">البدء&lt;/h2>
&lt;p>لنقم بإنشاء Minimal API من البداية. إذا كان لديك .NET SDK مثبتًا، فالأمر بسيط مثل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new web -n MyMinimalApi
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd MyMinimalApi
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمنحك هذا &lt;code>Program.cs&lt;/code> الذي يبدو كما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, () =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Hello World!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا كل شيء. هذه واجهة برمجة تطبيقات عاملة. لا &lt;code>Startup.cs&lt;/code>، ولا فئة وحدة التحكم، ولا تكوين التوجيه. تقوم بتشغيل &lt;code>dotnet run&lt;/code>، ثم تضغط على &lt;code>http://localhost:5000&lt;/code>، وستحصل على &amp;ldquo;Hello World!&amp;rdquo; خلف. البساطة هنا هي بيت القصيد.&lt;/p>
&lt;p>لنجعلها أكثر فائدة قليلًا باستخدام واجهة برمجة تطبيقات المهام الكلاسيكية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> todos = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;Todo&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, () =&amp;gt; todos);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todos.FirstOrDefault(t =&amp;gt; t.Id == id) &lt;span style="color:#ff7b72">is&lt;/span> { } todo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? Results.Ok(todo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : Results.NotFound());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, (Todo todo) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDelete(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = todos.FirstOrDefault(t =&amp;gt; t.Id == id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (todo &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todos.Remove(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.NoContent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Todo&lt;/span>(&lt;span style="color:#ff7b72">int&lt;/span> Id, &lt;span style="color:#ff7b72">string&lt;/span> Title, &lt;span style="color:#ff7b72">bool&lt;/span> IsComplete);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لدينا CRUD كامل في أقل من 30 سطرًا. هذه هي قوة النهج البسيط.&lt;/p>
&lt;h2 id="معالجات-الطريق">معالجات الطريق&lt;/h2>
&lt;p>معالجات المسار هي الوظائف التي يتم تنفيذها عندما يتطابق الطلب مع المسار. لديك عدة خيارات لتحديدها.&lt;/p>
&lt;h3 id="تعبيرات-لامدا">تعبيرات لامدا&lt;/h3>
&lt;p>النهج الأكثر شيوعًا، وما ستراه في معظم الأمثلة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/hello&amp;#34;&lt;/span>, () =&amp;gt; &lt;span style="color:#a5d6ff">&amp;#34;Hello!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/hello/{name}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string&lt;/span> name) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;Hello, {name}!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="مجموعات-الطريقة">مجموعات الطريقة&lt;/h3>
&lt;p>للحصول على منطق أكثر تعقيدًا، يمكنك الإشارة إلى طريقة مُسمّاة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, GetProducts);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, CreateProduct);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> IResult GetProducts(AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Results.Ok(db.Products.ToList());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> IResult CreateProduct(Product product, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Products.Add(product);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.SaveChanges();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/products/{product.Id}&amp;#34;&lt;/span>, product);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```هذا&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">النهج&lt;/span> &lt;span style="color:#f85149">المفضل&lt;/span> &lt;span style="color:#f85149">لدي&lt;/span> &lt;span style="color:#f85149">لأي&lt;/span> &lt;span style="color:#f85149">شيء&lt;/span> &lt;span style="color:#f85149">يتجاوز&lt;/span> &lt;span style="color:#f85149">الخطوط&lt;/span> &lt;span style="color:#f85149">الفردية&lt;/span>. &lt;span style="color:#f85149">فهو&lt;/span> &lt;span style="color:#f85149">يحافظ&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">قسم&lt;/span> &lt;span style="color:#f85149">تعيين&lt;/span> &lt;span style="color:#f85149">المسار&lt;/span> &lt;span style="color:#f85149">الخاص&lt;/span> &lt;span style="color:#f85149">بك&lt;/span> &lt;span style="color:#f85149">نظيفًا&lt;/span> &lt;span style="color:#f85149">وقابلاً&lt;/span> &lt;span style="color:#f85149">للقراءة&lt;/span> - &lt;span style="color:#f85149">يمكنك&lt;/span> &lt;span style="color:#f85149">رؤية&lt;/span> &lt;span style="color:#f85149">نقاط&lt;/span> &lt;span style="color:#f85149">النهاية&lt;/span> &lt;span style="color:#f85149">الموجودة&lt;/span> &lt;span style="color:#f85149">بنظرة&lt;/span> &lt;span style="color:#f85149">سريعة&lt;/span> &lt;span style="color:#f85149">دون&lt;/span> &lt;span style="color:#f85149">الخوض&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">تفاصيل&lt;/span> &lt;span style="color:#f85149">التنفيذ&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">###&lt;/span> &lt;span style="color:#f85149">الوظائف&lt;/span> &lt;span style="color:#f85149">المحلية&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">يمكنك&lt;/span> &lt;span style="color:#f85149">أيضًا&lt;/span> &lt;span style="color:#f85149">استخدام&lt;/span> &lt;span style="color:#f85149">الدوال&lt;/span> &lt;span style="color:#f85149">المحلية،&lt;/span> &lt;span style="color:#f85149">وهو&lt;/span> &lt;span style="color:#f85149">أمر&lt;/span> &lt;span style="color:#f85149">مفيد&lt;/span> &lt;span style="color:#f85149">عندما&lt;/span> &lt;span style="color:#f85149">تريد&lt;/span> &lt;span style="color:#f85149">إبقاء&lt;/span> &lt;span style="color:#f85149">المعالجات&lt;/span> &lt;span style="color:#f85149">قريبة&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">تعريفات&lt;/span> &lt;span style="color:#f85149">المسار&lt;/span> &lt;span style="color:#f85149">الخاصة&lt;/span> &lt;span style="color:#f85149">بها&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/health&amp;#34;&lt;/span>, CheckHealth);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>IResult CheckHealth()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Some health check logic&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> { Status = &lt;span style="color:#a5d6ff">&amp;#34;Healthy&amp;#34;&lt;/span>, Timestamp = DateTime.UtcNow });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="ربط-المعلمة">ربط المعلمة&lt;/h2>
&lt;p>أحد الأشياء التي أقدرها حقًا بشأن Minimal APIs هو مدى سهولة ربط المعلمات. يحدد إطار العمل مكان سحب القيم بناءً على السياق والنوع.&lt;/p>
&lt;h3 id="معلمات-الطريق">معلمات الطريق&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/users/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;User {id}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/files/{*path}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string&lt;/span> path) =&amp;gt; &lt;span style="color:#a5d6ff">$&amp;#34;File: {path}&amp;#34;&lt;/span>); &lt;span style="color:#8b949e;font-style:italic">// catch-all&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="معلمات-سلسلة-الاستعلام">معلمات سلسلة الاستعلام&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/search&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">string?&lt;/span> query, &lt;span style="color:#ff7b72">int&lt;/span> page = &lt;span style="color:#a5d6ff">1&lt;/span>, &lt;span style="color:#ff7b72">int&lt;/span> pageSize = &lt;span style="color:#a5d6ff">20&lt;/span>) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> { query, page, pageSize }));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تصبح الأنواع الخالية معلمات اختيارية. تعمل القيم الافتراضية تمامًا كما تتوقع.&lt;/p>
&lt;h3 id="نص-الطلب">نص الطلب&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/orders&amp;#34;&lt;/span>, (Order order) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// &amp;#39;order&amp;#39; is automatically deserialized from the JSON body&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/orders/{order.Id}&amp;#34;&lt;/span>, order);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="ربط-الرأس-والخدمة">ربط الرأس والخدمة&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/protected&amp;#34;&lt;/span>, (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [FromHeader(Name = &amp;#34;X-Api-Key&amp;#34;)] &lt;span style="color:#ff7b72">string&lt;/span> apiKey,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [FromServices] ILogger&amp;lt;Program&amp;gt; logger) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Request with API key: {Key}&amp;#34;&lt;/span>, apiKey[..&lt;span style="color:#a5d6ff">4&lt;/span>] + &lt;span style="color:#a5d6ff">&amp;#34;****&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(&lt;span style="color:#a5d6ff">&amp;#34;Authorized&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="httpcontext-وhttprequest">HttpContext وHttpRequest&lt;/h3>
&lt;p>عندما تحتاج إلى الوصول إلى المستوى الأدنى:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/info&amp;#34;&lt;/span>, (HttpContext context) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> userAgent = context.Request.Headers.UserAgent.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> ip = context.Connection.RemoteIpAddress?.ToString();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> { userAgent, ip });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="ربط-مخصص-باستخدام-bindasync">ربط مخصص باستخدام BindAsync&lt;/h3>
&lt;p>بالنسبة للأنواع المعقدة، يمكنك تنفيذ طريقة ثابتة &lt;code>BindAsync&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">record&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">PaginationParams&lt;/span>(&lt;span style="color:#ff7b72">int&lt;/span> Page, &lt;span style="color:#ff7b72">int&lt;/span> PageSize)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> ValueTask&amp;lt;PaginationParams?&amp;gt; BindAsync(HttpContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span>.TryParse(context.Request.Query[&lt;span style="color:#a5d6ff">&amp;#34;page&amp;#34;&lt;/span>], &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> page);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span>.TryParse(context.Request.Query[&lt;span style="color:#a5d6ff">&amp;#34;pageSize&amp;#34;&lt;/span>], &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> pageSize);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">new&lt;/span> PaginationParams(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Page: page &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span> ? page : &lt;span style="color:#a5d6ff">1&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PageSize: pageSize &amp;gt; &lt;span style="color:#a5d6ff">0&lt;/span> ? Math.Min(pageSize, &lt;span style="color:#a5d6ff">100&lt;/span>) : &lt;span style="color:#a5d6ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> );
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ValueTask.FromResult&amp;lt;PaginationParams?&amp;gt;(result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/items&amp;#34;&lt;/span>, (PaginationParams pagination, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> items = db.Items
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Skip((pagination.Page - &lt;span style="color:#a5d6ff">1&lt;/span>) * pagination.PageSize)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Take(pagination.PageSize)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(items);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا قوي بشكل لا يصدق. يمكنك تحديد منطق الربط مرة واحدة وإعادة استخدامه عبر جميع نقاط النهاية الخاصة بك.&lt;/p>
&lt;h2 id="التحقق-من-الصحة">التحقق من الصحة&lt;/h2>
&lt;p>أحد المجالات التي لا يمنحك فيها الحد الأدنى من واجهات برمجة التطبيقات (APIs) قدرًا كبيرًا من خارج الصندوق مقارنةً بوحدات التحكم هو التحقق من صحة النموذج. لا يوجد تنفيذ تلقائي لـ &lt;code>[Required]&lt;/code> أو &lt;code>[StringLength]&lt;/code>. ولكن هناك أنماط نظيفة للتعامل معها.&lt;/p>
&lt;h3 id="التحقق-اليدوي">التحقق اليدوي&lt;/h3>
&lt;p>أبسط طريقة — فقط قم بالتحقق من صحة المعالج:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/users&amp;#34;&lt;/span>, (CreateUserRequest request) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrWhiteSpace(request.Email))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Email is required&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (request.Name?.Length &amp;gt; &lt;span style="color:#a5d6ff">100&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Name must be 100 characters or less&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create user...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/users/{request.Email}&amp;#34;&lt;/span>, request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="استخدام-مكتبة-التحقق-من-الصحة">استخدام مكتبة التحقق من الصحة&lt;/h3>
&lt;p>بالنسبة لأي شيء غير تافه، أنصحك بالوصول إلى FluentValidation أو مكتبة مشابهة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CreateUserValidator&lt;/span> : AbstractValidator&amp;lt;CreateUserRequest&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CreateUserValidator()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuleFor(x =&amp;gt; x.Email).NotEmpty().EmailAddress();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuleFor(x =&amp;gt; x.Name).NotEmpty().MaximumLength(&lt;span style="color:#a5d6ff">100&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuleFor(x =&amp;gt; x.Age).InclusiveBetween(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">150&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكنك توصيل هذا إلى نقاط النهاية الخاصة بك من خلال مرشحات نقطة النهاية، وهو ما يقودنا إلى القسم التالي.&lt;/p>
&lt;h2 id="مرشحات-نقطة-النهاية">مرشحات نقطة النهاية&lt;/h2>
&lt;p>تعد مرشحات نقطة النهاية واحدة من أفضل ميزات Minimal APIs. فكر فيها كبرامج وسيطة، ولكنها تستهدف نقاط نهاية محددة بدلاً من خط الأنابيب بأكمله. لقد تم تقديمها في .NET 7 وهي رائعة للاهتمامات الشاملة.&lt;/p>
&lt;h3 id="الفلتر-الأساسي">الفلتر الأساسي&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, (Todo todo) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Handle the request&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.AddEndpointFilter(&lt;span style="color:#ff7b72">async&lt;/span> (context, next) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = context.GetArgument&amp;lt;Todo&amp;gt;(&lt;span style="color:#a5d6ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(todo.Title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Title is required&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">await&lt;/span> next(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="مرشح-التحقق-من-الصحة-باستخدام-fluentvalidation">مرشح التحقق من الصحة باستخدام FluentValidation&lt;/h3>
&lt;p>هنا يصبح الأمر قويًا حقًا - مرشح التحقق من الصحة القابل لإعادة الاستخدام:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ValidationFilter&lt;/span>&amp;lt;T&amp;gt; : IEndpointFilter &lt;span style="color:#ff7b72">where&lt;/span> T : &lt;span style="color:#ff7b72">class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IValidator&amp;lt;T&amp;gt; _validator;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ValidationFilter(IValidator&amp;lt;T&amp;gt; validator)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _validator = validator;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> ValueTask&amp;lt;&lt;span style="color:#ff7b72">object?&lt;/span>&amp;gt; InvokeAsync(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EndpointFilterInvocationContext context,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EndpointFilterDelegate next)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> model = context.Arguments
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .OfType&amp;lt;T&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .FirstOrDefault();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (model &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Error = &lt;span style="color:#a5d6ff">&amp;#34;Request body is required&amp;#34;&lt;/span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> _validator.ValidateAsync(model);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!result.IsValid)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> errors = result.Errors
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(e =&amp;gt; e.PropertyName)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToDictionary(g =&amp;gt; g.Key, g =&amp;gt; g.Select(e =&amp;gt; e.ErrorMessage).ToArray());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.ValidationProblem(errors);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">await&lt;/span> next(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Usage&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/users&amp;#34;&lt;/span>, (CreateUserRequest request) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Only reached if validation passes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/users/{request.Email}&amp;#34;&lt;/span>, request);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.AddEndpointFilter&amp;lt;ValidationFilter&amp;lt;CreateUserRequest&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="مرشح-التسجيل">مرشح التسجيل&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, (AppDbContext db) =&amp;gt; db.Products.ToList())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddEndpointFilter(&lt;span style="color:#ff7b72">async&lt;/span> (context, next) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> logger = context.HttpContext
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .RequestServices.GetRequiredService&amp;lt;ILogger&amp;lt;Program&amp;gt;&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> sw = Stopwatch.StartNew();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> next(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sw.Stop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Endpoint executed in {Elapsed}ms&amp;#34;&lt;/span>, sw.ElapsedMilliseconds);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكنك ربط مرشحات متعددة، ويتم تنفيذها بالترتيب الذي تمت إضافتها به - تمامًا مثل البرامج الوسيطة.&lt;/p>
&lt;h2 id="تكامل-openapiswagger">تكامل OpenAPI/Swagger&lt;/h2>
&lt;p>لم تعد وثائق API الجيدة اختيارية بعد الآن. لحسن الحظ، تتمتع Minimal APIs بدعم من الدرجة الأولى لـ OpenAPI من خلال الحزمة &lt;code>Microsoft.AspNetCore.OpenApi&lt;/code>.&lt;/p>
&lt;h3 id="الإعداد-الأساسي">الإعداد الأساسي&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, () =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;Todo&amp;gt;())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithName(&lt;span style="color:#a5d6ff">&amp;#34;GetTodos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithDescription(&lt;span style="color:#a5d6ff">&amp;#34;Retrieves all todo items&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="بيانات-تعريف-نقطة-النهاية-الغنية">بيانات تعريف نقطة النهاية الغنية&lt;/h3>
&lt;p>يمكنك تقديم معلومات مفصلة حول نقاط النهاية الخاصة بك:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, (Todo todo) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.WithName(&lt;span style="color:#a5d6ff">&amp;#34;CreateTodo&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.WithDescription(&lt;span style="color:#a5d6ff">&amp;#34;Creates a new todo item&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.Accepts&amp;lt;Todo&amp;gt;(&lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.Produces&amp;lt;Todo&amp;gt;(StatusCodes.Status201Created)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.Produces(StatusCodes.Status400BadRequest)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.ProducesValidationProblem();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="استخدام-withopenapi-للتخصيص">استخدام WithOpenApi للتخصيص&lt;/h3>
&lt;p>للتحكم الدقيق في مستند OpenAPI الذي تم إنشاؤه:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id) =&amp;gt; Results.Ok(&lt;span style="color:#ff7b72">new&lt;/span> Todo(id, &lt;span style="color:#a5d6ff">&amp;#34;Sample&amp;#34;&lt;/span>, &lt;span style="color:#79c0ff">false&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithOpenApi(operation =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.Summary = &lt;span style="color:#a5d6ff">&amp;#34;Get a specific todo&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.Description = &lt;span style="color:#a5d6ff">&amp;#34;Retrieves a single todo item by its unique identifier.&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.Parameters[&lt;span style="color:#a5d6ff">0&lt;/span>].Description = &lt;span style="color:#a5d6ff">&amp;#34;The unique identifier of the todo item&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> operation;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمنحك هذا نفس مستوى التحكم في التوثيق الذي تحصل عليه مع تعليقات Swashbuckle XML على وحدات التحكم، ولكن بطريقة أكثر وضوحًا، تعتمد على الكود أولاً.&lt;/p>
&lt;h2 id="المصادقة-والترخيص">المصادقة والترخيص&lt;/h2>
&lt;p>يتبع تأمين نقاط نهاية Minimal API نفس الأنماط مثل بقية ASP.NET Core - ما عليك سوى تطبيقها بشكل مختلف.&lt;/p>
&lt;h3 id="الإعداد-الأساسيcsharp">الإعداد الأساسي```csharp&lt;/h3>
&lt;p>var builder = WebApplication.CreateBuilder(args);&lt;/p>
&lt;p>builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =&amp;gt;
{
options.Authority = &amp;ldquo;&lt;a href="https://your-identity-provider.com">https://your-identity-provider.com&lt;/a>&amp;rdquo;;
options.Audience = &amp;ldquo;your-api&amp;rdquo;;
});&lt;/p>
&lt;p>builder.Services.AddAuthorizationBuilder()
.AddPolicy(&amp;ldquo;AdminOnly&amp;rdquo;, policy =&amp;gt; policy.RequireRole(&amp;ldquo;Admin&amp;rdquo;))
.AddPolicy(&amp;ldquo;PremiumUser&amp;rdquo;, policy =&amp;gt; policy.RequireClaim(&amp;ldquo;subscription&amp;rdquo;, &amp;ldquo;premium&amp;rdquo;));&lt;/p>
&lt;p>var app = builder.Build();&lt;/p>
&lt;p>app.UseAuthentication();
app.UseAuthorization();&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>### تطبيق التفويض على نقاط النهاية
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>```csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// Require any authenticated user
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&amp;#34;/profile&amp;#34;, (ClaimsPrincipal user) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> return Results.Ok(new
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = user.FindFirstValue(ClaimTypes.Name),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Email = user.FindFirstValue(ClaimTypes.Email)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}).RequireAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// Require a specific policy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDelete(&amp;#34;/admin/users/{id}&amp;#34;, (int id) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> // Delete user logic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> return Results.NoContent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}).RequireAuthorization(&amp;#34;AdminOnly&amp;#34;);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// Allow anonymous access
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&amp;#34;/public/health&amp;#34;, () =&amp;gt; Results.Ok(new { Status = &amp;#34;Healthy&amp;#34; }))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AllowAnonymous();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لاحظ كيف يمكنك إدخال &lt;code>ClaimsPrincipal&lt;/code> مباشرة في معلمات المعالج الخاص بك - يعتني إطار العمل بالباقي. هذا هو أحد الأشياء الصغيرة التي تجعل Minimal APIs تبدو أنيقة حقًا.&lt;/p>
&lt;h2 id="تنظيم-واجهات-برمجة-التطبيقات-الكبيرة">تنظيم واجهات برمجة التطبيقات الكبيرة&lt;/h2>
&lt;p>إن أهم ما يميز الغرفة مع الحد الأدنى من واجهات برمجة التطبيقات (APIs) هو التنظيم. عندما يحتوي &lt;code>Program.cs&lt;/code> على 50 نقطة نهاية، يصبح الأمر في حالة من الفوضى. فيما يلي الأنماط التي أستخدمها لإبقاء الأمور قابلة للإدارة.&lt;/p>
&lt;h3 id="مجموعات-الطريق">مجموعات الطريق&lt;/h3>
&lt;p>تتيح لك مجموعات المسار (المقدمة في .NET 7) مشاركة التكوين عبر نقاط النهاية ذات الصلة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> todos = app.MapGroup(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .RequireAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>todos.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, (AppDbContext db) =&amp;gt; db.Todos.ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>todos.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, (&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db) =&amp;gt; db.Todos.FindAsync(id));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>todos.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, (Todo todo, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تشترك جميع نقاط النهاية في المجموعة في البادئة &lt;code>/todos&lt;/code> والعلامة &lt;code>Todos&lt;/code> ومتطلبات الترخيص. ينظف.&lt;/p>
&lt;h3 id="طرق-التمديد">طرق التمديد&lt;/h3>
&lt;p>هذا هو النمط الذي يقاس حقًا. انقل كل مجموعة من نقاط النهاية إلى فئتها الثابتة الخاصة بها:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Endpoints/TodoEndpoints.cs&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TodoEndpoints&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> RouteGroupBuilder MapTodoEndpoints(&lt;span style="color:#ff7b72">this&lt;/span> WebApplication app)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> &lt;span style="color:#ff7b72">group&lt;/span> = app.MapGroup(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>).WithTags(&lt;span style="color:#a5d6ff">&amp;#34;Todos&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, GetAll);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, GetById);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, Create);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapPut(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, Update);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">group&lt;/span>.MapDelete(&lt;span style="color:#a5d6ff">&amp;#34;/{id:int}&amp;#34;&lt;/span>, Delete);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">group&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; GetAll(AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; Results.Ok(&lt;span style="color:#ff7b72">await&lt;/span> db.Todos.ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; GetById(&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> =&amp;gt; &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id) &lt;span style="color:#ff7b72">is&lt;/span> { } todo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? Results.Ok(todo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; Create(Todo todo, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; Update(&lt;span style="color:#ff7b72">int&lt;/span> id, Todo updated, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (todo &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todo.Title = updated.Title;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> todo.IsComplete = updated.IsComplete;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;IResult&amp;gt; Delete(&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (todo &lt;span style="color:#ff7b72">is&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>) &lt;span style="color:#ff7b72">return&lt;/span> Results.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Remove(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.NoContent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Program.cs — stays clean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapTodoEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapUserEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapOrderEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يصبح &lt;code>Program.cs&lt;/code> الخاص بك جدول محتويات لواجهة برمجة التطبيقات (API) الخاصة بك. تعيش كل مجموعة نقاط نهاية في ملف خاص بها. هذا هو النهج الذي أوصي به لتطبيقات الإنتاج.&lt;/p>
&lt;h3 id="مكتبة-كارتر">مكتبة كارتر&lt;/h3>
&lt;p>إذا كنت تريد المزيد من البنية، فإن مكتبة كارتر توفر منهجًا يعتمد على الوحدة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TodoModule&lt;/span> : ICarterModule
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> AddRoutes(IEndpointRouteBuilder app)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (Todo todo, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يكتشف كارتر جميع الوحدات ويسجلها تلقائيًا. إنها حل وسط لطيف بين نهج Minimal API الخام ووحدات التحكم الكاملة.&lt;/p>
&lt;h2 id="النتائج-المكتوبة-وأنواع-الاستجابات">النتائج المكتوبة وأنواع الاستجابات&lt;/h2>
&lt;p>بدءًا من .NET 7، يمكنك استخدام &lt;code>TypedResults&lt;/code> بدلاً من &lt;code>Results&lt;/code> للاستجابات الآمنة. قد يبدو هذا تغييرًا بسيطًا، لكن له فوائد حقيقية لتوثيق OpenAPI وقابلية الاختبار.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/todos/{id:int}&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;Results&amp;lt;Ok&amp;lt;Todo&amp;gt;, NotFound&amp;gt;&amp;gt; (&lt;span style="color:#ff7b72">int&lt;/span> id, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> todo = &lt;span style="color:#ff7b72">await&lt;/span> db.Todos.FindAsync(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> todo &lt;span style="color:#ff7b72">is&lt;/span> not &lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? TypedResults.Ok(todo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> : TypedResults.NotFound();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يخبر نوع الإرجاع &lt;code>Results&amp;lt;Ok&amp;lt;Todo&amp;gt;, NotFound&amp;gt;&lt;/code> بوضوح إطار العمل (ومستندات OpenAPI الخاصة بك) بأنواع الاستجابة التي يمكن أن تنتجها نقطة النهاية هذه بالضبط. لا مزيد من التخمين، ولا مزيد من الاستدعاءات اليدوية &lt;code>Produces&amp;lt;&amp;gt;()&lt;/code> للحالات الأساسية.&lt;/p>
&lt;p>للحصول على نتائج محتملة متعددة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/todos&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;Results&amp;lt;Created&amp;lt;Todo&amp;gt;, ValidationProblem, Conflict&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (Todo todo, AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(todo.Title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> TypedResults.ValidationProblem(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>[]&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { &lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">new&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;Title is required&amp;#34;&lt;/span> } }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">await&lt;/span> db.Todos.AnyAsync(t =&amp;gt; t.Title == todo.Title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> TypedResults.Conflict();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> db.Todos.Add(todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.SaveChangesAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> TypedResults.Created(&lt;span style="color:#a5d6ff">$&amp;#34;/todos/{todo.Id}&amp;#34;&lt;/span>, todo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لقد بدأت باستخدام &lt;code>TypedResults&lt;/code> في جميع المشاريع الجديدة. يلتقط المترجم عدم التطابق بين أنواع الإرجاع المعلنة وما تقوم بإرجاعه بالفعل، مما يلغي فئة كاملة من مفاجآت وقت التشغيل.&lt;/p>
&lt;h2 id="اعتبارات-الأداء">اعتبارات الأداء&lt;/h2>
&lt;p>يعد الأداء إحدى نقاط البيع لواجهات برمجة التطبيقات البسيطة (Minimal APIs)، ومن الجدير أن نفهم &lt;em>لماذا&lt;/em> تكون أسرع.&lt;/p>
&lt;p>&lt;strong>خفض تكاليف بدء التشغيل.&lt;/strong> تعتمد وحدات التحكم بشكل كبير على الانعكاس لاكتشاف نقاط النهاية وربط النماذج وتطبيق عوامل التصفية. يستخدم الحد الأدنى من واجهات برمجة التطبيقات مولدات المصدر (تبدأ في .NET 7) لإنشاء تعليمات برمجية ملزمة في وقت الترجمة. وهذا يعني عملاً أقل عند بدء التشغيل وتخصيصًا أقل للذاكرة لكل طلب.&lt;/p>
&lt;p>&lt;strong>لا يوجد خط أنابيب MVC.&lt;/strong> تمر واجهات برمجة التطبيقات المستندة إلى وحدة التحكم عبر خط أنابيب MVC الكامل: اختيار الإجراء، وربط النموذج، ومرشحات الإجراء، وتنفيذ النتائج. تتخطى واجهات برمجة التطبيقات البسيطة كل ذلك وتنتقل مباشرة من التوجيه إلى المعالج الخاص بك.&lt;/p>
&lt;p>&lt;strong>تجميع RequestDelegate.&lt;/strong> يقوم إطار العمل بتجميع تعبيرات lambda الخاصة بك في مثيلات &lt;code>RequestDelegate&lt;/code> المحسنة. الكود الناتج قريب جدًا مما كنت ستكتبه يدويًا إذا كنت تعمل مباشرة مع &lt;code>HttpContext&lt;/code>.&lt;/p>
&lt;p>فيما يلي بعض النصائح العملية لتحقيق أقصى قدر من الأداء:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Use AsNoTracking for read-only queries&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/products&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Products.AsNoTracking().ToListAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Return results directly — avoid unnecessary allocations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/count&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Products.CountAsync());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Use cancellation tokens for long-running operations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapGet(&lt;span style="color:#a5d6ff">&amp;#34;/report&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AppDbContext db, CancellationToken ct) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> db.Orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AsNoTracking()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .GroupBy(o =&amp;gt; o.Status)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .Select(g =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> { Status = g.Key, Count = g.Count() })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToListAsync(ct));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```ومن&lt;/span> &lt;span style="color:#f85149">الجدير&lt;/span> &lt;span style="color:#f85149">بالذكر&lt;/span> &lt;span style="color:#f85149">أيضًا&lt;/span> &lt;span style="color:#f85149">أن&lt;/span> &lt;span style="color:#f85149">فجوة&lt;/span> &lt;span style="color:#f85149">الأداء&lt;/span> &lt;span style="color:#f85149">بين&lt;/span> &lt;span style="color:#f85149">وحدات&lt;/span> &lt;span style="color:#f85149">التحكم&lt;/span> &lt;span style="color:#f85149">وواجهات&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">التطبيقات&lt;/span> &lt;span style="color:#f85149">البسيطة&lt;/span> &lt;span style="color:#f85149">تستمر&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">التضييق&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">كل&lt;/span> &lt;span style="color:#f85149">إصدار&lt;/span> .NET. &lt;span style="color:#f85149">بالنسبة&lt;/span> &lt;span style="color:#f85149">لمعظم&lt;/span> &lt;span style="color:#f85149">التطبيقات،&lt;/span> &lt;span style="color:#f85149">لن&lt;/span> &lt;span style="color:#f85149">يكون&lt;/span> &lt;span style="color:#f85149">الاختلاف&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">عنق&lt;/span> &lt;span style="color:#f85149">الزجاجة&lt;/span> &lt;span style="color:#f85149">لديك،&lt;/span> &lt;span style="color:#f85149">بل&lt;/span> &lt;span style="color:#f85149">سيكون&lt;/span> &lt;span style="color:#f85149">ذلك&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">خلال&lt;/span> &lt;span style="color:#f85149">استعلامات&lt;/span> &lt;span style="color:#f85149">قاعدة&lt;/span> &lt;span style="color:#f85149">البيانات&lt;/span> &lt;span style="color:#f85149">واستدعاءات&lt;/span> &lt;span style="color:#f85149">الخدمة&lt;/span> &lt;span style="color:#f85149">الخارجية&lt;/span>. &lt;span style="color:#f85149">اختر&lt;/span> &lt;span style="color:#f85149">بناءً&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">خبرة&lt;/span> &lt;span style="color:#f85149">المطور&lt;/span> &lt;span style="color:#f85149">واحتياجات&lt;/span> &lt;span style="color:#f85149">المشروع،&lt;/span> &lt;span style="color:#f85149">وليس&lt;/span> &lt;span style="color:#f85149">المعايير&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">##&lt;/span> &lt;span style="color:#f85149">الخلاصة&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">لقد&lt;/span> &lt;span style="color:#f85149">قطع&lt;/span> &lt;span style="color:#f85149">الحد&lt;/span> &lt;span style="color:#f85149">الأدنى&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">واجهات&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">التطبيقات&lt;/span> (APIs) &lt;span style="color:#f85149">شوطًا&lt;/span> &lt;span style="color:#f85149">طويلًا&lt;/span> &lt;span style="color:#f85149">منذ&lt;/span> &lt;span style="color:#f85149">تقديمها&lt;/span> &lt;span style="color:#f85149">في&lt;/span> .NET &lt;span style="color:#a5d6ff">6.&lt;/span> &lt;span style="color:#f85149">وما&lt;/span> &lt;span style="color:#f85149">بدأ&lt;/span> &lt;span style="color:#f85149">كميزة&lt;/span> &lt;span style="color:#f85149">تجريبية&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;hello World&amp;#34;&lt;/span> &lt;span style="color:#f85149">قد&lt;/span> &lt;span style="color:#f85149">تطور&lt;/span> &lt;span style="color:#f85149">ليصبح&lt;/span> &lt;span style="color:#f85149">خيارًا&lt;/span> &lt;span style="color:#f85149">مشروعًا&lt;/span> &lt;span style="color:#f85149">لواجهات&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">تطبيقات&lt;/span> &lt;span style="color:#f85149">الإنتاج&lt;/span>. &lt;span style="color:#f85149">باستخدام&lt;/span> &lt;span style="color:#f85149">عوامل&lt;/span> &lt;span style="color:#f85149">تصفية&lt;/span> &lt;span style="color:#f85149">نقطة&lt;/span> &lt;span style="color:#f85149">النهاية&lt;/span> &lt;span style="color:#f85149">ومجموعات&lt;/span> &lt;span style="color:#f85149">المسارات&lt;/span> &lt;span style="color:#f85149">والنتائج&lt;/span> &lt;span style="color:#f85149">المكتوبة&lt;/span> &lt;span style="color:#f85149">ودعم&lt;/span> OpenAPI &lt;span style="color:#f85149">القوي،&lt;/span> &lt;span style="color:#f85149">لديك&lt;/span> &lt;span style="color:#f85149">كل&lt;/span> &lt;span style="color:#f85149">ما&lt;/span> &lt;span style="color:#f85149">تحتاجه&lt;/span> &lt;span style="color:#f85149">لإنشاء&lt;/span> &lt;span style="color:#f85149">خدمات&lt;/span> &lt;span style="color:#f85149">جيدة&lt;/span> &lt;span style="color:#f85149">التنظيم&lt;/span> &lt;span style="color:#f85149">وقابلة&lt;/span> &lt;span style="color:#f85149">للصيانة&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">توصيتي؟&lt;/span> &lt;span style="color:#f85149">إذا&lt;/span> &lt;span style="color:#f85149">كنت&lt;/span> &lt;span style="color:#f85149">تبدأ&lt;/span> &lt;span style="color:#f85149">مشروعًا&lt;/span> &lt;span style="color:#f85149">جديدًا&lt;/span> &lt;span style="color:#f85149">لواجهة&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">التطبيقات&lt;/span> (API) - &lt;span style="color:#f85149">خاصةً&lt;/span> &lt;span style="color:#f85149">خدمة&lt;/span> &lt;span style="color:#f85149">صغيرة&lt;/span> &lt;span style="color:#f85149">أو&lt;/span> &lt;span style="color:#f85149">واجهة&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">تطبيقات&lt;/span> &lt;span style="color:#f85149">داخلية&lt;/span> &lt;span style="color:#f85149">مركزة&lt;/span> - &lt;span style="color:#f85149">فجرّب&lt;/span> Minimal APIs &lt;span style="color:#f85149">بجدية&lt;/span>. &lt;span style="color:#f85149">استخدم&lt;/span> &lt;span style="color:#f85149">نمط&lt;/span> &lt;span style="color:#f85149">أسلوب&lt;/span> &lt;span style="color:#f85149">الامتداد&lt;/span> &lt;span style="color:#f85149">للتنظيم،&lt;/span> &lt;span style="color:#f85149">واعتمد&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">مرشحات&lt;/span> &lt;span style="color:#f85149">نقطة&lt;/span> &lt;span style="color:#f85149">النهاية&lt;/span> &lt;span style="color:#f85149">للاهتمامات&lt;/span> &lt;span style="color:#f85149">الشاملة،&lt;/span> &lt;span style="color:#f85149">واستفد&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">`&lt;/span>TypedResults&lt;span style="color:#f85149">`&lt;/span> &lt;span style="color:#f85149">لسلامة&lt;/span> &lt;span style="color:#f85149">النوع&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">بالنسبة&lt;/span> &lt;span style="color:#f85149">للمشاريع&lt;/span> &lt;span style="color:#f85149">الحالية&lt;/span> &lt;span style="color:#f85149">القائمة&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">وحدات&lt;/span> &lt;span style="color:#f85149">التحكم،&lt;/span> &lt;span style="color:#f85149">ليس&lt;/span> &lt;span style="color:#f85149">هناك&lt;/span> &lt;span style="color:#f85149">عجلة&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">أمرها&lt;/span> &lt;span style="color:#f85149">للترحيل&lt;/span>. &lt;span style="color:#f85149">كلا&lt;/span> &lt;span style="color:#f85149">الطريقتين&lt;/span> &lt;span style="color:#f85149">تعملان&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">جيد،&lt;/span> &lt;span style="color:#f85149">ويمكنك&lt;/span> &lt;span style="color:#f85149">حتى&lt;/span> &lt;span style="color:#f85149">استخدامهما&lt;/span> &lt;span style="color:#f85149">جنبًا&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">جنب&lt;/span>. &lt;span style="color:#f85149">ولكن&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">المرة&lt;/span> &lt;span style="color:#f85149">القادمة&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تحتاج&lt;/span> &lt;span style="color:#f85149">فيها&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">إضافة&lt;/span> &lt;span style="color:#f85149">خدمة&lt;/span> &lt;span style="color:#f85149">صغيرة&lt;/span> &lt;span style="color:#f85149">أو&lt;/span> &lt;span style="color:#f85149">واجهة&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">تطبيقات&lt;/span> &lt;span style="color:#f85149">داخلية&lt;/span> &lt;span style="color:#f85149">سريعة،&lt;/span> &lt;span style="color:#f85149">تخطي&lt;/span> &lt;span style="color:#f85149">وحدات&lt;/span> &lt;span style="color:#f85149">التحكم&lt;/span> &lt;span style="color:#f85149">واتجه&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">الحد&lt;/span> &lt;span style="color:#f85149">الأدنى&lt;/span>. &lt;span style="color:#f85149">قد&lt;/span> &lt;span style="color:#f85149">لا&lt;/span> &lt;span style="color:#f85149">تعود&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">ترميز&lt;/span> &lt;span style="color:#f85149">سعيد&lt;/span>!
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content:encoded><category>.NET</category><category>API</category><category>Web Development</category></item><item><title>استخدام CSS المعزول في مكونات Blazor</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-isolated-css/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-isolated-css/</guid><description>قم بنطاق أنماط CSS لمكونات Blazor الفردية باستخدام عزل CSS لتجنب تعارض الأنماط العالمية.</description><content:encoded>&lt;p>الشيء الوحيد الذي كان يزعجني دائمًا عند العمل مع Blazor هو مدى سهولة كسر الأنماط عبر المكونات عن طريق الخطأ. يمكنك إضافة فصل دراسي في مكون واحد وفجأة يبدو شيء آخر في صفحة مختلفة تمامًا. اتضح أن Blazor لديه حل مدمج لهذا: عزل CSS.&lt;/p>
&lt;h1 id="ما-هو-عزل-css">ما هو عزل CSS؟&lt;/h1>
&lt;p>يتيح لك عزل CSS نطاق الأنماط لمكون معين. يقوم Blazor بذلك عن طريق إنشاء سمة فريدة لكل مكون في وقت الإنشاء وإلحاقها بمحددات CSS. لذلك تنطبق أنماطك فقط على المكون الذي تنتمي إليه.&lt;/p>
&lt;p>لا حاجة لتسمية BEM، ولا حاجة لاختراقات خصوصية مجنونة. فقط CSS نظيف ومحدد النطاق.&lt;/p>
&lt;p>#طريقة استخدامه&lt;/p>
&lt;p>لنفترض أن لديك مكونًا يسمى &lt;code>Card.razor&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span> class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#7ee787">h3&lt;/span> class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-title&amp;#34;&lt;/span>&amp;gt;@Title&amp;lt;/&lt;span style="color:#7ee787">h3&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#7ee787">p&lt;/span> class&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&amp;gt;@ChildContent&amp;lt;/&lt;span style="color:#7ee787">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public string Title { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public RenderFragment ChildContent { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لإضافة أنماط معزولة، ما عليك سوى إنشاء ملف بنفس الاسم ولكن بامتداد &lt;code>.razor.css&lt;/code>. في هذه الحالة: &lt;code>Card.razor.css&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">border&lt;/span>: &lt;span style="color:#a5d6ff">1&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span> &lt;span style="color:#79c0ff">solid&lt;/span> &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>border);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">border-radius&lt;/span>: &lt;span style="color:#a5d6ff">8&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">padding&lt;/span>: &lt;span style="color:#a5d6ff">1&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">background&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>bg&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>secondary);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card-title&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">font-size&lt;/span>: &lt;span style="color:#a5d6ff">1.1&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">font-weight&lt;/span>: &lt;span style="color:#a5d6ff">600&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">margin-bottom&lt;/span>: &lt;span style="color:#a5d6ff">0.5&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card-body&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">color&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#79c0ff">text&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>secondary);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">font-size&lt;/span>: &lt;span style="color:#a5d6ff">0.9&lt;/span>&lt;span style="color:#ff7b72">rem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وهذا كل شيء! سوف يلتقطه Blazor تلقائيًا ويوسع نطاق هذه الأنماط إلى مكون &lt;code>Card&lt;/code> فقط. إذا كان لديك فصل &lt;code>.card&lt;/code> آخر في مكان آخر، فلن يتأثر.&lt;/p>
&lt;p>#كيف يعمل تحت غطاء محرك السيارة&lt;/p>
&lt;p>عندما تقوم بإنشاء مشروعك، يقوم Blazor بإعادة كتابة HTML وCSS. يحصل المكون الخاص بك على سمة فريدة مثل &lt;code>b-3x8qz7k2f1&lt;/code>، ويتم إلحاق محددات CSS بهذه السمة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">card&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#7ee787">b-3x8qz7k2f1&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">border&lt;/span>: &lt;span style="color:#a5d6ff">1&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span> &lt;span style="color:#79c0ff">solid&lt;/span> &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>border);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">/* ... */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يتم تقديم حزمة CSS التي تم إنشاؤها كـ &lt;code>{ProjectName}.styles.css&lt;/code>. تأكد من وجود هذا في &lt;code>index.html&lt;/code> أو &lt;code>_Host.cshtml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">link&lt;/span> href&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;YourApp.styles.css&amp;#34;&lt;/span> rel&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;stylesheet&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إذا كنت تفتقد هذا الرابط، فلن تظهر أنماطك المعزولة وستقضي 30 دقيقة تتساءل عما يحدث. ثق بي، لقد كنت هناك.&lt;/p>
&lt;h1 id="استهداف-العناصر-الفرعية">استهداف العناصر الفرعية&lt;/h1>
&lt;p>شيء واحد يجب أخذه في الاعتبار هو أنه افتراضيًا، ينطبق CSS المعزول فقط على عناصر المكون الحالي. إذا كنت تريد تصميم العناصر داخل مكون فرعي، فأنت بحاجة إلى أداة الدمج &lt;code>::deep&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>::&lt;span style="color:#d2a8ff;font-weight:bold">deep&lt;/span> .&lt;span style="color:#f0883e;font-weight:bold">child-element&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">color&lt;/span>: &lt;span style="color:#79c0ff">red&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا يخبر Blazor بتطبيق النمط على العناصر المطابقة داخل المكونات الفرعية أيضًا. مفيد جدًا عندما يكون لديك مكون مجمع يحتاج إلى تصميم أبنائه.&lt;/p>
&lt;p>#متى يتم استخدامه&lt;/p>
&lt;p>أستخدم عزل CSS لكل مكون تقريبًا الآن. إنها تحافظ على الأشياء نظيفة ويمكن التنبؤ بها. المرة الوحيدة التي لا أستخدمها فيها هي للأنماط العالمية مثل عمليات إعادة التعيين أو الطباعة أو متغيرات السمات - تلك التي تدخل في ملف CSS مشترك.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! لا تتردد في الاتصال بي على أي وسائل التواصل الاجتماعي على **@emimontesdeoca **.&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation">عزل ASP.NET Core Blazor CSS&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>CSS</category></item><item><title>التحكم في إنفاق عيد الميلاد باستخدام Semantic Kernel</title><link>https://emimontesdeoca.github.io/ar/posts/keeping-christmas-spending-with-semantic-kernel/</link><pubDate>Sat, 28 Dec 2024 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/keeping-christmas-spending-with-semantic-kernel/</guid><description>قم بتحليل الإيصالات وتتبع إنفاق عيد الميلاد باستخدام Semantic Kernel وAzure OpenAI وBlazor.</description><content:encoded>&lt;p>﻿## مقدمة&lt;/p>
&lt;p>مع اقتراب موسم العطلات، يمكن أن تصبح إدارة النفقات تحديًا، خاصة مع موجة التسوق وشراء الهدايا. في منشور المدونة هذا، سنستكشف كيفية الاستفادة من الذكاء الاصطناعي للمساعدة في تتبع إنفاقك في عيد الميلاد باستخدام تقنيات .NET. من خلال تحليل الإيصالات بقوة Semantic Kernel والذكاء الاصطناعي، يمكننا استخراج التفاصيل الأساسية بكفاءة مثل أسماء المتاجر والتواريخ وقوائم العناصر والمبالغ الإجمالية. يتيح لك هذا الحل مراقبة إنفاقك في عيد الميلاد وإدارته بسهولة، مما يضمن لك البقاء على اطلاع بميزانيتك دون الحاجة إلى مراجعة الإيصالات يدويًا.&lt;/p>
&lt;h2 id="calendario-de-adviento-de-inteligencia-artificial-2024-en-español">Calendario de Adviento de Inteligencia Artificial 2024 en Español&lt;/h2>
&lt;p align="center">
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdkovc8lzahgm8pvsblk.png"/>
&lt;/ص>
&lt;p>استلهم هذا المشروع من مشاركتي في &lt;strong>Calendario de Adviento de Inteligencia Artificial 2024 en Español&lt;/strong>، وهو حدث عبر الإنترنت مخصص للذكاء الاصطناعي. يمكنك العثور على المزيد حول الحدث على &lt;a href="https://dev.to/roberto_navarro_mate/calendario-de-adviento-de-inteligencia-artificial-2024-en-espanol-bdb">رابط Dev.to هذا&lt;/a>.&lt;/p>
&lt;p>##المشروع&lt;/p>
&lt;p>في هذا المشروع، سنستخدم &lt;strong>Azure OpenAI&lt;/strong>، وهي خدمة تتيح لنا استخدام نماذج الذكاء الاصطناعي القوية مثل GPT-4 لمعالجة الصور وتحليلها. تتضمن العملية عدة خطوات، بدءًا من إعداد خدمة واجهة برمجة التطبيقات الخلفية وحتى التكامل مع الواجهة الأمامية لـ Blazor لتحميل الصور. سنستخدم أيضًا &lt;strong>.NET Aspire&lt;/strong>، وهو مكون يساعد على ربط كل شيء بسلاسة.&lt;/p>
&lt;h2 id="المتطلبات-الأساسية">المتطلبات الأساسية&lt;/h2>
&lt;p>قبل أن نتعمق في الكود، تأكد من أن لديك المتطلبات الأساسية التالية:&lt;/p>
&lt;p>-.نت 9&lt;/p>
&lt;ul>
&lt;li>الوصول إلى Azure OpenAI (مفتاح API)&lt;/li>
&lt;li>Visual Studio أو Visual Studio Code&lt;/li>
&lt;li>المعرفة الأساسية بـ Blazor وعملاء HTTP وتطوير واجهة برمجة التطبيقات&lt;/li>
&lt;/ul>
&lt;h2 id="حل-visual-studio">حل Visual Studio&lt;/h2>
&lt;p>سينتهي بنا الأمر بالحصول على شيء كهذا، أحب أن أبقي الأشياء منفصلة وبأسماء رائعة، فإليك كيف يبدو الأمر:&lt;/p>
&lt;p align="center">
&lt;img src="https://imgur.com/gAnGLhM.png">
&lt;/ص>
&lt;p>ولكن دعونا نمضي قدمًا في إنشاء الأشياء خطوة بخطوة!&lt;/p>
&lt;h2 id="الخطوة-0-النماذج">الخطوة 0: النماذج&lt;/h2>
&lt;p>يعتمد جوهر تطبيق Receipt Scanner على العديد من النماذج الرئيسية التي تسهل التفاعل بين خدمات الواجهة الأمامية وواجهة برمجة التطبيقات (API) وخدمات الذكاء الاصطناعي (AI). وفيما يلي النماذج الرئيسية المستخدمة في هذا المشروع:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>طلب تحليل الإيصال&lt;/strong>&lt;br>
يمثل هذا النموذج بنية الطلب لتحليل الإيصال. يحتوي على خاصية &lt;code>ImageBytes&lt;/code>، التي تحتوي على مصفوفة البايت لصورة الإيصال التي ستتم معالجتها.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AnalyzeReceiptRequest&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">byte&lt;/span>[] ImageBytes { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>نتيجة تحليل الإيصال&lt;/strong>&lt;br>
يلتقط هذا النموذج النتيجة بعد معالجة الإيصال. ويحتفظ بالبيانات المنظمة المستخرجة من الإيصال، مثل اسم المتجر والتاريخ والأصناف والمبلغ الإجمالي.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ReceiptAnalyzeResult&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime CreatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ReceiptData Result { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>بيانات الاستلام&lt;/strong>&lt;br>
هذا هو النموذج الذي يحمل بيانات الاستلام المنظمة. ويتضمن خصائص اسم المتجر، والتاريخ، وقائمة العناصر (مع اسم كل عنصر وسعره)، والمبلغ الإجمالي في الإيصال.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ReceiptData&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Store { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime? Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;ReceiptItem&amp;gt; Items { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal?&lt;/span> Total { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>عنصر الإيصال&lt;/strong>&lt;br>
يتم تمثيل كل عنصر في الإيصال بواسطة هذا النموذج. يحمل اسم السلعة وسعرها.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ReceiptItem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal?&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```تعمل&lt;/span> &lt;span style="color:#f85149">هذه&lt;/span> &lt;span style="color:#f85149">النماذج&lt;/span> &lt;span style="color:#f85149">كأساس&lt;/span> &lt;span style="color:#f85149">لتمرير&lt;/span> &lt;span style="color:#f85149">البيانات&lt;/span> &lt;span style="color:#f85149">بين&lt;/span> &lt;span style="color:#f85149">العميل&lt;/span> &lt;span style="color:#f85149">والخادم،&lt;/span> &lt;span style="color:#f85149">مما&lt;/span> &lt;span style="color:#f85149">يضمن&lt;/span> &lt;span style="color:#f85149">التدفق&lt;/span> &lt;span style="color:#f85149">السلس&lt;/span> &lt;span style="color:#f85149">للمعلومات&lt;/span>. &lt;span style="color:#f85149">تتلقى&lt;/span> &lt;span style="color:#f85149">واجهة&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">التطبيقات&lt;/span> (API) &lt;span style="color:#f85149">صورة&lt;/span> &lt;span style="color:#f85149">الإيصال،&lt;/span> &lt;span style="color:#f85149">وفي&lt;/span> &lt;span style="color:#f85149">المقابل،&lt;/span> &lt;span style="color:#f85149">تقوم&lt;/span> &lt;span style="color:#f85149">بمعالجة&lt;/span> &lt;span style="color:#f85149">وإرجاع&lt;/span> &lt;span style="color:#f85149">كائن&lt;/span> JSON &lt;span style="color:#f85149">منظم&lt;/span> &lt;span style="color:#f85149">يمكن&lt;/span> &lt;span style="color:#f85149">استهلاكه&lt;/span> &lt;span style="color:#f85149">بسهولة&lt;/span> &lt;span style="color:#f85149">بواسطة&lt;/span> &lt;span style="color:#f85149">الواجهة&lt;/span> &lt;span style="color:#f85149">الأمامية&lt;/span>.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h2 id="الخطوة-1-إعداد-خدمة-backend-api">الخطوة 1: إعداد خدمة Backend API&lt;/h2>
&lt;p>الخطوة الأولى في بناء هذا التطبيق هي إعداد خدمة API لتحليل صور الإيصالات. سنستخدم &lt;strong>Azure OpenAI&lt;/strong> API لاستخراج المعلومات من صور الإيصال. فيما يلي تفصيل لكيفية تناسب كل شيء معًا:&lt;/p>
&lt;h3 id="خدمة-الذكاء-الاصطناعي---نظرة-عميقة">خدمة الذكاء الاصطناعي - نظرة عميقة&lt;/h3>
&lt;p>تقع خدمة الذكاء الاصطناعي في قلب نظام تحليل الإيصالات الخاص بنا. إنها مسؤولة عن التواصل مع واجهة برمجة تطبيقات Azure OpenAI لمعالجة بيانات الصورة وإرجاع رؤى ذات معنى. فئة &lt;strong>AiApiClient&lt;/strong> هي العميل الذي سيتعامل مع كافة التفاعلات مع Azure OpenAI API.&lt;/p>
&lt;h4 id="تنفيذ-عميل-الذكاء-الاصطناعي">تنفيذ عميل الذكاء الاصطناعي&lt;/h4>
&lt;p>&lt;code>AiApiClient&lt;/code> هو المكون الرئيسي المسؤول عن إرسال صورة الإيصال (بتنسيق صفيف البايت) إلى Azure OpenAI API. يتعامل مع الاتصالات وتسجيل الأخطاء وتحليل البيانات:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">AiApiClient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILogger&amp;lt;AiApiClient&amp;gt; _logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> AiApiClient(HttpClient httpClient, ILogger&amp;lt;AiApiClient&amp;gt; logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger = logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ReceiptAnalyzeResult?&amp;gt; AnalyzeAsync(&lt;span style="color:#ff7b72">byte&lt;/span>[] imageBytes, CancellationToken cancellationToken = &lt;span style="color:#ff7b72">default&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (imageBytes == &lt;span style="color:#79c0ff">null&lt;/span> || imageBytes.Length == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;ImageBytes is null or empty.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Sending analyze request with image bytes of length: {Length}&amp;#34;&lt;/span>, imageBytes.Length);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> request = &lt;span style="color:#ff7b72">new&lt;/span> AnalyzeReceiptRequest
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ImageBytes = imageBytes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.PostAsJsonAsync(&lt;span style="color:#a5d6ff">&amp;#34;/analyze-receipt&amp;#34;&lt;/span>, request, cancellationToken);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!response.IsSuccessStatusCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;Failed to analyze receipt. StatusCode: {StatusCode}&amp;#34;&lt;/span>, response.StatusCode);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> analyzeResult = &lt;span style="color:#ff7b72">await&lt;/span> response.Content.ReadFromJsonAsync&amp;lt;ReceiptAnalyzeResult&amp;gt;(cancellationToken: cancellationToken);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (analyzeResult == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;No content received from AI API service.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Analysis result received: {AnalyzeResult}&amp;#34;&lt;/span>, analyzeResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> analyzeResult;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger.LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;An error occurred while analyzing the receipt.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في هذا الجزء من الكود، قمنا بتعريف الطريقة &lt;code>AnalyzeAsync&lt;/code>، وهي المسؤولة عن:&lt;/p>
&lt;ol>
&lt;li>إرسال مصفوفة بايت الصورة إلى Azure OpenAI API.&lt;/li>
&lt;li>التعامل مع أي أخطاء أو استجابات غير ناجحة من API.&lt;/li>
&lt;li>تحليل بيانات JSON التي تم إرجاعها إلى نتيجة منظمة (&lt;code>ReceiptAnalyzeResult&lt;/code>).&lt;/li>
&lt;/ol>
&lt;p>تتضمن فوائد فصل هذه الوظيفة إلى خدمة مخصصة (AiApiClient) ما يلي:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>معالجة الأخطاء:&lt;/strong> معالجة مركزية للأخطاء مثل مشكلات الشبكة أو الاستجابات غير الصالحة.&lt;/li>
&lt;li>&lt;strong>التسجيل:&lt;/strong> التسجيل الصحيح للطلبات والاستجابات لمراقبة سلوك النظام.&lt;/li>
&lt;/ul>
&lt;p align="center">
&lt;img src="https://imgur.com/q3EpCSy.png"/>
&lt;/ص>
&lt;h3 id="خدمة-api---التعامل-مع-الطلبات-والردود">خدمة API - التعامل مع الطلبات والردود&lt;/h3>
&lt;p>تعمل &lt;strong>خدمة API&lt;/strong> كوسيط بين تطبيق Blazor للواجهة الأمامية وخدمة الذكاء الاصطناعي. هذه الخدمة مسؤولة عن قبول بيانات الصورة وتمريرها إلى خدمة الذكاء الاصطناعي وإرجاع نتائج التحليل إلى العميل.&lt;/p>
&lt;h4 id="نقطة-نهاية-واجهة-برمجة-التطبيقات">نقطة نهاية واجهة برمجة التطبيقات&lt;/h4>
&lt;p>في هذه الخطوة، نحدد نقطة نهاية بسيطة لواجهة برمجة التطبيقات (API) لقبول صور الإيصالات، وإعادة توجيهها إلى خدمة الذكاء الاصطناعي للمعالجة، وإرجاع النتائج إلى العميل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">ReceiptScanner.Shared.Clients&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">ReceiptScanner.Shared.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add service defaults &amp;amp; Aspire client integrations.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.AddServiceDefaults();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddProblemDetails();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Register AiApiClient with HttpClient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddHttpClient&amp;lt;AiApiClient&amp;gt;(client =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.BaseAddress = &lt;span style="color:#ff7b72">new&lt;/span> Uri(&lt;span style="color:#a5d6ff">&amp;#34;https+http://aiservice&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseExceptionHandler();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.MapOpenApi();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// POST endpoint to analyze receipt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapPost(&lt;span style="color:#a5d6ff">&amp;#34;/api/analyze-receipt&amp;#34;&lt;/span>, &lt;span style="color:#ff7b72">async&lt;/span> (AnalyzeReceiptRequest request, AiApiClient aiApiClient, ILogger&amp;lt;Program&amp;gt; logger) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (request.ImageBytes == &lt;span style="color:#79c0ff">null&lt;/span> || request.ImageBytes.Length == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;ImageBytes is null or empty.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.BadRequest(&lt;span style="color:#a5d6ff">&amp;#34;ImageBytes is required.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Received analyze receipt request with image bytes of length: {Length}&amp;#34;&lt;/span>, request.ImageBytes.Length);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = &lt;span style="color:#ff7b72">await&lt;/span> aiApiClient.AnalyzeAsync(request.ImageBytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (result == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogWarning(&lt;span style="color:#a5d6ff">&amp;#34;Failed to analyze the receipt.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Problem(&lt;span style="color:#a5d6ff">&amp;#34;Unable to process the receipt at this time. Please try again later.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Analysis completed successfully. Result: {Result}&amp;#34;&lt;/span>, result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Ok(result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;An error occurred while processing the receipt.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Results.Problem(&lt;span style="color:#a5d6ff">&amp;#34;An error occurred while processing the receipt. Please try again later.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapDefaultEndpoints();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نقطة النهاية هذه:&lt;/p>
&lt;ol>
&lt;li>قبول صورة الإيصال كجزء من نص الطلب.&lt;/li>
&lt;li>يستدعي أسلوب النقطة الداخلية &lt;code>AiService&lt;/code> لإرسال الصورة إلى Azure OpenAI للمعالجة.&lt;/li>
&lt;li>إرجاع نتيجة التحليل إلى العميل.&lt;/li>
&lt;/ol>
&lt;p align="center">
&lt;img src="https://imgur.com/u9mQrpq.png"/>
&lt;/ص>
&lt;h2 id="الخطوة-الثانية-إعداد-واجهة-blazor-الأمامية">الخطوة الثانية: إعداد واجهة Blazor الأمامية&lt;/h2>
&lt;p>الآن بعد أن انتهينا من إعداد الواجهة الخلفية، فلنوجه انتباهنا إلى &lt;strong>واجهة Blazor الأمامية&lt;/strong>. هذا هو المكان الذي يمكن للمستخدمين تحميل صور الإيصالات الخاصة بهم لتحليلها ورؤية النتائج.&lt;/p>
&lt;h3 id="تنفيذ-صفحة-blazor">تنفيذ صفحة Blazor&lt;/h3>
&lt;p>توفر صفحة Blazor واجهة بسيطة حيث يمكن للمستخدمين تحميل صور إيصالات متعددة ثم رؤية نتائج التحليل معروضة في جدول. وهذا هو الكود الخاص بالصفحة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/analyzer&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using ReceiptScanner&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Shared&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clients
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using ReceiptScanner&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Shared&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using System&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Globalization
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ApiServiceClient ApiClient
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ILogger&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>Program&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> Logger
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>attribute [StreamRendering]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>rendermode InteractiveServer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>PageTitle&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Receipt Analyzer&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>PageTitle&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h1 &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;text-center my-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Receipt Analyzer&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h1&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;container&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;lead text-center mb-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Upload receipt images below to extract their data&lt;span style="color:#ff7b72;font-weight:bold">.&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">File&lt;/span> Upload Section &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card mb-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>InputFile OnChange&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;HandleFileSelected&amp;#34;&lt;/span> multiple &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;form-control mb-3&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary w-100&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;ProcessReceipts&amp;#34;&lt;/span> disabled&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@(!hasFiles)&amp;#34;&lt;/span> type&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;button&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@(!processing ? &amp;#34;&lt;/span>d&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>none&lt;span style="color:#a5d6ff">&amp;#34; : &amp;#34;&amp;#34;) spinner-border spinner-border-sm&amp;#34;&lt;/span> role&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;status&amp;#34;&lt;/span> aria&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>hidden&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (processing)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Processing&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Process Receipts&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>span&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> Uploaded Images Preview &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card mb-4&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-header&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h5 &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;mb-0&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Uploaded Receipt Images&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h5&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;row&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> fileBytes &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> fileBytesList)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;col-12 col-md-4 mb-3&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>img src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@($&amp;#34;&lt;/span>data:image&lt;span style="color:#ff7b72;font-weight:bold">/&lt;/span>jpeg;base64,{Convert&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToBase64String(fileBytes)}&lt;span style="color:#a5d6ff">&amp;#34;)&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;img-fluid rounded&amp;#34;&lt;/span> alt&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Uploaded receipt&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> Processing Indicator &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (processing)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert alert-info text-center&amp;#34;&lt;/span> role&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Processing receipts&lt;span style="color:#ff7b72;font-weight:bold">...&amp;lt;/&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> Please wait &lt;span style="color:#ff7b72">while&lt;/span> we analyze the uploaded files&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;!--&lt;/span> Analysis Results Section &lt;span style="color:#ff7b72;font-weight:bold">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (analyzedReceipts &lt;span style="color:#ff7b72;font-weight:bold">!=&lt;/span> null &lt;span style="color:#ff7b72;font-weight:bold">&amp;amp;&amp;amp;&lt;/span> analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-header&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h5 &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;mb-0&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Analysis Results&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h5&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;card-body&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table table-striped table-bordered&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Store&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Total&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Items&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> receipt &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> analyzedReceipts)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>(receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Store &lt;span style="color:#f85149">??&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unknown&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>(receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString() &lt;span style="color:#f85149">??&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unknown&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>(receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Total&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>, ci) &lt;span style="color:#f85149">??&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Unknown&amp;#34;&lt;/span>)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ul &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;list-unstyled&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Items is &lt;span style="color:#ff7b72;font-weight:bold">not&lt;/span> null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> receipt&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Result&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Items&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&amp;lt;&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>item&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Name&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span> &lt;span style="color:#f85149">@&lt;/span>item&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#f85149">?&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToString(&lt;span style="color:#a5d6ff">&amp;#34;C&amp;#34;&lt;/span>, ci)&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>li&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>ul&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (processed &lt;span style="color:#ff7b72;font-weight:bold">&amp;amp;&amp;amp;&lt;/span> (analyzedReceipts &lt;span style="color:#ff7b72;font-weight:bold">==&lt;/span> null &lt;span style="color:#ff7b72;font-weight:bold">||&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>div &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert alert-warning text-center&amp;#34;&lt;/span> role&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>No results found&lt;span style="color:#ff7b72;font-weight:bold">.&amp;lt;/&lt;/span>strong&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> Please try again with different images &lt;span style="color:#ff7b72;font-weight:bold">or&lt;/span> ensure they are clear &lt;span style="color:#ff7b72;font-weight:bold">and&lt;/span> legible&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>div&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> hasFiles;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> processing;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">bool&lt;/span> processed;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>byte[]&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> fileBytesList &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ReceiptAnalyzeResult&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> analyzedReceipts &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CultureInfo ci &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new CultureInfo(&lt;span style="color:#a5d6ff">&amp;#34;es-es&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task HandleFileSelected(InputFileChangeEventArgs e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> try
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> foreach (&lt;span style="color:#ff7b72">var&lt;/span> file &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> e&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetMultipleFiles())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> memoryStream &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new MemoryStream();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> await file&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>OpenReadStream()&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>CopyToAsync(memoryStream);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(memoryStream&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>ToArray());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hasFiles &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> fileBytesList&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> catch (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Logger&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;Error while handling file upload.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task ProcessReceipts()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>hasFiles)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> processing &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> true;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> try
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> foreach (&lt;span style="color:#ff7b72">var&lt;/span> fileBytes &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> fileBytesList)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ApiClient&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>AnalyzeReceiptAsync(fileBytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (result &lt;span style="color:#ff7b72;font-weight:bold">!=&lt;/span> null)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analyzedReceipts&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> catch (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Logger&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>LogError(ex, &lt;span style="color:#a5d6ff">&amp;#34;Error while processing receipts.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> finally
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> processing &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> false;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> processed &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> true;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```تسمح&lt;/span> &lt;span style="color:#f85149">هذه&lt;/span> &lt;span style="color:#f85149">الصفحة&lt;/span> &lt;span style="color:#f85149">للمستخدمين&lt;/span> &lt;span style="color:#f85149">بتحميل&lt;/span> &lt;span style="color:#f85149">الإيصالات&lt;/span> &lt;span style="color:#f85149">وإظهار&lt;/span> &lt;span style="color:#f85149">نتائج&lt;/span> &lt;span style="color:#f85149">التحليل&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">جدول&lt;/span> &lt;span style="color:#f85149">يحتوي&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">أسماء&lt;/span> &lt;span style="color:#f85149">المتاجر&lt;/span> &lt;span style="color:#f85149">والتواريخ&lt;/span> &lt;span style="color:#f85149">والمبالغ&lt;/span> &lt;span style="color:#f85149">الإجمالية&lt;/span> &lt;span style="color:#f85149">وقائمة&lt;/span> &lt;span style="color:#f85149">العناصر&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p align&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;center&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>img src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;https://imgur.com/BLswKhm.png&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#f85149">ص&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">## الخطوة 3: .NET Aspire&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p align&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;center&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>img src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;https://imgur.com/ja56RWN.png&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>&lt;span style="color:#f85149">ص&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">### ما هو .NET Aspire؟&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire &lt;span style="color:#f85149">عبارة&lt;/span> &lt;span style="color:#f85149">عن&lt;/span> &lt;span style="color:#f85149">مجموعة&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">الأدوات&lt;/span> &lt;span style="color:#f85149">والقوالب&lt;/span> &lt;span style="color:#f85149">والحزم&lt;/span> &lt;span style="color:#f85149">القوية&lt;/span> &lt;span style="color:#f85149">لإنشاء&lt;/span> &lt;span style="color:#f85149">تطبيقات&lt;/span> &lt;span style="color:#f85149">يمكن&lt;/span> &lt;span style="color:#f85149">ملاحظتها&lt;/span> &lt;span style="color:#f85149">وجاهزة&lt;/span> &lt;span style="color:#f85149">للإنتاج&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> &lt;span style="color:#f85149">يتم&lt;/span> &lt;span style="color:#f85149">تسليم&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">خلال&lt;/span> &lt;span style="color:#f85149">مجموعة&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">حزم&lt;/span> NuGet &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تتعامل&lt;/span> &lt;span style="color:#f85149">مع&lt;/span> &lt;span style="color:#f85149">اهتمامات&lt;/span> &lt;span style="color:#f85149">محددة&lt;/span> &lt;span style="color:#f85149">تتعلق&lt;/span> &lt;span style="color:#f85149">بالسحابة&lt;/span> &lt;span style="color:#f85149">الأصلية&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> &lt;span style="color:#f85149">غالبًا&lt;/span> &lt;span style="color:#f85149">ما&lt;/span> &lt;span style="color:#f85149">تتكون&lt;/span> &lt;span style="color:#f85149">التطبيقات&lt;/span> &lt;span style="color:#f85149">السحابية&lt;/span> &lt;span style="color:#f85149">الأصلية&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">أجزاء&lt;/span> &lt;span style="color:#f85149">صغيرة&lt;/span> &lt;span style="color:#f85149">مترابطة&lt;/span> &lt;span style="color:#f85149">أو&lt;/span> &lt;span style="color:#f85149">خدمات&lt;/span> &lt;span style="color:#f85149">صغيرة&lt;/span> &lt;span style="color:#f85149">بدلاً&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">قاعدة&lt;/span> &lt;span style="color:#f85149">تعليمات&lt;/span> &lt;span style="color:#f85149">برمجية&lt;/span> &lt;span style="color:#f85149">واحدة&lt;/span> &lt;span style="color:#f85149">متجانسة&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> &lt;span style="color:#f85149">تستهلك&lt;/span> &lt;span style="color:#f85149">التطبيقات&lt;/span> &lt;span style="color:#f85149">السحابية&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">عام&lt;/span> &lt;span style="color:#f85149">عددًا&lt;/span> &lt;span style="color:#f85149">كبيرًا&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">الخدمات،&lt;/span> &lt;span style="color:#f85149">مثل&lt;/span> &lt;span style="color:#f85149">قواعد&lt;/span> &lt;span style="color:#f85149">البيانات&lt;/span> &lt;span style="color:#f85149">والمراسلة&lt;/span> &lt;span style="color:#f85149">والتخزين&lt;/span> &lt;span style="color:#f85149">المؤقت&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> &lt;span style="color:#f85149">للحصول&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">معلومات&lt;/span> &lt;span style="color:#f85149">حول&lt;/span> &lt;span style="color:#f85149">الدعم،&lt;/span> &lt;span style="color:#f85149">راجع&lt;/span> &lt;span style="color:#f85149">سياسة&lt;/span> &lt;span style="color:#f85149">دعم&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">التطبيق&lt;/span> &lt;span style="color:#f85149">الموزع&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">الذي&lt;/span> &lt;span style="color:#f85149">يستخدم&lt;/span> &lt;span style="color:#f85149">الموارد&lt;/span> &lt;span style="color:#f85149">الحسابية&lt;/span> &lt;span style="color:#f85149">عبر&lt;/span> &lt;span style="color:#f85149">عقد&lt;/span> &lt;span style="color:#f85149">متعددة،&lt;/span> &lt;span style="color:#f85149">مثل&lt;/span> &lt;span style="color:#f85149">الحاويات&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تعمل&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">مضيفين&lt;/span> &lt;span style="color:#f85149">مختلفين&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> &lt;span style="color:#f85149">يجب&lt;/span> &lt;span style="color:#f85149">أن&lt;/span> &lt;span style="color:#f85149">تتواصل&lt;/span> &lt;span style="color:#f85149">هذه&lt;/span> &lt;span style="color:#f85149">العقد&lt;/span> &lt;span style="color:#f85149">عبر&lt;/span> &lt;span style="color:#f85149">حدود&lt;/span> &lt;span style="color:#f85149">الشبكة&lt;/span> &lt;span style="color:#f85149">لتقديم&lt;/span> &lt;span style="color:#f85149">الاستجابات&lt;/span> &lt;span style="color:#f85149">للمستخدمين&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> &lt;span style="color:#f85149">التطبيق&lt;/span> &lt;span style="color:#f85149">السحابي&lt;/span> &lt;span style="color:#f85149">الأصلي&lt;/span> &lt;span style="color:#f85149">هو&lt;/span> &lt;span style="color:#f85149">نوع&lt;/span> &lt;span style="color:#f85149">محدد&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">التطبيقات&lt;/span> &lt;span style="color:#f85149">الموزعة&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تستفيد&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">كامل&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">قابلية&lt;/span> &lt;span style="color:#f85149">التوسع&lt;/span> &lt;span style="color:#f85149">والمرونة&lt;/span> &lt;span style="color:#f85149">وسهولة&lt;/span> &lt;span style="color:#f85149">الإدارة&lt;/span> &lt;span style="color:#f85149">للبنى&lt;/span> &lt;span style="color:#f85149">التحتية&lt;/span> &lt;span style="color:#f85149">السحابية&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">يوفر&lt;/span> &lt;span style="color:#f85149">استخدام&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">**.&lt;/span>NET Aspire&lt;span style="color:#ff7b72;font-weight:bold">**&lt;/span> &lt;span style="color:#f85149">لهذا&lt;/span> &lt;span style="color:#f85149">المشروع&lt;/span> &lt;span style="color:#f85149">العديد&lt;/span> &lt;span style="color:#f85149">من&lt;/span> &lt;span style="color:#f85149">الفوائد&lt;/span> &lt;span style="color:#f85149">التي&lt;/span> &lt;span style="color:#f85149">تعمل&lt;/span> &lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">تحسين&lt;/span> &lt;span style="color:#f85149">جودة&lt;/span> &lt;span style="color:#f85149">النظام&lt;/span> &lt;span style="color:#f85149">بشكل&lt;/span> &lt;span style="color:#f85149">عام،&lt;/span> &lt;span style="color:#f85149">مثل&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">### 1. **التسجيل المركزي**&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">يقوم&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>NET Aspire &lt;span style="color:#f85149">بدمج&lt;/span> &lt;span style="color:#f85149">التسجيل&lt;/span> &lt;span style="color:#f85149">تلقائيًا&lt;/span> &lt;span style="color:#f85149">عبر&lt;/span> &lt;span style="color:#f85149">التطبيق&lt;/span> &lt;span style="color:#f85149">بأكمله،&lt;/span> &lt;span style="color:#f85149">مما&lt;/span> &lt;span style="color:#f85149">يعني&lt;/span> &lt;span style="color:#f85149">أنك&lt;/span> &lt;span style="color:#f85149">لن&lt;/span> &lt;span style="color:#f85149">تضطر&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">تكوين&lt;/span> &lt;span style="color:#f85149">التسجيل&lt;/span> &lt;span style="color:#f85149">يدويًا&lt;/span> &lt;span style="color:#f85149">لكل&lt;/span> &lt;span style="color:#f85149">خدمة&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span> &lt;span style="color:#f85149">ويضمن&lt;/span> &lt;span style="color:#f85149">ذلك&lt;/span> &lt;span style="color:#f85149">اتساق&lt;/span> &lt;span style="color:#f85149">السجلات&lt;/span> &lt;span style="color:#f85149">وتخزينها&lt;/span> &lt;span style="color:#f85149">في&lt;/span> &lt;span style="color:#f85149">موقع&lt;/span> &lt;span style="color:#f85149">مركزي،&lt;/span> &lt;span style="color:#f85149">مما&lt;/span> &lt;span style="color:#f85149">يجعل&lt;/span> &lt;span style="color:#f85149">تصحيح&lt;/span> &lt;span style="color:#f85149">الأخطاء&lt;/span> &lt;span style="color:#f85149">والمراقبة&lt;/span> &lt;span style="color:#f85149">أسهل&lt;/span> &lt;span style="color:#f85149">بكثير&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">على&lt;/span> &lt;span style="color:#f85149">سبيل&lt;/span> &lt;span style="color:#f85149">المثال،&lt;/span> &lt;span style="color:#f85149">تستخدم&lt;/span> &lt;span style="color:#f85149">فئة&lt;/span> &lt;span style="color:#f85149">`&lt;/span>AiApiClient&lt;span style="color:#f85149">`&lt;/span> &lt;span style="color:#f85149">التسجيل&lt;/span> &lt;span style="color:#f85149">لتسجيل&lt;/span> &lt;span style="color:#f85149">بايتات&lt;/span> &lt;span style="color:#f85149">الصورة&lt;/span> &lt;span style="color:#f85149">المرسلة&lt;/span> &lt;span style="color:#f85149">إلى&lt;/span> &lt;span style="color:#f85149">خدمة&lt;/span> &lt;span style="color:#f85149">الذكاء&lt;/span> &lt;span style="color:#f85149">الاصطناعي،&lt;/span> &lt;span style="color:#f85149">واستجابات&lt;/span> &lt;span style="color:#f85149">واجهة&lt;/span> &lt;span style="color:#f85149">برمجة&lt;/span> &lt;span style="color:#f85149">التطبيقات،&lt;/span> &lt;span style="color:#f85149">وأي&lt;/span> &lt;span style="color:#f85149">أخطاء&lt;/span> &lt;span style="color:#f85149">تحدث&lt;/span> &lt;span style="color:#f85149">أثناء&lt;/span> &lt;span style="color:#f85149">عملية&lt;/span> &lt;span style="color:#f85149">التحليل&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>_logger&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>LogInformation(&lt;span style="color:#a5d6ff">&amp;#34;Sending analyze request with image bytes of length: {Length}&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> imageBytes&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Length);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p align="center">
&lt;img src="https://imgur.com/5NS416X.png">
&lt;/ص>
&lt;h3 id="2-جمع-المقاييس-تلقائيا">2. &lt;strong>جمع المقاييس تلقائيًا&lt;/strong>&lt;/h3>
&lt;p>يقوم .NET Aspire أيضًا بتتبع مقاييس التطبيق المهمة والإبلاغ عنها تلقائيًا مثل أوقات الاستجابة وعدد الطلبات ومعدلات الخطأ. يساعدك هذا على فهم كيفية أداء التطبيق والكشف بسرعة عن أي اختناقات أو مشكلات.&lt;/p>
&lt;p align="center">
&lt;img src="https://imgur.com/SGawOY3.png">
&lt;/ص>
&lt;h3 id="3-تحسين-الأداء">3. &lt;strong>تحسين الأداء&lt;/strong>&lt;/h3>
&lt;p>يعمل .NET Aspire على تحسين مكالمات HTTP، مما يساعد على إبقاء أوقات الاستجابة منخفضة وتقليل استهلاك الموارد غير الضرورية. فهو يوفر ميزات مثل تجميع الاتصالات وإعادة محاولة الطلب والتوجيه الذكي.&lt;/p>
&lt;h3 id="4-التكامل-السلس">4. &lt;strong>التكامل السلس&lt;/strong>&lt;/h3>
&lt;p>يعمل .NET Aspire على تبسيط تكامل الخدمات المتنوعة (مثل خدمات الذكاء الاصطناعي وواجهة برمجة التطبيقات في هذا المشروع) وتبسيط عملية النشر. لا داعي للقلق بشأن التكوينات ذات المستوى المنخفض، حيث تتولى Aspire المهام المتعلقة بالبنية التحتية نيابةً عنك.&lt;/p>
&lt;p align="center">
&lt;img src="https://imgur.com/OSHhWVb.png">
&lt;/ص>
&lt;h3 id="الخلاصةلم-يعد-الذكاء-الاصطناعي-مجرد-كلمة-طنانة-أو-شيء-نراه-في-أفلام-الخيال-العلمي-إنها-تعمل-بنشاط-على-حل-مشكلات-العالم-الحقيقي-اليوم-مثل-تلك-التي-عالجناها-في-هذا-المشروع-وهي-استخراج-البيانات-المنظمة-من-الإيصالات-بمساعدة-azure-openai-وnet-aspire-وblazor-يمكننا-أتمتة-المهمة-اليدوية-التي-قد-تستغرق-وقتا-طويلا-وعرضة-للأخطاء-لا-يقوم-الذكاء-الاصطناعي-فقط-بالدردشة-أو-الاستجابة-للمطالبات-مثل-chatgpt-فهو-يفسر-الصور-ويستخرج-معلومات-قيمة-ويعطينا-رؤى-قابلة-للتنفيذ-في-ثوان">الخلاصةلم يعد الذكاء الاصطناعي مجرد كلمة طنانة أو شيء نراه في أفلام الخيال العلمي. إنها تعمل بنشاط على حل مشكلات العالم الحقيقي اليوم، مثل تلك التي عالجناها في هذا المشروع، وهي استخراج البيانات المنظمة من الإيصالات. بمساعدة &lt;strong>Azure OpenAI&lt;/strong> و**.NET Aspire** و&lt;strong>Blazor&lt;/strong>، يمكننا أتمتة المهمة اليدوية التي قد تستغرق وقتًا طويلاً وعرضة للأخطاء. لا يقوم الذكاء الاصطناعي فقط بالدردشة أو الاستجابة للمطالبات مثل ChatGPT؛ فهو يفسر الصور ويستخرج معلومات قيمة ويعطينا رؤى قابلة للتنفيذ في ثوانٍ.&lt;/h3>
&lt;p>باستخدام &lt;strong>Azure OpenAI&lt;/strong> لتحليل الإيصالات و**.NET Aspire** للتكامل السلس مع التسجيل والمقاييس، قمنا بإنشاء حل قوي وقابل للتطوير. إن إمكانات الذكاء الاصطناعي في تبسيط العمليات التجارية، وأتمتة المهام الشاقة، وتحسين الدقة هائلة، وهذا مجرد مثال واحد لكيفية تطبيقه.&lt;/p>
&lt;p>يعد هذا المنشور جزءًا من &lt;strong>Calendario de Adviento de Inteligencia Artificial 2024 en Español&lt;/strong>، وهو حدث يعرض تطبيقات الذكاء الاصطناعي في العالم الحقيقي ويقوم بتثقيف مجتمع التكنولوجيا الناطق بالإسبانية حول أحدث الاتجاهات. إذا كنت تتطلع إلى التعمق أكثر في الذكاء الاصطناعي وإمكانياته، فإن هذا الحدث يعد مكانًا رائعًا للبدء.&lt;/p>
&lt;p>يعمل الذكاء الاصطناعي على تغيير طريقة عملنا، وهذا المشروع هو مجرد لمحة عما هو ممكن. تكمن القوة الحقيقية للذكاء الاصطناعي في قدرته على حل المشكلات الحقيقية، سواء كانت معالجة الإيصالات، أو تحليل الصور، أو التنبؤ بالاتجاهات. نحن مجرد خدش السطح.&lt;/p>
&lt;h3 id="كود-المصدر">كود المصدر&lt;/h3>
&lt;p>كود المصدر الكامل لهذا المشروع متاح على &lt;a href="https://github.com/emimontesdeoca/ReceiptScannerPoc">GitHub&lt;/a>. لا تتردد في تنزيله، واستكشاف كيفية عمل خدمات الذكاء الاصطناعي وواجهة برمجة التطبيقات معًا، وتكييفه ليناسب حالات الاستخدام الخاصة بك. إذا واجهت أية مشكلات، أو إذا كانت لديك أفكار للتحسينات، فلا تتردد في إنشاء مشكلة أو إرسال طلب سحب. المساهمات مرحب بها دائمًا، وستساعد تعليقاتك في جعل هذا المشروع أفضل!&lt;/p>
&lt;p>ترميز سعيد!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category><category>Azure</category><category>NuGet</category><category>Docker</category><category>AI</category></item><item><title>هل تستخدم الحاويات... لتتبع أسعار هدايا عيد الميلاد؟</title><link>https://emimontesdeoca.github.io/ar/posts/monitoring-prices-containers/</link><pubDate>Thu, 12 Dec 2024 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/monitoring-prices-containers/</guid><description>أتمتة مراقبة أسعار هدايا عيد الميلاد باستخدام حاويات Docker وBlazor والواجهة الخلفية لـ .NET API.</description><content:encoded>&lt;p>اقترب عيد الميلاد، ومعه تأتي المهمة المبهجة المتمثلة في العثور على الهدايا المثالية لأحبائك. إذا كنت مثلي، فمن المحتمل أنك تحب الحصول على صفقة جيدة، ولكن التغلب على الأسعار المرتفعة للسلع الرائجة خلال موسم العطلات يمكن أن يبدو وكأنه رحلة مزلقة حقيقية - مثيرة ولكنها ساحقة بعض الشيء! هذا العام، بدلاً من أن أقود نفسي إلى الجنون من خلال فحص الأسعار يوميًا، قررت الاستفادة من مهاراتي التقنية وأتمتة العملية.&lt;/p>
&lt;p align="center">
&lt;img src="https://imgur.com/7K7QC08.png" />
&lt;/ص>
&lt;p>باعتباري مطور برامج وشخصًا شغوفًا بالاستفادة من التكنولوجيا لحل المشكلات اليومية، وجدت طريقة لأتمتة عملية التحقق من الأسعار. بدلاً من زيارة العديد من المتاجر عبر الإنترنت يدويًا لمقارنة الأسعار، قررت إنشاء نظام يمكنه القيام بذلك نيابةً عني. وهذا لا يوفر الوقت فحسب، بل يضمن أيضًا حصولي على أفضل الصفقات دون ضغوط الفحوصات اليومية.&lt;/p>
&lt;h2 id="لماذا-يتم-أتمتة-مراقبة-الأسعار">لماذا يتم أتمتة مراقبة الأسعار؟&lt;/h2>
&lt;p>تشبه أتمتة مراقبة الأسعار وجود فريق خاص بك من أقزام سانتا يعمل على مدار الساعة لضمان حصولك على أفضل الصفقات دون أن تحرك ساكنًا. إليك سبب إعجابك به:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>العناصر باهظة الثمن&lt;/strong>: نعلم جميعًا أن هدايا عيد الميلاد يمكن أن تصبح باهظة الثمن، خاصة عندما تكون الأدوات والألعاب مدرجة في قائمتك. يمكن أن يكون توفير المال على هذه المنتجات مرضيًا تمامًا مثل العثور على آخر قطعة علوية للشجرة في المخزون.&lt;/li>
&lt;li>&lt;strong>الفحوصات اليومية&lt;/strong>: من لديه الوقت (أو الصبر) للتحقق من الأسعار كل يوم؟ ليس أنا!&lt;/li>
&lt;li>&lt;strong>الأتمتة&lt;/strong>: احتضن روح العطاء في العطلة، وامنح نفسك هدية الكفاءة.&lt;/li>
&lt;li>&lt;strong>الدقة&lt;/strong>: تضمن الأتمتة أن تكون عمليات التحقق من الأسعار دقيقة بقدر سطوع أنف رودولف.&lt;/li>
&lt;/ol>
&lt;h2 id="التقنيات-الأساسية">التقنيات الأساسية&lt;/h2>
&lt;p>لبناء مساعد سانتا الصغير الخاص بي، قررت استخدام العديد من التقنيات الأساسية:&lt;/p>
&lt;p>###حاويات&lt;/p>
&lt;p>تشبه الحاويات غلاف عيد الميلاد للبرامج. يقومون بتجميع تطبيقاتك بكل الأشياء الجيدة (التبعيات) بحيث يسير كل شيء بسلاسة مثل ركوب الزلاجة في الثلج الطازج. Docker هو خيارنا الأمثل لإنشاء هذه الحاويات.&lt;/p>
&lt;p>###بلازور&lt;/p>
&lt;p>يعد Blazor إطارًا رائعًا لبناء تطبيقات الويب التفاعلية باستخدام .NET. يشبه الأمر استبدال ترانيم عيد الميلاد العامة بقائمة التشغيل الخاصة بك في العطلات، والتي تكون مصممة خصيصًا وفعالة وممتعة للغاية.&lt;/p>
&lt;h3 id="عامل-الميناء-يؤلف">عامل الميناء يؤلف&lt;/h3>
&lt;p>Docker-Compose هو مدير عملياتنا في القطب الشمالي. فهو يساعدنا في الحفاظ على تشغيل جميع خدماتنا - مثل واجهة برمجة التطبيقات (API) والواجهة الأمامية لـ Blazor - معًا في وئام تام. فكر في الأمر كقائد لسيمفونية عطلتنا.&lt;/p>
&lt;h2 id="دليل-خطوة-بخطوة">دليل خطوة بخطوة&lt;/h2>
&lt;p>الآن، دعونا نتعمق في ورشة عمل سانتا ونجعل هذا المشروع ينبض بالحياة!&lt;/p>
&lt;h3 id="الخطوة-1-إنشاء-واجهة-برمجة-التطبيقات">الخطوة 1: إنشاء واجهة برمجة التطبيقات&lt;/h3>
&lt;h4 id="11-إعداد-المشروع">1.1 إعداد المشروع&lt;/h4>
&lt;p>ارتدي قبعة سانتا للبرمجة وقم بإعداد مشروع ASP.NET Core Web API جديد. افتح جهازك الطرفي (يشبه إلى حد ما فتح تقويم المجيء الخاص بك) وقم بتشغيل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new webapi -o PriceMonitorApi
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd PriceMonitorApi
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يقوم هذا الأمر بإنشاء دليل جديد باسم &lt;code>PriceMonitorApi&lt;/code> وإعداد مشروع Web API أساسي. تخيل أن الأمر يشبه إنشاء قاعدة متينة لمنزل خبز الزنجبيل الخاص بك.&lt;/p>
&lt;h4 id="12-إضافة-httpclient-وحذف-المكتباتبعد-ذلك-أضف-httpclient-ومكتبة-لتحليل-html-وستكون-هذه-مزلقتنا-الموثوقة-لجلب-بيانات-الأسعار-وقراءتها">1.2 إضافة HttpClient وحذف المكتباتبعد ذلك، أضف &lt;code>HttpClient&lt;/code> ومكتبة لتحليل HTML. وستكون هذه مزلقتنا الموثوقة لجلب بيانات الأسعار وقراءتها.&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet add package HtmlAgilityPack
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>HtmlAgilityPack هو القزم الذي يساعدنا في تحليل مستندات HTML.&lt;/p>
&lt;h4 id="13-إنشاء-النماذج">1.3 إنشاء النماذج&lt;/h4>
&lt;p>النماذج تشبه مخطط ألعابنا. لنقم بإنشاء نموذج لتمثيل معلومات المنتج:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Models&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ProductInfo&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Title { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لقد أنشأنا للتو القالب المثالي لبياناتنا.&lt;/p>
&lt;h4 id="14-تنفيذ-خدمة-الكاشطة">1.4 تنفيذ خدمة الكاشطة&lt;/h4>
&lt;p>تشبه خدمة الكشط لدينا مساعد سانتا الصغير، حيث تقوم بجلب المعلومات ومعالجتها لنا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HtmlAgilityPack&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Globalization&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperService(HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ProductInfo&amp;gt; ScrapeProductInfoAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.GetStringAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> htmlDoc = &lt;span style="color:#ff7b72">new&lt;/span> HtmlDocument();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> htmlDoc.LoadHtml(response);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> title = htmlDoc.DocumentNode.SelectSingleNode(&lt;span style="color:#a5d6ff">&amp;#34;//h1[@class=&amp;#39;product-title&amp;#39;]&amp;#34;&lt;/span>).InnerText.Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> priceString = htmlDoc.DocumentNode.SelectSingleNode(&lt;span style="color:#a5d6ff">&amp;#34;//span[@class=&amp;#39;product-price&amp;#39;]&amp;#34;&lt;/span>).InnerText.Trim();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">decimal&lt;/span>.TryParse(priceString, NumberStyles.Currency, CultureInfo.InvariantCulture, &lt;span style="color:#ff7b72">out&lt;/span> &lt;span style="color:#ff7b72">var&lt;/span> price))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ProductInfo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Title = title,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Price = price,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = DateTime.UtcNow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Unable to parse price&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يحول هذا المقتطف &lt;code>HtmlAgilityPack&lt;/code> إلى عصا سحرية للعطلات.&lt;/p>
&lt;h4 id="15-إنشاء-وحدة-تحكم-api">1.5 إنشاء وحدة تحكم API&lt;/h4>
&lt;p>لنقم بإنشاء وحدة تحكم تعمل كحارس البوابة لبياناتنا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Services&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorApi.Controllers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ApiController]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;api/[controller]&lt;span style="color:#a5d6ff">&amp;#34;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperController&lt;/span> : ControllerBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ScraperService _scraperService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperController(ScraperService scraperService)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _scraperService = scraperService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet(&amp;#34;productinfo&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ActionResult&amp;lt;ProductInfo&amp;gt;&amp;gt; GetProductInfo(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> productInfo = &lt;span style="color:#ff7b72">await&lt;/span> _scraperService.ScrapeProductInfoAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Ok(productInfo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception ex)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> BadRequest(&lt;span style="color:#ff7b72">new&lt;/span> { Message = ex.Message });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تضمن وحدة التحكم لدينا تسليم البيانات بشكل أسرع من تسليم سانتا أسفل المدخنة.&lt;/p>
&lt;h4 id="16-تسجيل-الخدمات-في-حاوية-di">1.6 تسجيل الخدمات في حاوية DI&lt;/h4>
&lt;p>وأخيرًا، قم بتسجيل &lt;code>ScraperService&lt;/code> للتأكد من أنه متاح عند الحاجة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>services.AddHttpClient&amp;lt;ScraperService&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن أصبحت واجهة برمجة التطبيقات الخاصة بك جاهزة - مثل مزلقة سانتا عشية عيد الميلاد!&lt;/p>
&lt;hr>
&lt;h3 id="الخطوة-الثانية-إنشاء-تطبيق-blazor">الخطوة الثانية: إنشاء تطبيق Blazor&lt;/h3>
&lt;p>يساعدنا Blazor في تزيين مشروعنا مثل شجرة عيد الميلاد، مما يجعله جذابًا بصريًا وتفاعليًا.&lt;/p>
&lt;h4 id="21-إعداد-مشروع-blazor">2.1 إعداد مشروع Blazor&lt;/h4>
&lt;p>بعد ذلك، سنقوم بإنشاء مشروع Blazor الذي يعمل كواجهة لركوب مزلقة لمراقبة الأسعار.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>dotnet new blazor -o PriceMonitorBlazor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd PriceMonitorBlazor
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يُضفي هذا الأمر بعض سحر العطلة لإعداد مشروع Blazor WebAssembly الأساسي.&lt;/p>
&lt;h4 id="22-إضافة-النماذج">2.2 إضافة النماذج&lt;/h4>
&lt;p>تمامًا مثل إعداد الزخارف، قم بإضافة نموذج &lt;code>ProductInfo&lt;/code> في مشروع Blazor:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Models&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ProductInfo&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Title { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">decimal&lt;/span> Price { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="23-إنشاء-الخدمة-لاستدعاءات-api">2.3 إنشاء الخدمة لاستدعاءات API&lt;/h4>
&lt;p>قم بإنشاء خدمة لجلب البيانات من واجهة برمجة التطبيقات (API) الخاصة بنا - فكر في الأمر على أنه صديقنا للتسوق عبر الإنترنت:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Threading.Tasks&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperService(HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ProductInfo&amp;gt; GetProductInfoAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.GetFromJsonAsync&amp;lt;ProductInfo&amp;gt;(&lt;span style="color:#a5d6ff">$&amp;#34;https://localhost:5001/api/scraper/productinfo?url={url}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="24-تسجيل-الخدمات-في-حاوية-di">2.4 تسجيل الخدمات في حاوية DI&lt;/h4>
&lt;p>تأكد من تسجيل &lt;code>ScraperService&lt;/code> حتى نتمكن من إدخاله في مكوناتنا.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;ScraperService&amp;gt;();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="25-إنشاء-مكون-blazor">2.5 إنشاء مكون Blazor&lt;/h3>
&lt;p>قم بتحديث &lt;code>Pages/Index.razor&lt;/code> ليشمل واجهة ممتعة وتفاعلية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ScraperService ScraperService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price Monitor&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>label &lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Product URL:&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>label&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>input id&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>bind&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;productUrl&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;FetchProductInfo&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Fetch Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> productInfos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string productUrl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> productInfos &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task FetchProductInfo()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>IsNullOrEmpty(productUrl))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> productInfo &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetProductInfoAsync(productUrl);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(productInfo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إنه مثل إعداد عرض احتفالي للعطلات — مما يجعل تطبيقنا تفاعليًا ومبهجًا.&lt;/p>
&lt;hr>
&lt;h3 id="الخطوة-3-توصيل-كل-شيء-باستخدام-docker-compose">الخطوة 3: توصيل كل شيء باستخدام Docker-Compose&lt;/h3>
&lt;p>الآن دعونا نربط كل شيء باستخدام Docker-Compose، لنحول مشروعنا إلى رحلة مزلقة جيدة التزييت.&lt;/p>
&lt;h4 id="31-إنشاء-ملفات-dockerfiles">3.1 إنشاء ملفات Dockerfiles&lt;/h4>
&lt;p>قم بإنشاء Dockerfiles لكل من مشاريع API وBlazor:&lt;/p>
&lt;p>&lt;strong>PriceMonitorApi/Dockerfile&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/aspnet:6.0 AS base&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 80&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 443&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/sdk:6.0 AS build&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /src&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi/PriceMonitorApi.csproj&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi/&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet restore &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi/PriceMonitorApi.csproj&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> . .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> &amp;#34;/src/PriceMonitorApi&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet build &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi.csproj&amp;#34;&lt;/span> -c Release -o /app/build&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> build AS publish&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet publish &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi.csproj&amp;#34;&lt;/span> -c Release -o /app/publish&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> base AS final&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> --from&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>publish /app/publish .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">ENTRYPOINT&lt;/span> [&lt;span style="color:#a5d6ff">&amp;#34;dotnet&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorApi.dll&amp;#34;&lt;/span>]&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>PriceMonitorBlazor/Dockerfile&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/aspnet:6.0 AS base&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 80&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">EXPOSE&lt;/span>&lt;span style="color:#a5d6ff"> 443&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> mcr.microsoft.com/dotnet/sdk:6.0 AS build&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /src&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor/PriceMonitorBlazor.csproj&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor/&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet restore &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor/PriceMonitorBlazor.csproj&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> . .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> &amp;#34;/src/PriceMonitorBlazor&amp;#34;&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet build &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor.csproj&amp;#34;&lt;/span> -c Release -o /app/build&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> build AS publish&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">RUN&lt;/span> dotnet publish &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor.csproj&amp;#34;&lt;/span> -c Release -o /app/publish&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">FROM&lt;/span>&lt;span style="color:#a5d6ff"> base AS final&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">WORKDIR&lt;/span>&lt;span style="color:#a5d6ff"> /app&lt;/span>&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">COPY&lt;/span> --from&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>publish /app/publish .&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">&lt;/span>&lt;span style="color:#ff7b72">ENTRYPOINT&lt;/span> [&lt;span style="color:#a5d6ff">&amp;#34;dotnet&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;PriceMonitorBlazor.dll&amp;#34;&lt;/span>]&lt;span style="color:#f85149">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="32-إنشاء-docker-composeyml">3.2 إنشاء docker-compose.yml&lt;/h4>
&lt;p>قم بتوصيل جميع النقاط (أضواء عيد الميلاد) بملف &lt;code>docker-compose.yml&lt;/code> واحد:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">version&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#39;3.4&amp;#39;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">services&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">api&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">image&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">pricemonitorapi&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">build&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">context&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">./PriceMonitorApi&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">dockerfile&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Dockerfile&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">ports&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">&amp;#34;5000:80&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">networks&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">price-monitor-network&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">blazor&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">image&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">pricemonitorblazor&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">build&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">context&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">./PriceMonitorBlazor&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">dockerfile&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Dockerfile&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">ports&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">&amp;#34;5001:80&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">depends_on&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">api&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">networks&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">price-monitor-network&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">networks&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">price-monitor-network&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">driver&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bridge&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="المخططات">المخططات&lt;/h3>
&lt;p>فيما يلي الرسوم البيانية للمساعدة في تصور المكونات المختلفة وتدفق البيانات، وهذا سيساعدنا على فهم ما يحدث بالفعل في عالم سانتا لدينا:&lt;/p>
&lt;h4 id="مخطط-معمارية-النظام">مخطط معمارية النظام&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/nE0hSJ4.png" alt="الصورة">&lt;/p>
&lt;h4 id="مخطط-تدفق-البيانات">مخطط تدفق البيانات&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/hJXv4Jw.png" alt="Imgur">&lt;/p>
&lt;h4 id="مخطط-التسلسل">مخطط التسلسل&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/FH6VzuH.png" alt="الصورة">&lt;/p>
&lt;h4 id="مخطط-المكونات-لإعداد-docker">مخطط المكونات لإعداد Docker&lt;/h4>
&lt;p>&lt;img src="https://imgur.com/efQ9WuT.png" alt="الصورة">&lt;/p>
&lt;h3 id="الخطوة-4-إدارة-url-والتحديث-والتحديث-الدوري-الآليفي-هذا-العرض-التوضيحي-سنقوم-فقط-بتخزينه-في-الذاكرة-ولكن-في-التطبيق-الحقيقي-يمكنك-استخدام-قاعدة-بيانات-بالنسبة-لهذا-المشروع-الممتع-دعونا-نلتزم-بالتخزين-في-الذاكرة">الخطوة 4: إدارة URL، والتحديث، والتحديث الدوري الآليفي هذا العرض التوضيحي، سنقوم فقط بتخزينه في الذاكرة، ولكن في التطبيق الحقيقي، يمكنك استخدام قاعدة بيانات. بالنسبة لهذا المشروع الممتع، دعونا نلتزم بالتخزين في الذاكرة.&lt;/h3>
&lt;h4 id="41-تعديل-الخدمة-لإدارة-عناوين-url">4.1 تعديل الخدمة لإدارة عناوين URL&lt;/h4>
&lt;p>أولاً، سوف نتأكد من أن خدمتنا يمكنها التعامل مع إضافة عناوين URL واستردادها، بالإضافة إلى جلب معلومات المنتج:&lt;/p>
&lt;p>تحديث &lt;code>Services/ScraperService.cs&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Models&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Collections.Generic&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net.Http.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Threading.Tasks&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">PriceMonitorBlazor.Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ScraperService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> HttpClient _httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; urls = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ScraperService(HttpClient httpClient)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _httpClient = httpClient;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;ProductInfo&amp;gt; GetProductInfoAsync(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> response = &lt;span style="color:#ff7b72">await&lt;/span> _httpClient.GetFromJsonAsync&amp;lt;ProductInfo&amp;gt;(&lt;span style="color:#a5d6ff">$&amp;#34;http://localhost:5000/api/scraper/productinfo?url={url}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> AddUrl(&lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!urls.Contains(url))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> urls.Add(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetUrls()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> urls;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ClearUrls()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> urls.Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="42-تحديث-مكون-blazor-لإدارة-url-وتحديثه">4.2 تحديث مكون Blazor لإدارة URL وتحديثه&lt;/h4>
&lt;p>بعد ذلك، قم بتحديث &lt;code>Pages/Index.razor&lt;/code> لإضافة إدارة URL والتحديث والتحديث الدوري التلقائي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>page &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>using PriceMonitorBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>inject ScraperService ScraperService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price Monitor&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>h3&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>label &lt;span style="color:#ff7b72">for&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Product URL:&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>label&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>input id&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;urlInput&amp;#34;&lt;/span> &lt;span style="color:#f85149">@&lt;/span>bind&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;productUrl&amp;#34;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;AddUrl&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Add URL&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>button &lt;span style="color:#f85149">@&lt;/span>onclick&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;RefreshPrices&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Refresh All Prices&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>button&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>p&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>&lt;span style="color:#ff7b72">if&lt;/span> (productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Any())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>table &lt;span style="color:#ff7b72">class&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;table&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>th&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>thead&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>foreach (&lt;span style="color:#ff7b72">var&lt;/span> product &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> productInfos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Title&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Price&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>&lt;span style="color:#f85149">@&lt;/span>product&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Date&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>td&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tr&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>tbody&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;/&lt;/span>table&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private string productUrl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span> productInfos &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new List&lt;span style="color:#ff7b72;font-weight:bold">&amp;lt;&lt;/span>ProductInfo&lt;span style="color:#ff7b72;font-weight:bold">&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private &lt;span style="color:#f0883e;font-weight:bold">Timer&lt;/span> _timer;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> protected override void OnInitialized()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StartTimer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void StartTimer()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72;font-weight:bold">//&lt;/span> Set up the timer to call RefreshPrices every minute (&lt;span style="color:#a5d6ff">60000&lt;/span> ms)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _timer &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> new &lt;span style="color:#f0883e;font-weight:bold">Timer&lt;/span>(async _ &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> await InvokeAsync(RefreshPrices), null, TimeSpan&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Zero, TimeSpan&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>FromMinutes(&lt;span style="color:#a5d6ff">1&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private void AddUrl()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72;font-weight:bold">!&lt;/span>string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>IsNullOrEmpty(productUrl))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>AddUrl(productUrl);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productUrl &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> string&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Empty;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> private async Task RefreshPrices()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> urls &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetUrls();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> foreach (&lt;span style="color:#ff7b72">var&lt;/span> url &lt;span style="color:#ff7b72;font-weight:bold">in&lt;/span> urls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> productInfo &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> await ScraperService&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>GetProductInfoAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> productInfos&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Add(productInfo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في هذا المكون المحدث:&lt;/p>
&lt;ul>
&lt;li>نستخدم &lt;code>Timer&lt;/code> للاتصال بطريقة &lt;code>RefreshPrices&lt;/code> بشكل دوري كل دقيقة.&lt;/li>
&lt;li>تعمل طريقة &lt;code>StartTimer&lt;/code> على تهيئة المؤقت ليبدأ على الفور ثم يتم تشغيله كل 60 ثانية.&lt;/li>
&lt;li>يستدعي أسلوب دورة الحياة &lt;code>OnInitialized&lt;/code> &lt;code>StartTimer&lt;/code> عند تهيئة المكون لبدء التحديث الدوري.&lt;/li>
&lt;/ul>
&lt;h4 id="43-تشغيل-الحل">4.3 تشغيل الحل&lt;/h4>
&lt;p>لتشغيل تطبيق Blazor المحدث بالميزات الجديدة، قم بإعادة بناء حاويات Docker وإعادة تشغيلها:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>docker-compose build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker-compose up
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد تحميل &lt;code>http://localhost:5001&lt;/code> في متصفحك، يجب الآن على تطبيق Blazor تحديث أسعار المنتجات تلقائيًا كل دقيقة بالإضافة إلى السماح بالتحديثات اليدوية وإدارة عناوين URL.&lt;/p>
&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>لقد كان بناء نظام مراقبة الأسعار هذا بمثابة انفجار احتفالي! لم ينقذني ذلك من ضغوط التحقق اليومي من الأسعار فحسب، بل أظهر أيضًا سحر تقنيات الويب الحديثة.&lt;/p>
&lt;h1 id="التقويم-التقني-الاحتفالي-2024">التقويم التقني الاحتفالي 2024&lt;/h1>
&lt;p align="center">
&lt;img src="https://festivetechcalendar.com/assets/images/Heading.png" />
&lt;/ص>
&lt;p>لقد أنشأت هذا المنشور كجزء من حدث &lt;strong>Festive Tech Calendar 2024&lt;/strong>، الذي يجمع عشاق التكنولوجيا والمبتكرين والحالمين الرقميين لمشاركة المعرفة والاحتفال بدمج الروح الاحتفالية والعجائب التكنولوجية. لا تقتصر هذه المبادرة على التعلم والتواصل فحسب، بل تتعلق أيضًا بتقديم العطاء.&lt;/p>
&lt;p>&lt;strong>يدعم تقويم التكنولوجيا الاحتفالي 2024&lt;/strong> مؤسسة Beatson Cancer الخيرية هذا العام. جمعية بيتسون لمكافحة السرطان مكرسة لدعم الأشخاص المصابين بالسرطان وأسرهم ومتخصصي الرعاية الصحية الذين يعتنون بهم. يمكن العثور على مزيد من المعلومات حول عملهم المذهل على &lt;a href="https://www.beatsoncancercharity.org/">https://www.beatsoncancercharity.org/&lt;/a>.&lt;/p>
&lt;p>قم بزيارة موقع ويب Festive Tech Calendar على &lt;a href="https://festivetechcalendar.com">https://festivetechcalendar.com&lt;/a> للحصول على الأسئلة الشائعة ومزيد من التفاصيل حول الحدث.&lt;/p>
&lt;h1 id="هو-هو-هو">&lt;strong>هو هو هو!&lt;/strong>&lt;/h1>
&lt;p>لقد كان إنشاء هذا المشروع طريقة رائعة للمساهمة في مجتمع التكنولوجيا الاحتفالي ودعم قضية عظيمة في نفس الوقت.&lt;/p>
&lt;p>أتمنى أن تجد هذا الدليل مفيدًا وأن يلهمك لاستكشاف المزيد من الطرق لاستخدام التكنولوجيا لتبسيط المهام اليومية.&lt;/p>
&lt;p>إذا كانت لديك أي أسئلة أو كنت بحاجة إلى مزيد من المساعدة، فلا تتردد في التواصل معنا.&lt;/p>
&lt;p>ترميز سعيد!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category><category>Docker</category><category>API</category></item><item><title>سمة التحقق المخصصة والتحقق من صحة Blazor</title><link>https://emimontesdeoca.github.io/ar/posts/custom-validationattribute-blazor/</link><pubDate>Fri, 29 Mar 2024 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/custom-validationattribute-blazor/</guid><description>قم بإنشاء فئات ValidationAttribute مخصصة قابلة لإعادة الاستخدام للتحقق من صحة نموذج Blazor مع التعليقات التوضيحية للبيانات.</description><content:encoded>&lt;p>﻿#تخصيص كل شيء&lt;/p>
&lt;p>كما رأيت على الأرجح في كل منشوراتي، أحاول حقًا الحفاظ على كل شيء نظيفًا قدر الإمكان، حيث أنني كتبت بالفعل منشورات تتعلق بالسمات المخصصة، ومعالجة الاستثناءات المخصصة، وحقن مجموعة الخدمات، وما إلى ذلك.&lt;/p>
&lt;p>لقد أدركت مع مرور الوقت أن هذا النوع من طريقة البرمجة يمنحني وفريقي طريقة لتحسين العمل الإضافي، والعثور على المشكلات بشكل أسهل وفصل التعليمات البرمجية قدر الإمكان.&lt;/p>
&lt;p>نعم، بعد هذه القصة الرائعة، لقد كنت أعمل على Blazor مؤخرًا كالمعتاد ووجدت أنه بعد سنوات وسنوات من التطوير، يمكنك إنشاء سمات تحقق مخصصة.&lt;/p>
&lt;p>نعم إنه أمر مضحك، بعد كل هذه السنوات&amp;hellip;&lt;/p>
&lt;h1 id="سمات-التحقق-المخصصة">سمات التحقق المخصصة&lt;/h1>
&lt;p>في الواقع، جاءت الفكرة من العمل، فنحن نقوم دائمًا بالتحقق من الصحة في جميع الأماكن ولكن كان لدي بعض الحقول التي تتطلب نفس عملية التحقق من الصحة، لذلك اعتقدت أنه قد يكون هناك شيء ما هناك&amp;hellip;. مثل سمات التحقق المخصصة!&lt;/p>
&lt;p>لذلك قمت بتشغيل وثائق Microsoft لذلك واكتشفت أنه نعم، يمكنك إنشاء سمات تحقق مخصصة وتعيينها للخصائص، تمامًا مثل هذا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">StringLengthRangeAttribute&lt;/span> : ValidationAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Minimum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Maximum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> StringLengthRangeAttribute()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.Minimum = &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.Maximum = &lt;span style="color:#ff7b72">int&lt;/span>.MaxValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsValid(&lt;span style="color:#ff7b72">object&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> strValue = &lt;span style="color:#ff7b72">value&lt;/span> &lt;span style="color:#ff7b72">as&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(strValue))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span> len = strValue.Length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> len &amp;gt;= &lt;span style="color:#ff7b72">this&lt;/span>.Minimum &amp;amp;&amp;amp; len &amp;lt;= &lt;span style="color:#ff7b72">this&lt;/span>.Maximum;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>واستخدامها في فئة بسيطة مثل هذا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Required]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[StringLengthRange(Minimum = 10, ErrorMessage = &amp;#34;Must be &amp;gt;10 characters.&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[StringLengthRange(Maximum = 20)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Required]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[StringLengthRange(Minimum = 10, Maximum = 20)]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="مدقق-مخصص">مدقق مخصص&lt;/h1>
&lt;p>لذا، لدي أداة التحقق هذه التي أحتاجها لبعض حالات العمل المحددة التي ستحتوي على 20 حرفًا أولًا تستبدل 9 أرقام وواصلة، وتنتهي بحرفين يمثلان عادةً رمز البلد، لذلك شيء من هذا القبيل: &lt;strong>123456789-123456789-ES&lt;/strong>&lt;/p>
&lt;p>انتهى بي الأمر بشيء مثل هذا، إنه بسيط حقًا ولكنه يعمل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.ComponentModel.DataAnnotations&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text.RegularExpressions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">SpecialStringValidatorAttribute&lt;/span> : ValidationAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">const&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> TotalLength = &lt;span style="color:#a5d6ff">22&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">const&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Pattern = &lt;span style="color:#a5d6ff">@&amp;#34;^(\d{10})-(\d{10})-([A-Za-z]{2})$&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> ValidationResult IsValid(&lt;span style="color:#ff7b72">object&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>, ValidationContext validationContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> strValue = &lt;span style="color:#ff7b72">value&lt;/span> &lt;span style="color:#ff7b72">as&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(strValue))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (strValue.Length != TotalLength)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationResult(&lt;span style="color:#a5d6ff">$&amp;#34;The string must be {TotalLength} characters long.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!Regex.IsMatch(strValue, Pattern))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationResult(&lt;span style="color:#a5d6ff">&amp;#34;The string must follow the pattern: 1234567890-1234567890-AB&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ValidationResult.Success;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> ValidationResult(&lt;span style="color:#a5d6ff">&amp;#34;The string cannot be null or empty.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="الاختبارات">الاختبارات&lt;/h2>
&lt;p>لقد كتبت بعض الاختبارات لهم أيضًا، فقط في حالة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">SpecialStringValidatorTests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Theory]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-AB&amp;#34;, true)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-XY&amp;#34;, true)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-A1&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-A&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-123456789-AB&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [InlineData(&amp;#34;1234567890-1234567890-ABCDE&amp;#34;, false)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> SpecialStringValidatorTest(&lt;span style="color:#ff7b72">string&lt;/span> input, &lt;span style="color:#ff7b72">bool&lt;/span> expectedResult)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Arrange&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> validator = &lt;span style="color:#ff7b72">new&lt;/span> SpecialStringValidatorAttribute();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Act&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> result = validator.IsValid(input);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.Equal(expectedResult, result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وعندما ركضت حصلت على هذه النتائج:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>Microsoft &lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>R&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span> Test Execution Command Line Tool Version 16.9.1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Copyright &lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>c&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span> Microsoft Corporation. All rights reserved.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Starting test execution, please wait...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>A total of &lt;span style="color:#a5d6ff">1&lt;/span> test files matched the specified pattern.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Test run in progress...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-AB&amp;#34;&lt;/span>, expectedResult: True&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-XY&amp;#34;&lt;/span>, expectedResult: True&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-A1&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-A&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-123456789-AB&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Passed! - SpecialStringValidatorTests.SpecialStringValidatorTest&lt;span style="color:#ff7b72;font-weight:bold">(&lt;/span>input: &lt;span style="color:#a5d6ff">&amp;#34;1234567890-1234567890-ABCDE&amp;#34;&lt;/span>, expectedResult: False&lt;span style="color:#ff7b72;font-weight:bold">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Test Run Successful.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Total tests: &lt;span style="color:#a5d6ff">7&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Passed: &lt;span style="color:#a5d6ff">7&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Total time: 1.7296 Seconds
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="التحقق-المخصص-وblazor">التحقق المخصص وBlazor&lt;/h1>
&lt;p>والآن بعد أن علمت أنه يمكن استخدامه، من الواضح أنه من الجيد تنفيذه في Blazor، أليس كذلك؟&lt;/p>
&lt;p>لنفترض أن لدي هذا النموذج، والذي سيستخدم النموذج &lt;code>Person&lt;/code> الذي عرضته من قبل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@using Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt;Home&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;EditForm Model=@Person FormName=&lt;span style="color:#a5d6ff">&amp;#34;PersonForm&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;DataAnnotationsValidator/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationSummary/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-group&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Name&amp;#34;&lt;/span>&amp;gt;Name&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText @bind-Value=Person.Name class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;Name&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&lt;span style="color:#a5d6ff">&amp;#34;() =&amp;gt; Person.Name&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-group&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;MySpecialString&amp;#34;&lt;/span>&amp;gt;My special &lt;span style="color:#ff7b72">string&lt;/span>&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputText @bind-Value=Person.MySpecialString class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;Name&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&lt;span style="color:#a5d6ff">&amp;#34;() =&amp;gt; Person.MySpecialString&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-group&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Age&amp;#34;&lt;/span>&amp;gt;Age&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;InputNumber @bind-Value=Person.Age class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;Age&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;ValidationMessage For=&lt;span style="color:#f85149">@&lt;/span>(() =&amp;gt; Person.Age) /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Save&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/EditForm&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Person Person = &lt;span style="color:#ff7b72">new&lt;/span> Person();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بمجرد تشغيل هذا، نحصل على الأخطاء التالية:&lt;/p>
&lt;img src="https://i.imgur.com/psfpzdr.png">
&lt;p>وإذا وضعنا ما نريد فقط، نحصل على الأمر التالي، كل شيء واضح:&lt;/p>
&lt;img src="https://i.imgur.com/RPoUq02.png">
&lt;p>لأكون صادقًا بالنسبة لي، من الواضح تمامًا أننا يجب أن ننقل المنطق على الأقل للتحقق من صحة هذه النماذج إلى سمات التحقق المخصصة، فهذا يمنحنا حرية تخزين الكود الخاص بتسجيل الدخول هذا في مكان واحد، ويمكننا استخدامه لاحقًا لواجهة برمجة التطبيقات (API) أو تطبيق آخر.&lt;/p>
&lt;p>آمل أن تكون قد نالت إعجابك، إذا كان لديك أي أسئلة أو كنت ترغب في التواصل معي، فلا تتردد واتصل بي!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>معالجة الاستثناءات المخصصة على .NET API</title><link>https://emimontesdeoca.github.io/ar/posts/custom-exception-handler-api/</link><pubDate>Sun, 01 Oct 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/custom-exception-handler-api/</guid><description>أنشئ برامج وسيطة مخصصة لمعالجة الاستثناءات لإرجاع استجابات الأخطاء النظيفة من واجهات برمجة تطبيقات .NET.</description><content:encoded>&lt;p>﻿الاستثناءات سيئة، ونحن نعلم أليس كذلك؟ ولكن ماذا لو كان علينا التعامل معهم؟&lt;/p>
&lt;p>ماذا يحدث عندما يكون لدينا استثناء، على سبيل المثال، في واجهة برمجة التطبيقات (API)، فإنه يعرض رسالة مكدسة تتضمن الكثير من المعلومات التي قد نرغب في إزالتها من الاستجابة التي يتلقاها مستخدمونا.&lt;/p>
&lt;p>بالنسبة للعرض التوضيحي، قمت بإنشاء واجهة برمجة تطبيقات dotnet وأضفت طريقة من شأنها طرح استثناء:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Route(&amp;#34;GetWithoutExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> Task GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إذا قمنا بطرح استثناء، فسيبدو كما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>System.Exception: This &lt;span style="color:#ff7b72">is&lt;/span> a custom exception!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at CustomExceptionHandleDemo.Controllers.WeatherForecastController.GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at lambda_method16(Closure , Object , Object[] )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeActionMethodAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">12_1&lt;/span>(ControllerActionInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeNextActionFilterAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">10_0&lt;/span>(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State&amp;amp; next, Scope&amp;amp; scope, Object&amp;amp; state, Boolean&amp;amp; isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--- End of stack trace &lt;span style="color:#ff7b72">from&lt;/span> previous location ---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeFilterPipelineAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">20_0&lt;/span>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Routing.EndpointMiddleware.&amp;lt;Invoke&amp;gt;g__AwaitRequestTask|&lt;span style="color:#a5d6ff">6_0&lt;/span>(Endpoint endpoint, Task requestTask, ILogger logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يبدو هذا طبيعيًا، وهذا ما تحصل عليه من الاستثناء، ولكن بالنسبة لي، فإنه يعرض الكثير من المعلومات، إذا كان لديك مكتبات وأشياء، فقد يعرض معلومات معقولة تتعلق بعميلك أو مشروعك أو أشياء أخرى لشخص قد يحاول رؤية الأشياء.&lt;/p>
&lt;p>وهذا ما يبدو على Swagger:&lt;/p>
&lt;img src="https://imgur.com/fUujR3s.png"/>
&lt;h3 id="إنشاء-استثناء-مخصص">إنشاء استثناء مخصص&lt;/h3>
&lt;p>يمكن تجنب هذه الخطوة، لأننا نعرف ما هو الاستثناء الذي سيتم طرحه، في هذه الحالة &lt;code>Exception&lt;/code>، ولكن بالنسبة لي، فإن الحصول على استثناءاتك المخصصة هو الأفضل نظرًا لأن لديك المزيد من التحكم في ما ترميه.&lt;/p>
&lt;p>في هذه الحالة، قمت للتو بإنشاء كائن &lt;code>CustomException&lt;/code> يرث من &lt;code>Exception&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Exceptions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomException&lt;/span> : Exception
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;CustomException&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomException() { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;CustomException&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;string&amp;#34; name=&amp;#34;message&amp;#34;&amp;gt;Parameter for message&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomException(&lt;span style="color:#ff7b72">string&lt;/span> message) : &lt;span style="color:#ff7b72">base&lt;/span>(message) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;CustomException&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;string&amp;#34; name=&amp;#34;message&amp;#34;&amp;gt;Parameter for message&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;Exception&amp;#34; name=&amp;#34;inner&amp;#34;&amp;gt;Parameter for inner&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CustomException(&lt;span style="color:#ff7b72">string&lt;/span> message, Exception inner) : &lt;span style="color:#ff7b72">base&lt;/span>(message, inner) { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد أن قمنا بإنشاء الاستثناء المخصص، فلنقم بتحديث طريقتنا لرمي &lt;code>CustomException&lt;/code> بدلاً من &lt;code>Exception&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Route(&amp;#34;GetWithoutExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> Task GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> CustomException(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لن يغير هذا أي شيء في الوقت الحالي ولكن تتبع المكدس سيُظهر أن الكائن الذي تم طرحه هو &lt;code>CustomException&lt;/code> بدلاً من &lt;code>Exception&lt;/code>، ألقِ نظرة على بداية تتبع المكدس:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>CustomExceptionHandleDemo.Exceptions.CustomException: This &lt;span style="color:#ff7b72">is&lt;/span> a custom exception!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at CustomExceptionHandleDemo.Controllers.WeatherForecastController.GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at lambda_method24(Closure , Object , Object[] )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeActionMethodAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">12_1&lt;/span>(ControllerActionInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&amp;lt;InvokeNextActionFilterAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">10_0&lt;/span>(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State&amp;amp; next, Scope&amp;amp; scope, Object&amp;amp; state, Boolean&amp;amp; isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--- End of stack trace &lt;span style="color:#ff7b72">from&lt;/span> previous location ---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeFilterPipelineAsync&amp;gt;g__Awaited|&lt;span style="color:#a5d6ff">20_0&lt;/span>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&amp;lt;InvokeAsync&amp;gt;g__Logged|&lt;span style="color:#a5d6ff">17_1&lt;/span>(ResourceInvoker invoker)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Routing.EndpointMiddleware.&amp;lt;Invoke&amp;gt;g__AwaitRequestTask|&lt;span style="color:#a5d6ff">6_0&lt;/span>(Endpoint endpoint, Task requestTask, ILogger logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="إنشاء-exceptionfilterattribute">إنشاء ExceptionFilterAttribute&lt;/h3>
&lt;p>لقد منحتنا Microsoft طريقة للتعامل مع الاستثناءات بعد طرحها، ويمكنك التحقق من مزيد من المعلومات &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.exceptionfilterattribute?view=aspnetcore-7.0">هنا&lt;/a>.&lt;/p>
&lt;p>لكن الوثائق التي قدموها لنا حتى الآن هي:&lt;/p>
&lt;blockquote>
&lt;p>عامل تصفية مجردة يتم تشغيله بشكل غير متزامن بعد قيام الإجراء بطرح استثناء. يجب أن تتجاوز الفئات الفرعية OnException(ExceptionContext) أو OnExceptionAsync(ExceptionContext) ولكن ليس كليهما.&lt;/p>&lt;/blockquote>
&lt;p>لذا فلنقم بإنشاء واحد، سنقوم بإنشاء &lt;code>CustomExceptionFilterAttribute&lt;/code> حيث سنقوم بتجاوز &lt;code>OnException&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc.Filters&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Exceptions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.ExceptionFilterAttributes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomExceptionFilterAttribute&lt;/span> : ExceptionFilterAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// OnException&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;ExceptionContext&amp;#34; name=&amp;#34;context&amp;#34;&amp;gt;Parameter for context&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnException(ExceptionContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (context.Exception &lt;span style="color:#ff7b72">is&lt;/span> CustomException)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.HttpContext.Response.StatusCode = &lt;span style="color:#a5d6ff">500&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Result = &lt;span style="color:#ff7b72">new&lt;/span> ObjectResult(context.Exception.Message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كما ترون، نحن نلقي نظرة على &lt;code>ExceptionContext&lt;/code>، عندما يكون الاستثناء نوعًا من &lt;code>CustomException&lt;/code>، فإننا نفعل شيئًا ما.&lt;/p>
&lt;p>يقوم هذا الشيء بتحديث الاستجابة ورمز الحالة لما سنعود إليه.&lt;/p>
&lt;p>من أجل تحديث رمز الحالة، يجب علينا تحديث &lt;code>context.HttpContext.Response.StatusCode&lt;/code> ومن أجل إرجاع النتيجة، علينا تحديث &lt;code>context.Result&lt;/code> عن طريق إعطائه كائنًا موروثًا من &lt;code>ActionResult&lt;/code>.&lt;/p>
&lt;p>هذا مرشح، لذا فهذا يعني أنه يتعين علينا إضافته بشيء ما عن طريق إضافة &lt;code>[CustomExceptionFilter]&lt;/code>.&lt;/p>
&lt;h3 id="استخدام-الفلتر">استخدام الفلتر&lt;/h3>
&lt;p>الآن، دعونا نكرر الطريقة التي لدينا ونضيف هذا الفلتر حتى يتم اتخاذ الإجراء اللازم، وستنتهي نقطة نهاية واجهة برمجة التطبيقات الخاصة بنا على النحو التالي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.ExceptionFilterAttributes&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Exceptions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomExceptionHandleDemo.Controllers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ApiController]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;[controller]&lt;span style="color:#a5d6ff">&amp;#34;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WeatherForecastController&lt;/span> : ControllerBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> WeatherForecastController()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWithoutExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Task GetWithoutExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> CustomException(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWithExceptionHandler&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [CustomExceptionFilter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Task GetWithExceptionHandler()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> CustomException(&lt;span style="color:#a5d6ff">&amp;#34;This is a custom exception!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كما ترون، لدينا طريقة جديدة تسمى &lt;code>GetWithExceptionHandler&lt;/code>، لها نفس المنطق الذي يمتلكه &lt;code>GetWithoutExceptionHandler&lt;/code>، ولكن في هذه الحالة، أضفنا عامل التصفية &lt;code>[CustomExceptionFilter]&lt;/code> إلى الطريقة.&lt;/p>
&lt;p>والنتيجة هي التالية بعد تشغيل الطريقة، سأعرض صورة، لأنها لم تعد تظهر تتبع المكدس:&lt;/p>
&lt;p>&lt;img src="https://imgur.com/a8TSAy2.png"/>وبهذا أنشأنا استثناءً مخصصًا، وهو مرشح لتجاوز ما يحدث عندما نرمي واستثناء ونستخدمه في إحدى الطرق.&lt;/p>
&lt;p>يمكن استخدام هذا للعديد من الأشياء مثل التسجيل ومعرفة ماذا ومتى وأين يحدث الخطأ.&lt;/p></content:encoded><category>.NET</category><category>API</category></item><item><title>طريقة أجمل لحقن الأشياء</title><link>https://emimontesdeoca.github.io/ar/posts/custom-iservicecollection-services/</link><pubDate>Mon, 04 Sep 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/custom-iservicecollection-services/</guid><description>تنظيم تسجيلات حقن التبعية .NET باستخدام طرق ملحق IServiceCollection النظيفة.</description><content:encoded>&lt;p>﻿عندما أقوم بإنشاء أشياء مثل الخدمات أو المستودعات أو السمات أو أي شيء لإدخاله في تطبيقاتي، هناك هذه الخطوة التي يجب علينا القيام بها، وهي إضافة الخدمات فعليًا إلى التطبيق.&lt;/p>
&lt;p>هذا هو نفسه دائمًا، تذهب إلى &lt;code>Program.cs&lt;/code>، ثم تنتقل إلى جزء ما من الملف لإضافة &lt;code>builder.Services.AddScoped&amp;lt;MyService&amp;gt;();&lt;/code> من أجل إدخال الخدمة.&lt;/p>
&lt;p>شيء من هذا القبيل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Repositories&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;ARepository&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;BRepository&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;AService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;BService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;CService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddScoped&amp;lt;DService&amp;gt;(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (!app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseExceptionHandler(&lt;span style="color:#a5d6ff">&amp;#34;/Error&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseHsts();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseHttpsRedirection();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseStaticFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseRouting();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>أعني أنه يعمل، لكني صعب الإرضاء، ولا أحبه حقًا.&lt;/p>
&lt;p>لنفترض أن لدينا عمليات حقن تبعية متعددة نريد القيام بها، وهي ليست في الواقع نفس الأشياء، في حالتي قد يكون هذا شيئًا مثل وجود مكتبة تحتوي على جميع المستودعات، ومكتبة أخرى تحتوي على جميع الخدمات، وأخيرًا، مكتبة أخرى تحتوي على السمات.&lt;/p>
&lt;p>في هذه الحالة، هل يمكنك تخيل عدد الأسطر التي يتعين علينا إضافتها إلى &lt;code>Program.cs&lt;/code>.&lt;/p>
&lt;p>لنفترض أن لدينا مكتبة تحتوي على بعض الخدمات، إذا أردنا تضمين جميع خدماتنا، علينا العمل مع &lt;code>IServiceCollection&lt;/code>.&lt;/p>
&lt;p>لذلك سنقوم بإنشاء &lt;code>static class&lt;/code> الذي سيكون له طريقة &lt;code>static&lt;/code> تسمى &lt;code>AddServices&lt;/code> والتي تُرجع &lt;code>IServiceCollection&lt;/code>.&lt;/p>
&lt;p>في هذه الحالة سيتم تسميته &lt;code>IServiceCollectionServicesExtensions&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// IServiceCollectionServicesExtensions class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">IServiceCollectionServicesExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// AddCoreServices&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;IServiceCollection&amp;#34; name=&amp;#34;services&amp;#34;&amp;gt;Parameter for &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;An object of type &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IServiceCollection AddServices(&lt;span style="color:#ff7b72">this&lt;/span> IServiceCollection services)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;AService&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;BService&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;CService&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;DService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> services;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ليس هذا فحسب، لدينا أيضًا مكتبة أخرى تتضمن بعض المستودعات التي تستخدمها هذه الخدمات، لذلك دعونا نفعل الشيء نفسه.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// IServiceCollectionServicesExtensions class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">IServiceCollectionServicesExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// AddCoreServices&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;IServiceCollection&amp;#34; name=&amp;#34;services&amp;#34;&amp;gt;Parameter for &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;An object of type &amp;lt;see cref=&amp;#34;IServiceCollection&amp;#34;/&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> IServiceCollection AddRepositories(&lt;span style="color:#ff7b72">this&lt;/span> IServiceCollection services)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> services
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;ARepository&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .AddScoped&amp;lt;BRepository&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> services;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن، لدينا مستودعاتنا وطرق تقديم الخدمات التي تم إنشاؤها، ولكن كيف نستخدمها؟&lt;/p>
&lt;p>دعنا نعود إلى &lt;code>Program.cs&lt;/code> ونضيف ما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Repositories&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRepositories(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Services&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddServices(); &lt;span style="color:#8b949e;font-style:italic">// 👀&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (!app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseExceptionHandler(&lt;span style="color:#a5d6ff">&amp;#34;/Error&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseHsts();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseHttpsRedirection();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseStaticFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseRouting();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseAuthorization();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا يبدو أنظف بكثير، أليس كذلك؟ حسنًا، لقد نجحنا في إدخال بعض الخدمات والمستودعات، لكنها الآن تبدو أجمل ولدينا بالفعل ما نقوم بإدخاله في المكتبة الخارجية.&lt;/p></content:encoded><category>.NET</category><category>Docker</category></item><item><title>حقن التبعية مع السمات على .NET API</title><link>https://emimontesdeoca.github.io/ar/posts/api-di-attributes/</link><pubDate>Tue, 22 Aug 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/api-di-attributes/</guid><description>تمكين حقن التبعية في عوامل تصفية إجراءات .NET API باستخدام TypeFilterAttribute بدلاً من ActionAttribute.</description><content:encoded>&lt;p>من المحتمل أن يكون حقن التبعية أحد أفضل الميزات المتوفرة لدينا على .NET في هذه المرحلة. لا توجد طريقة في أي حالة محتملة لعدم استخدامها، لذلك إذا كنت مثلي، فأنت تريد إضافتها إلى جميع عمليات التنفيذ التي تقوم بها.&lt;/p>
&lt;p>المرشحات، وفقًا لـ [وثائق] Microsoft الرسمية(&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1%29">https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1)&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>تسمح عوامل التصفية في ASP.NET Core بتشغيل التعليمات البرمجية قبل أو بعد مراحل محددة في مسار معالجة الطلب.&lt;/p>
&lt;p>تتعامل المرشحات المضمنة مع مهام مثل:&lt;/p>
&lt;ul>
&lt;li>التفويض، منع الوصول إلى الموارد غير المصرح للمستخدم بها.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>التخزين المؤقت للاستجابة، مما يؤدي إلى تقصير مسار الطلب لإرجاع استجابة مخبأة.&lt;/p>&lt;/blockquote>
&lt;p>يمكن إنشاء مرشحات مخصصة للتعامل مع الاهتمامات الشاملة. تتضمن أمثلة الاهتمامات الشاملة معالجة الأخطاء والتخزين المؤقت والتكوين والترخيص والتسجيل. تتجنب المرشحات تكرار التعليمات البرمجية.&lt;/p>&lt;/blockquote>
&lt;p>أنا أعمل كثيرًا مع واجهات برمجة التطبيقات (APIs) وهناك بعض الأشياء التي يجب أن تقوم بتشغيل كل طلب على حدة، أو جميعها تقريبًا، لذا، من الأفضل أن ما نريد القيام به هو العمل معها بالإضافة إلى&amp;hellip;. حقن التبعية!&lt;/p>
&lt;p>لكنها خدعة بعض الشيء في بعض الأحيان، فهي لا تعمل كما نريد إذا أردنا أن نرث من &lt;code>ActionAttribute&lt;/code> لذلك نحتاج إلى العمل مع &lt;code>TypeFilterAttribute&lt;/code>، مما يتيح لنا القيام بالأشياء عند تجاوز &lt;code>OnActionExecutionAsync&lt;/code>.&lt;/p>
&lt;p>عادةً ما أقوم بإنشاء هذه المرشحات لإجراء بعض التسجيل، لذلك سنستخدم ذلك كمثال:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// LoggedQueryAttribute class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">LoggedQueryTypeFilterAttribute&lt;/span> : TypeFilterAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;LoggedQueryTypeFilterAttribute&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> LoggedQueryTypeFilterAttribute() : &lt;span style="color:#ff7b72">base&lt;/span>(&lt;span style="color:#ff7b72">typeof&lt;/span>(LoggedQueryFilter))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// LoggedQueryFilter class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">LoggedQueryFilter&lt;/span> : IAsyncActionFilter
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;see cref=&amp;#34;_loggingService&amp;#34;/&amp;gt; object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> LoggingService _loggingService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Constructor for &amp;lt;see cref=&amp;#34;LoggedQueryFilter&amp;#34;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;LoggingService&amp;#34; name=&amp;#34;loggingService&amp;#34;&amp;gt;Parameter for loggingService&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> LoggedQueryFilter(LoggingService loggingService)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loggingService = loggingService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// OnActionExecutionAsync&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;ActionExecutingContext&amp;#34; name=&amp;#34;context&amp;#34;&amp;gt;Parameter for context&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param cref=&amp;#34;ActionExecutionDelegate&amp;#34; name=&amp;#34;next&amp;#34;&amp;gt;Parameter for next&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get properties&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> properties = (Request)context.ActionArguments.First().Value!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get call from context&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> call = context.HttpContext.Request.Path.Value!;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Logging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loggingService.LogCustomEvent(call);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Continue call&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> next();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>المنطق بسيط جدًا، نحصل على النص عن طريق الوصول إلى كائن &lt;code>context&lt;/code> باستخدام &lt;code>context.ActionArguments.First().Value&lt;/code>، كما نستخدم استدعاء الأسلوب باستخدام &lt;code>context.HttpContext.Request.Path.Value&lt;/code>.&lt;/p>
&lt;p>ثم نقوم فقط باستدعاء طريقتنا من خدمتنا، في هذه الحالة هي &lt;code> _loggingService.LogCustomEvent(call)&lt;/code>.&lt;/p>
&lt;p>بعد ذلك، يجب علينا الاتصال بـ &lt;code>await next();&lt;/code>، لأن خط الأنابيب يجب أن يستمر.&lt;/p>
&lt;p>هذا بالنسبة للسمة، والآن، يجب علينا بالفعل تضمين هذه السمة في إحدى الطرق.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[LoggedQueryTypeFilterAttribute]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> ActionResult&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; TestFilter()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Ok(&lt;span style="color:#a5d6ff">&amp;#34;Hello world!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>آمل أن تكون قد نالت إعجابك، إذا كان لديك أي أسئلة أو كنت ترغب في التواصل معي، فلا تتردد واتصل بي!&lt;/p></content:encoded><category>.NET</category></item><item><title>التوثيق مع المكتبات الخارجية</title><link>https://emimontesdeoca.github.io/ar/posts/swagger-libraries-documentation/</link><pubDate>Fri, 17 Feb 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/swagger-libraries-documentation/</guid><description>قم بتمكين Swagger لعرض وثائق XML من النماذج المحددة في مكتبات فئات .NET الخارجية.</description><content:encoded>&lt;p>﻿يعد استخدام مكتبات متعددة لتقسيم التعليمات البرمجية الخاصة بك للحالات المستقبلية أمرًا جيدًا. أنا شخصياً أحب القيام بذلك، إذا كان بإمكاني تقسيم المنطق الخاص بي بحيث يمكن إعادة استخدامه في مشاريع أخرى أو أجزاء من المشاريع.&lt;/p>
&lt;p>بالنسبة لي على الأقل، من الضروري تقسيم نماذج البيانات، التي تتضمن &lt;code>Entity Framework&lt;/code>، بحيث يمكن الرجوع إليها واستخدامها في &lt;code>Console application&lt;/code>، أو &lt;code>Blazor application&lt;/code> أو &lt;code>API&lt;/code>.&lt;/p>
&lt;p>دعونا نلقي نظرة على مثال لكيفية القيام بالأشياء، هذه لقطة شاشة لمشروع يحتوي على عدد قليل من التطبيقات ومكتبة مشتركة تحتوي على جميع النماذج.&lt;/p>
&lt;img src="https://imgur.com/gdg2nn3.png">
&lt;p>هذا مثال على فصل قمت بنقله من مشروع &lt;code>API&lt;/code> مع وثائق تحتوي على &lt;code>summary&lt;/code> لكل خاصية وفئة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CustomLibrariesDocumentation.Models&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// WeatherForecast class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WeatherForecast&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the Date&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateOnly Date { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the TemperatureC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> TemperatureC { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the TemperatureF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> TemperatureF =&amp;gt; &lt;span style="color:#a5d6ff">32&lt;/span> + (&lt;span style="color:#ff7b72">int&lt;/span>)(TemperatureC / &lt;span style="color:#a5d6ff">0.5556&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Gets or sets the Summary&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string?&lt;/span> Summary { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إذا أشرنا إلى &lt;code>Console application&lt;/code> وبدأنا في استخدام النموذج، فيمكننا رؤية الوثائق، وهي ميزة قياسية في Visual Studio لذا فهي تعمل بشكل جيد، كما ترون هنا:&lt;/p>
&lt;img src="https://imgur.com/Djf1MeO.png">
&lt;p>ولكن بعد ذلك، إذا أشرنا إلى &lt;code>API&lt;/code> وانتقلنا إلى &lt;code>Swagger&lt;/code>، فلن يكون هناك توثيق موجز.&lt;/p>
&lt;img src="https://imgur.com/3Dg2Xpm.png">
&lt;p>كيف نصلح هذا؟&lt;/p>
&lt;p>أولاً، علينا تمكين تعليقات XML على المكتبة، وللقيام بذلك، تحتاج إلى تحديث إعدادات المشاريع وتمكين ``:&lt;/p>
&lt;img src="https://imgur.com/R1j8SfX.png">
&lt;p>سيؤدي هذا إلى إنشاء بعض الملفات في مجلد البناء والتي تنتهي بامتداد الملف &lt;code>xml&lt;/code>، كما ترون هنا:&lt;/p>
&lt;img src="https://imgur.com/O8ywjr2.png">
&lt;p>الآن بعد أن انتهينا من ذلك، يتعين علينا إضافة بعض العناصر على &lt;code>Program.cs&lt;/code>، من أجل التبختر في قراءة تلك الملفات، وذلك لأنه بشكل افتراضي يقوم فقط بتحميل تعريف XML للمشروع الذي يعمل عليه.&lt;/p>
&lt;p>يستخدم طريقة موجودة داخل &lt;code>AddSwaggerGen&lt;/code> تسمى &lt;code>IncludeXmlComments&lt;/code>.&lt;/p>
&lt;p>الفكرة هي أنه إذا كان لدينا جميع ملفات &lt;code>xml&lt;/code>، فسنضطر إلى تحميلها على Swagger.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>builder.Services.AddSwaggerGen(s =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Comments&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> allXmlFiles = Directory.GetFiles(AppContext.BaseDirectory, &lt;span style="color:#a5d6ff">&amp;#34;*.xml&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span> xmlFiles &lt;span style="color:#ff7b72">in&lt;/span> allXmlFiles)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> s.IncludeXmlComments(xmlFiles);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>});
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لقد تم توجيهها مباشرة، نحصل على ملفات &lt;code>xml&lt;/code> من دليل البناء ونضيفها باستخدام طريقة &lt;code>IncludeXmlComments&lt;/code>.&lt;/p>
&lt;p>الآن نقوم بتحميل &lt;code>API&lt;/code> مرة أخرى ونتحقق مما إذا كان بإمكاننا رؤية الوثائق.&lt;/p>
&lt;img src="https://imgur.com/uNVNswn.png">
&lt;p>ويمكنك أن ترى أنه يمكننا رؤية الوثائق!&lt;/p>
&lt;p>آمل أن يكون قد ساعدك، إذا كان لديك أي سؤال فلا تتردد في الاتصال بي!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category><category>API</category></item><item><title>تنظيف الفروع المحلية في جيت</title><link>https://emimontesdeoca.github.io/ar/posts/cleanup-local-branches/</link><pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/cleanup-local-branches/</guid><description>احذف جميع فروع Git المحلية باستثناء الفرع الرئيسي باستخدام أمر PowerShell واحد.</description><content:encoded>&lt;p>﻿هل وصلت إلى مرحلة أصبح لديك فيها عدد كبير جدًا من الفروع المحلية؟ يحدث هذا لي كثيرًا، لأننا نستخدم الفروع لكل ميزة أو خطأ أو عمل روتيني.&lt;/p>
&lt;p>انتهى بي الأمر بشيء كهذا ويصبح قذرًا للغاية بعد فترة&lt;/p>
&lt;img src="https://imgur.com/W3OGJE7.png">
&lt;p>إنه يزعجني حقًا، لذا سأشارككم أمرًا سريعًا يمكنك تشغيله على وحدة التحكم الخاصة بك للقيام بعملية التنظيف هذه!&lt;/p>
&lt;p>لقد وجدت هذا الأمر في Stack Overflow في &lt;a href="https://stackoverflow.com/a/56671336/7823470">الإجابة&lt;/a> التالية بواسطة &lt;a href="https://stackoverflow.com/users/529612/robert-corvus">Robert Corvus&lt;/a>، وهو إصدار يعمل على Powerhsell.&lt;/p>
&lt;p>&lt;strong>يرجى توخي الحذر عند تشغيل هذا الأمر لأنك قد تفقد تغييراتك&lt;/strong>&lt;/p>
&lt;p>قبل تشغيله، تذكر تحديث &lt;code>MY_MASTER_BRANCH_NAME&lt;/code> إلى فرعك الرئيسي، والذي يمكن أن يكون &lt;code>master&lt;/code> كما أستخدمه أو الفروع الجديدة التي تأتي افتراضيًا والتي تسمى &lt;code>main&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>git branch | %{ &lt;span style="color:#79c0ff">$_&lt;/span>.&lt;span style="color:#79c0ff">Trim&lt;/span>() } | ?{ &lt;span style="color:#79c0ff">$_&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">-ne&lt;/span> &lt;span style="color:#a5d6ff">&amp;#39;MY_MASTER_BRANCH_NAME&amp;#39;&lt;/span> } | %{ git branch -D &lt;span style="color:#79c0ff">$_&lt;/span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد تشغيل هذا الأمر، سوف تحصل على نتيجة مثل هذا&lt;/p>
&lt;img src="https://imgur.com/VJn89OZ.png">
&lt;p>نأمل أن يكون مفيدا لك!&lt;/p></content:encoded><category>Git</category></item><item><title>تحديث مسارات الهوية في ASP.NET Core 7</title><link>https://emimontesdeoca.github.io/ar/posts/identity-url-change/</link><pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/identity-url-change/</guid><description>قم بتخصيص تسجيل الدخول الافتراضي لـ ASP.NET Core Identity وتسجيل عناوين URL عن طريق صفحات الهوية السقالة.</description><content:encoded>&lt;p>﻿هل تتساءل كيف تسمي Microsoft الأشياء النادرة جدًا؟ لقد اعتقدت دائمًا أنهم لا يفعلون ذلك بشكل رائع حقًا، لكن هذا ما هو عليه الأمر!&lt;/p>
&lt;p>والشيء الجيد في هذا هو أنه يمكنك تغيير كل شيء إلى حد كبير أثناء تطويرك!&lt;/p>
&lt;p>هل سبق لك أن دخلت إلى إحدى الصفحات وأدركت على الفور أن هذا هو مشروع ويب ASP.NET بمجرد القيام بعملية التسجيل أو تسجيل الدخول؟&lt;/p>
&lt;img src="https://imgur.com/8NMNHGp.png">
&lt;p>لقد حدث لي ذلك كثيرًا لأنه افتراضيًا في المشروع الذي تقوم بإنشائه، لديك عناوين URL هذه للقيام بعملية تسجيل الدخول والتسجيل (لديك أيضًا الكثير من الصفحات الأخرى أيضًا).&lt;/p>
&lt;p>لذلك يوضح هذا البرنامج التعليمي طريقة لتحديث عناوين URL هذه حتى يبدو مشروعك أجمل!!&lt;/p>
&lt;h2 id="السلوك-الافتراضي">السلوك الافتراضي&lt;/h2>
&lt;p>عندما نقوم بإنشاء مشروع Blazor ونقرر استخدامه مع الهوية، فإنه يعرض شيئًا مثل هذا:&lt;/p>
&lt;img align="center" src="https://i.imgur.com/2W8Oou9.png">
&lt;p>وعندما نحاول القيام بعملية تسجيل الدخول أو التسجيل، فإننا ننتقل إما &lt;code>/Identity/Account/Login&lt;/code> أو &lt;code>/Identity/Account/Register&lt;/code>.&lt;/p>
&lt;p>ولكن ماذا لو أخبرتك أنه يمكنك تحديث عناوين URL هذه بشكل كامل لتكون مختلفة؟&lt;/p>
&lt;h2 id="سقالات-صفحات-login--و-register">سقالات صفحات &lt;code>Login &lt;/code> و &lt;code>Register&lt;/code>&lt;/h2>
&lt;p>ومن أجل تحديث هذه الصفحات، تقوم Microsoft بإخفائها، ولكن يمكنك دعمها بسرعة وإجراء التغييرات التي تريدها!&lt;/p>
&lt;p>للقيام بذلك، عليك الانتقال إلى &lt;code>Add Scaffolded Item&lt;/code> في قائمة السياق الخاصة بالمشروع، تمامًا مثل هذا:&lt;/p>
&lt;img src="https://imgur.com/F3C4C9b.png">
&lt;p>بعد ذلك سينبثق شكل مشروط وعليك تحديد &lt;code>Identity&lt;/code> مرتين والنقر على &lt;code>Add&lt;/code>:&lt;/p>
&lt;img src="https://imgur.com/tZUqUlY.png">
&lt;p>بعد ذلك، سينبثق نموذج آخر، حيث يمكنك تحديد الصفحات من الهوية بأكملها التي تريد تحديثها. هناك الكثير من الصفحات التي يمكنك تحديثها، لكننا سنركز على &lt;code>Account/Login&lt;/code> و&lt;code>Account/Register&lt;/code>:&lt;/p>
&lt;img src="https://imgur.com/BS6ZLas.png">
&lt;p>الآن دع الأمر يعمل قليلاً ثم تحقق من Solution Explorer، ستجد بعض الملفات الجديدة:&lt;/p>
&lt;img src="https://imgur.com/5lWHLyI.png">
&lt;p>هذه الملفات الجديدة هي صفحة تسجيل الدخول والتسجيل التي يضيفها ASP.NET إلى مشروعك عندما تحدده لإضافة الهوية!&lt;/p>
&lt;h2 id="تحديث-عناوين-url">تحديث عناوين URL&lt;/h2>
&lt;p>كما لاحظت على الأرجح، هذه الملفات هي ملفات razor، نظرًا لأن امتدادها هو &lt;code>cshtml&lt;/code>، لذلك سنستخدم توجيهًا لتحديث عنوان url للصفحة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/login&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@model LoginModel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>] = &lt;span style="color:#a5d6ff">&amp;#34;Log in&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;@ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>]&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;row&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-4&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;account&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h2&amp;gt;Use a local account to log &lt;span style="color:#ff7b72">in&lt;/span>.&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div asp-validation-summary=&lt;span style="color:#a5d6ff">&amp;#34;ModelOnly&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span> role=&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;username&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;name@example.com&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt;Email&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;current-password&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;password&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt;Password&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;checkbox mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.RememberMe&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input class=&lt;span style="color:#a5d6ff">&amp;#34;form-check-input&amp;#34;&lt;/span> asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.RememberMe&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @Html.DisplayNameFor(m =&amp;gt; m.Input.RememberMe)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button id=&lt;span style="color:#a5d6ff">&amp;#34;login-submit&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;w-100 btn btn-lg btn-primary&amp;#34;&lt;/span>&amp;gt;Log &lt;span style="color:#ff7b72">in&lt;/span>&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a id=&lt;span style="color:#a5d6ff">&amp;#34;forgot-password&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ForgotPassword&amp;#34;&lt;/span>&amp;gt;Forgot your password?&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./Register&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span>&amp;gt;Register &lt;span style="color:#ff7b72">as&lt;/span> a &lt;span style="color:#ff7b72">new&lt;/span> user&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;a id=&lt;span style="color:#a5d6ff">&amp;#34;resend-confirmation&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ResendEmailConfirmation&amp;#34;&lt;/span>&amp;gt;Resend email confirmation&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-6 col-md-offset-2&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h3&amp;gt;Use another service to log &lt;span style="color:#ff7b72">in&lt;/span>.&amp;lt;/h3&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> ((Model.ExternalLogins?.Count ?? &lt;span style="color:#a5d6ff">0&lt;/span>) == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> There are no external authentication services configured. See &lt;span style="color:#ff7b72">this&lt;/span> &amp;lt;a href=&lt;span style="color:#a5d6ff">&amp;#34;https://go.microsoft.com/fwlink/?LinkID=532715&amp;#34;&lt;/span>&amp;gt;article
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> about setting up &lt;span style="color:#ff7b72">this&lt;/span> ASP.NET application to support logging &lt;span style="color:#ff7b72">in&lt;/span> via external services&amp;lt;/a&amp;gt;.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;external-account&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ExternalLogin&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-horizontal&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @foreach (&lt;span style="color:#ff7b72">var&lt;/span> provider &lt;span style="color:#ff7b72">in&lt;/span> Model.ExternalLogins!)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;provider&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;@provider.Name&amp;#34;&lt;/span> title=&lt;span style="color:#a5d6ff">&amp;#34;Log in using your @provider.DisplayName account&amp;#34;&lt;/span>&amp;gt;@provider.DisplayName&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@section Scripts {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#ff7b72">partial&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;_ValidationScriptsPartial&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كما ترون معظم الأشياء هي نفسها، ولكن إذا ألقيت نظرة على السطر الأول في الفصل، فقد قمت بتحديث ما كان لدينا من قبل من:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ل&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/login&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>حسناً، كان ذلك سهلاً، أليس كذلك؟ الآن دعونا نجري اختبارًا سريعًا لمعرفة ما إذا كان يعمل أم لا.&lt;/p>
&lt;p>أولاً، دعنا نذهب إلى الصفحة الافتراضية التي كانت لدينا في البداية، للتحقق مما إذا كانت لا تزال تعمل:&lt;/p>
&lt;img src="https://imgur.com/ngbRNaG.png">
&lt;p>وهو ما لا! فلنبدأ الآن ونختبر عنوان URL الجديد وهو &lt;code>/login&lt;/code>:&lt;/p>
&lt;img src="https://imgur.com/R067PnF.png">
&lt;p>ويعمل!!&lt;/p>
&lt;p>الآن دعونا نفعل الشيء نفسه بالنسبة للسجل، نقوم بتحديث صفحته وإضافة المسار الذي نريده في التوجيه &lt;code>@page&lt;/code> ودعنا نجري اختبارًا!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/register&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@model RegisterModel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>] = &lt;span style="color:#a5d6ff">&amp;#34;Register&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt;@ViewData[&lt;span style="color:#a5d6ff">&amp;#34;Title&amp;#34;&lt;/span>]&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;row&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-4&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;registerForm&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h2&amp;gt;Create a &lt;span style="color:#ff7b72">new&lt;/span> account.&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div asp-validation-summary=&lt;span style="color:#a5d6ff">&amp;#34;ModelOnly&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span> role=&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;username&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;name@example.com&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span>&amp;gt;Email&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Email&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;new-password&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;password&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span>&amp;gt;Password&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.Password&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;form-floating mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;input asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.ConfirmPassword&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> autocomplete=&lt;span style="color:#a5d6ff">&amp;#34;new-password&amp;#34;&lt;/span> aria-required=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span> placeholder=&lt;span style="color:#a5d6ff">&amp;#34;password&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;label asp-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.ConfirmPassword&amp;#34;&lt;/span>&amp;gt;Confirm Password&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span asp-validation-&lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;Input.ConfirmPassword&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;text-danger&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button id=&lt;span style="color:#a5d6ff">&amp;#34;registerSubmit&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;w-100 btn btn-lg btn-primary&amp;#34;&lt;/span>&amp;gt;Register&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;col-md-6 col-md-offset-2&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;h3&amp;gt;Use another service to register.&amp;lt;/h3&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;hr /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f85149">@&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> ((Model.ExternalLogins?.Count ?? &lt;span style="color:#a5d6ff">0&lt;/span>) == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> There are no external authentication services configured. See &lt;span style="color:#ff7b72">this&lt;/span> &amp;lt;a href=&lt;span style="color:#a5d6ff">&amp;#34;https://go.microsoft.com/fwlink/?LinkID=532715&amp;#34;&lt;/span>&amp;gt;article
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> about setting up &lt;span style="color:#ff7b72">this&lt;/span> ASP.NET application to support logging &lt;span style="color:#ff7b72">in&lt;/span> via external services&amp;lt;/a&amp;gt;.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;form id=&lt;span style="color:#a5d6ff">&amp;#34;external-account&amp;#34;&lt;/span> asp-page=&lt;span style="color:#a5d6ff">&amp;#34;./ExternalLogin&amp;#34;&lt;/span> asp-route-returnUrl=&lt;span style="color:#a5d6ff">&amp;#34;@Model.ReturnUrl&amp;#34;&lt;/span> method=&lt;span style="color:#a5d6ff">&amp;#34;post&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-horizontal&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @foreach (&lt;span style="color:#ff7b72">var&lt;/span> provider &lt;span style="color:#ff7b72">in&lt;/span> Model.ExternalLogins!)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;button type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;provider&amp;#34;&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;@provider.Name&amp;#34;&lt;/span> title=&lt;span style="color:#a5d6ff">&amp;#34;Log in using your @provider.DisplayName account&amp;#34;&lt;/span>&amp;gt;@provider.DisplayName&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/form&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/section&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@section Scripts {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#ff7b72">partial&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;_ValidationScriptsPartial&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لقد قمت بتحديث الجزء العلوي بـ &lt;code>@page &amp;quot;/login&amp;quot;&lt;/code> والآن نختبر ما إذا كان يعمل:&lt;/p>
&lt;img src="https://imgur.com/M7KakaF.png">
&lt;p>يعمل أيضا!!&lt;/p>
&lt;h2 id="هذا-كل-شيءأتمنى-أن-تكون-قد-تعلمت-كيفية-تحديث-عناوين-url-هذه-ويرجع-ذلك-في-الغالب-إلى-أنه-في-بعض-المشاريع-عندما-تقوم-بعمل-عناوين-url-بطريقة-معينة-ثم-تبدو-الهوية-مختلفة-فهذا-أمر-سيئ-للغاية">هذا كل شيءأتمنى أن تكون قد تعلمت كيفية تحديث عناوين url هذه، ويرجع ذلك في الغالب إلى أنه في بعض المشاريع عندما تقوم بعمل عناوين url بطريقة معينة ثم تبدو الهوية مختلفة، فهذا أمر سيئ للغاية!&lt;/h2>
&lt;p>إذا كنت بحاجة إلى أي شيء، فقط قم بالتغريد علي أو أرسل لي بريدًا إلكترونيًا وسأحاول تقديم المساعدة!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>السمات المخصصة على .NET 6 Core API</title><link>https://emimontesdeoca.github.io/ar/posts/custom-attributes-net-6-core-api/</link><pubDate>Fri, 09 Dec 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/custom-attributes-net-6-core-api/</guid><description>قم بإنشاء فئات ActionFilterAttribute مخصصة للتحقق من صحة رؤوس الطلب في .NET 6 Core APIs.</description><content:encoded>&lt;p>تعد السمات المخصصة أمرًا جيدًا حقًا للاستخدام، وقد بدأت استخدامها مؤخرًا جدًا، لأنها تسمح لي بإنشاء واحدة منها وإعادة استخدامها إما على وحدة التحكم أو الفصل أو الطريقة نفسها.&lt;/p>
&lt;p>إنها تساعد حقًا عندما تريد القيام ببعض الأشياء الأمنية مثل التحقق من الرؤوس، أو التحقق من قيمة المعلمة التي تحتاجها بالتأكيد.&lt;/p>
&lt;p>في حالتي، سنستخدمه في مشروع .NET Core API، حيث سنتحقق مما إذا كان كل الطلب يحتوي على رأس معين.&lt;/p>
&lt;h1 id="سمة-التحقق-من-الرأس">سمة التحقق من الرأس&lt;/h1>
&lt;p>لذا، بعد أن أنشأنا واجهة برمجة تطبيقات .NET Core الرائعة، فلنقم بإنشاء مجلد لتخزين الأشياء الخاصة بنا، لأننا نحب استخدام المجلدات.&lt;/p>
&lt;img src="https://i.imgur.com/i2VKbZN.png"/>
&lt;p>وبعد ذلك سنقوم بإضافة المنطق إلى فئة &lt;code>HeaderCheckAttribute&lt;/code> الخاصة بنا.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc.Filters&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">DotNet6CustomAttribute.Attributes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">HeaderCheckAttribute&lt;/span> : ActionFilterAttribute
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnActionExecuting(ActionExecutingContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Get all headers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> headers = context.HttpContext.Request.Headers;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Check if headers has x-dotnet-6-custom-attribute&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (!headers.ContainsKey(&lt;span style="color:#a5d6ff">&amp;#34;x-dotnet-6-custom-attribute&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Result = &lt;span style="color:#ff7b72">new&lt;/span> BadRequestObjectResult(&lt;span style="color:#a5d6ff">&amp;#34;The header x-dotnet-6-custom-attribute is missing&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span> &lt;span style="color:#ff7b72">if&lt;/span> (&lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(headers[&lt;span style="color:#a5d6ff">&amp;#34;x-dotnet-6-custom-attribute&amp;#34;&lt;/span>]))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.Result = &lt;span style="color:#ff7b72">new&lt;/span> BadRequestObjectResult(&lt;span style="color:#a5d6ff">&amp;#34;The header x-dotnet-6-custom-attribute can&amp;#39;t be null or empty&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">base&lt;/span>.OnActionExecuting(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>المنطق الأساسي هو، أولاً يتحقق من وجود رأس باستخدام المفتاح &lt;code>x-dotnet-6-custom-attribute&lt;/code> وإذا كان موجودًا، فإنه يتحقق مما إذا كان يحتوي على قيم.&lt;/p>
&lt;p>إذا كان كلا التعبيرين صحيحين، فسيتم إعادة &lt;code>BadRequestObjectResult&lt;/code> برسالة معينة.&lt;/p>
&lt;h1 id="إضافته-إلى-وحدة-التحكم">إضافته إلى وحدة التحكم&lt;/h1>
&lt;p>يمكننا إضافة هذا المنطق إلى أماكن متعددة، يمكننا إضافته مباشرة إلى وحدة التحكم بأكملها، أو يمكننا إضافته إلى بعض الطرق، سنقوم بإضافته أولاً إلى الطرق ثم إلى وحدة التحكم بأكملها.&lt;/p>
&lt;p>لذلك دعونا نزين الفصل &lt;code>WeatherForecastController&lt;/code> معهم.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">DotNet6CustomAttribute.Attributes&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Mvc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">DotNet6CustomAttribute.Controllers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ApiController]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;[controller]&lt;span style="color:#a5d6ff">&amp;#34;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">&lt;/span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">WeatherForecastController&lt;/span> : ControllerBase
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] Summaries = &lt;span style="color:#ff7b72">new&lt;/span>[]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;Freezing&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Bracing&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Chilly&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Cool&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Mild&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Warm&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Balmy&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Hot&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Sweltering&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Scorching&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> ILogger&amp;lt;WeatherForecastController&amp;gt; _logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> WeatherForecastController(ILogger&amp;lt;WeatherForecastController&amp;gt; logger)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _logger = logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWeatherForecastWithCheck&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HeaderCheckAttribute]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> IEnumerable&amp;lt;WeatherForecast&amp;gt; GetWithCheck()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Enumerable.Range(&lt;span style="color:#a5d6ff">1&lt;/span>, &lt;span style="color:#a5d6ff">5&lt;/span>).Select(index =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> WeatherForecast
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = DateTime.Now.AddDays(index),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TemperatureC = Random.Shared.Next(-&lt;span style="color:#a5d6ff">20&lt;/span>, &lt;span style="color:#a5d6ff">55&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Summary = Summaries[Random.Shared.Next(Summaries.Length)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToArray();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [HttpGet]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Route(&amp;#34;GetWeatherForecastWithoutCheck&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> IEnumerable&amp;lt;WeatherForecast&amp;gt; GetWithoutCheck()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Enumerable.Range(&lt;span style="color:#a5d6ff">1&lt;/span>, &lt;span style="color:#a5d6ff">5&lt;/span>).Select(index =&amp;gt; &lt;span style="color:#ff7b72">new&lt;/span> WeatherForecast
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Date = DateTime.Now.AddDays(index),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TemperatureC = Random.Shared.Next(-&lt;span style="color:#a5d6ff">20&lt;/span>, &lt;span style="color:#a5d6ff">55&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Summary = Summaries[Random.Shared.Next(Summaries.Length)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToArray();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>دعونا تشغيل المشروع!&lt;/p>
&lt;img src="https://i.imgur.com/ZvO4LnE.png"/>
&lt;p>لدينا وظيفتان هناك: &lt;code>GetWeatherForecastWithCheck&lt;/code> و&lt;code>GetWeatherForecastWithoutCheck&lt;/code>، إحداهما ستفشل والبعض الآخر لن يفشل، ولكن دعونا نتحقق من ذلك على Swagger!&lt;/p>
&lt;img src="https://i.imgur.com/x1yRb9j.png"/>
&lt;img src="https://i.imgur.com/XiO4GC9.png">
&lt;p>كما ترون، تقوم إحدى الإرجاعات بإرجاع خطأ 400 في رسالتنا، بينما تقوم الأخرى بإرجاع القيم، والآن لاختبار ذلك بشكل كامل، فلنقم بتشغيل Postman وإضافة رأس حتى نتمكن أيضًا من رؤية البيانات باستخدام &lt;code>GetWeatherForecastWithCheck&lt;/code>.&lt;/p>
&lt;h1 id="ساعي-البريد">ساعي البريد&lt;/h1>
&lt;p>الآن قيد التشغيل على Postman، نضيف الرأس ونرى أن رسالة الخطأ قد تغيرت، منذ الآن نقوم بتوفير الرأس ولكن ليس هناك قيمة له&lt;/p>
&lt;img src="https://i.imgur.com/JHP8ZXZ.png"/>
&lt;p>إذا أضفنا قيمة إليها، فسنحصل في النهاية على القيم!&lt;/p>
&lt;img src="https://i.imgur.com/jxt6xFe.png"/>
&lt;h1 id="هذا-كل-شيء">هذا كل شيء&lt;/h1>
&lt;p>حسنا هذا كل شيء! حق بسيط جدا؟ حسنًا، أنت تعرف الآن كيفية إنشاء سمة وتخصيصها للطرق ووحدات التحكم!&lt;/p>
&lt;p>استمتع معهم!&lt;/p>
&lt;h1 id="الكود">الكود&lt;/h1>
&lt;p>هذا المشروع بأكمله موجود على Github ويمكنك العثور عليه &lt;a href="https://github.com/emimontesdeoca/dotnet-6-attribute-post">هنا&lt;/a>!&lt;/p>
&lt;p>إذا كانت لديك أي مشاكل أو أسئلة، فلا تتردد في الاتصال بي على أي من وسائل التواصل الاجتماعي على @emimontesdeoca (في تويتر هو في الواقع &lt;code>@emimontesdeocaa&lt;/code> مع اثنين من &lt;code>aa&lt;/code> في النهاية). يمكنك أيضًا العثور على معظم وسائل التواصل الاجتماعي الخاصة بي على رأس المدونة.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! سيا!&lt;/p></content:encoded><category>.NET</category><category>API</category></item><item><title>التعامل مع مكونات التحميل في Blazor</title><link>https://emimontesdeoca.github.io/ar/posts/loading-component-blazor/</link><pubDate>Tue, 19 Jul 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/loading-component-blazor/</guid><description>قم ببناء مكون غلاف دوار للتحميل قابل لإعادة الاستخدام في Blazor باستخدام RenderFragment وChildContent.</description><content:encoded>&lt;p>﻿Blazor رائع، رائع حقًا، خاصة عندما نقوم بأشياء غير متزامنة مثل صناديق التحميل ويبدو الأمر جيدًا.&lt;/p>
&lt;p>لقد كنت أحاول القيام بطرق متعددة حول كيفية التعامل مع تحميل الصفحات والحالات والمكونات وما إلى ذلك. وأعتقد أنني وجدت أخيرًا الطريقة المثالية للقيام بذلك بالطريقة التي أريدها.&lt;/p>
&lt;p>#فكرة&lt;/p>
&lt;p>بدلاً من إعادة كتابة منطق التحميل على كل صفحة أو مكون، نقوم ببناء المكون الأصلي باستخدام &lt;code>ChildComponent&lt;/code>، وهذا سيمنحنا التغيير لإعادة الاستخدام عدة مرات فقط.&lt;/p>
&lt;h1 id="رمز-تحميل-المكون">رمز تحميل المكون&lt;/h1>
&lt;p>الكود بسيط جدًا، ليس هناك الكثير مما يجب فعله، &lt;code>if&lt;/code> أساسي مع خاصية التحميل، ووظيفة تبديل بالداخل، وقد انتهى الأمر!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@if (_loaded)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;text-center&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;spinner-border&amp;#34;&lt;/span> role=&lt;span style="color:#a5d6ff">&amp;#34;status&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span class=&lt;span style="color:#a5d6ff">&amp;#34;sr-only&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @ChildContent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> _loaded = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [Parameter]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> RenderFragment? ChildContent { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ToggleLoad(&lt;span style="color:#ff7b72">bool&lt;/span> state)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded = state;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="الاستخدام">الاستخدام&lt;/h1>
&lt;p>الاستخدام بسيط جدًا، ومن أجل الاختبار، سنستخدم صفحة جديدة ونضع المحتوى الخاص بنا داخل مكون &lt;code>LoadingComponent&lt;/code> الذي أنشأناه للتو.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/loading&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using LoadingBoxes.Components
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;LoadingBoxes.Components.LoadingComponent @ref=loadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> This &lt;span style="color:#ff7b72">is&lt;/span> some content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/LoadingBoxes.Components.LoadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> LoadingComponent loadingComponent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// We are going to simulate some load&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(&lt;span style="color:#a5d6ff">5000&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loadingComponent.ToggleLoad(&lt;span style="color:#79c0ff">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذه هي الطريقة التي تبدو&lt;/p>
&lt;img src="https://i.gyazo.com/cb892d796f396d43d5c54e30e1e87568.gif"/>
&lt;h1 id="المزيد-من-الأمثلة-المضحكة">المزيد من الأمثلة المضحكة&lt;/h1>
&lt;p>لنفترض أن لدينا مكونات متعددة لكل منها أوقات تحميل خاصة به، يمكننا بناء شيء يبدو جيدًا بناءً على ذلك!&lt;/p>
&lt;p>لنقم بإنشاء مكون تحميل مزيف يمكننا إعادة استخدامه يسمى &lt;code>FakeLoadingComponent&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@using LoadingBoxes.Components
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;LoadingBoxes.Components.LoadingComponent @ref=loadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> It took @ellapsedTime seconds!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/LoadingBoxes.Components.LoadingComponent&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> LoadingComponent loadingComponent = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> ellapsedTime = &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> Random rnd = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">protected&lt;/span> &lt;span style="color:#ff7b72">override&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task OnInitializedAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> random = rnd.Next(&lt;span style="color:#a5d6ff">0&lt;/span>, &lt;span style="color:#a5d6ff">5000&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// We are going to simulate some load&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.Delay(random);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ellapsedTime = random/ &lt;span style="color:#a5d6ff">1000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loadingComponent.ToggleLoad(&lt;span style="color:#79c0ff">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ثم نقوم فقط بتحديث صفحة &lt;code>Loading&lt;/code> بمكونات &lt;code>FakeLoadingComponent&lt;/code> المتعددة والتحقق من النتيجة !!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/loading&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using LoadingBoxes.Components
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;FakeLoadingComponent/&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن يبدو هذا أفضل بكثير&lt;/p>
&lt;img src="https://i.gyazo.com/e427ca31af410314762446979d1c3739.gif">
&lt;p>وهذا كل شيء!&lt;/p>
&lt;p>إذا كانت لديك أي مشاكل أو أسئلة، فلا تتردد في الاتصال بي على أي وسيلة تواصل اجتماعية على @emimontesdeoca (في تويتر هو في الواقع &lt;code>@emimontesdeocaa&lt;/code> مع اثنين من &lt;code>aa&lt;/code> في النهاية). يمكنك أيضًا العثور على معظم وسائل التواصل الاجتماعي الخاصة بي على رأس المدونة.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب!&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>الحصول على تاريخ انتهاء الصلاحية من الشهادة</title><link>https://emimontesdeoca.github.io/ar/posts/expiration-date-certificate/</link><pubDate>Fri, 27 May 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/expiration-date-certificate/</guid><description>يمكنك استرداد تواريخ انتهاء صلاحية شهادة SSL برمجيًا باستخدام C# HttpClient وX509Certificate2.</description><content:encoded>&lt;p>﻿هناك عدد قليل من تطبيقات الويب التي تساعدك على التحقق من الحالة الحالية للشهادات من النطاقات التي نستخدمها، ولدينا عدد قليل منها.&lt;/p>
&lt;p>لقد قمت بإنشاء وظيفة Azure التي تعمل مرة واحدة يوميًا وتتحقق من عدد قليل من المجالات التي أحتاج إلى إلقاء نظرة عليها، وهي عبارة عن تطبيق وحدة تحكم بسيط للغاية، فإذا كان تاريخ انتهاء صلاحية الشهادة أقل من 30 يومًا، فسوف يرسل بريدًا إلكترونيًا.&lt;/p>
&lt;p>لن يتم عرض المنطق نفسه لكيفية عمل الوظيفة، أود أن أعرض الوظيفة التي تقوم بالفحص الأساسي للشهادة والبيانات التي نحصل عليها.&lt;/p>
&lt;h2 id="الوظيفة">الوظيفة&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;X509Certificate2&amp;gt; CheckCertificateAsync(&lt;span style="color:#ff7b72">string&lt;/span> urlPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpClientHandler = &lt;span style="color:#ff7b72">new&lt;/span> HttpClientHandler
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ServerCertificateCustomValidationCallback = (request, cert, chain, policyErrors) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2(cert);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HttpClient&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient(httpClientHandler);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> httpClient.SendAsync(&lt;span style="color:#ff7b72">new&lt;/span> HttpRequestMessage(HttpMethod.Head, urlPath));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> certificate;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذه الطريقة &lt;code>CheckCertificateAsync&lt;/code> ستعيد إلينا شهادة &lt;code>X509Certificate2&lt;/code> التي ستتيح لنا القيام بمجموعة من الأشياء، بما في ذلك إلقاء نظرة على تاريخ انتهاء الصلاحية.&lt;/p>
&lt;h2 id="نتيجة-متسلسلة">نتيجة متسلسلة&lt;/h2>
&lt;p>هذه هي القيمة التسلسلية للكائن &lt;code>certificate&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Archived&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Extensions&amp;#34;&lt;/span>:[
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyUsages&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">160&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.15&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Key Usage&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;AwIFoA==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;EnhancedKeyUsages&amp;#34;&lt;/span>:[
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.5.5.7.3.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Server Authentication&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.5.5.7.3.2&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Client Authentication&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.37&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Enhanced Key Usage&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MBQGCCsGAQUFBwMBBggrBgEFBQcDAg==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;CertificateAuthority&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;HasPathLengthConstraint&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;PathLengthConstraint&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.19&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Basic Constraints&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MAA=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SubjectKeyIdentifier&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;634E1585565AA49402C21642A4A5979A38025797&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.14&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Subject Key Identifier&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;BBRjThWFVlqklALCFkKkpZeaOAJXlw==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.35&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Authority Key Identifier&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MBaAFBQusxe3WFbLrlAJQOYfr52LFMLG&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.5.5.7.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Authority Information Access&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MEcwIQYIKwYBBQUHMAGGFWh0dHA6Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iub3JnLw==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.17&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Subject Alternative Name&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MB6CHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2.5.29.32&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Certificate Policies&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Critical&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.3.6.1.4.1.11129.2.4.2&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;SCT List&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;BIH0APIAdwBByMqx3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAYCeJIwYAAAEAwBIMEYCIQCG8sf4iBitUjNCc1dsxVd5mdRQCKapRqqnTHKxSKHjHgIhAJFGNXEZkCHKygT1T7bE4orpd6p2l1+GmifMEIuRsgHbAHcARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAGAniSMNgAABAMASDBGAiEAoxv1LBn/vfyR7s67kRLB/n1tq3eicuA/8/V0S2YzQCYCIQDXaS3FZbdIVNxQvKxPFxM1awBO/sGxBXafz0lspOoWSA==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;HasPrivateKey&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;PrivateKey&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IssuerName&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Name&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=R3, O=Let&amp;#39;s Encrypt, C=US&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSMw==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;NotAfter&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2022-08-05T10:50:35+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;NotBefore&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;2022-05-07T10:50:36+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;PublicKey&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;EncodedKeyValue&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MIIBCgKCAQEAq8cbDO3GAfjqqbPPCBdPost8NMRmEubv85gXecll7mZMH5qSfTPuB/ouFWL3tPMf1U8usWeoSUK/48yatzBGwmj1KKlkaW9MS2QkydztRp+kH8LvbzbQvGknuOLWGHBALLT17o/3DYxuA5LnXdY+vLvJWygQoFr2N/XhnhUjcm6OaQEJpIykydfbBQGQSEuQIIw4egpgdHkYJjCOYAsXuSSggN8/FADTCec0RzVjfFTSoJ3hV9HLE9M8MCSXjuo0AJ/MbAxq91S8XmDcRjHCCd7Zw+NjHo8cxZCQ6NqGvn3xwx8ahmmbC+CyDEcIyJJZK2Yv+qE4oS8QZfaX/RaHMwIDAQAB&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;EncodedParameters&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;BQA=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Key&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Key&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Algorithm&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Algorithm&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;AlgorithmGroup&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;AlgorithmGroup&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ExportPolicy&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Handle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsInvalid&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsClosed&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsEphemeral&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsMachineKey&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeySize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">2048&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyUsage&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">16777215&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ParentWindowHandle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Provider&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Provider&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;Microsoft Software Key Storage Provider&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ProviderHandle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsInvalid&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;IsClosed&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">false&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;UIPolicy&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;ProtectionLevel&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Description&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;UseContext&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;CreationTitle&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;UniqueName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;LegalKeySizes&amp;#34;&lt;/span>:[
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;MinSize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">512&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;MaxSize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">16384&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SkipSize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">64&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeyExchangeAlgorithm&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SignatureAlgorithm&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;KeySize&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MIIFQDCCBCigAwIBAgISBNwTmwP/RTcrEeIgAdMrpaFtMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSMzAeFw0yMjA1MDcwOTUwMzZaFw0yMjA4MDUwOTUwMzVaMCcxJTAjBgNVBAMTHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrxxsM7cYB+Oqps88IF0+iy3w0xGYS5u/zmBd5yWXuZkwfmpJ9M+4H+i4VYve08x/VTy6xZ6hJQr/jzJq3MEbCaPUoqWRpb0xLZCTJ3O1Gn6Qfwu9vNtC8aSe44tYYcEAstPXuj/cNjG4Dkudd1j68u8lbKBCgWvY39eGeFSNybo5pAQmkjKTJ19sFAZBIS5AgjDh6CmB0eRgmMI5gCxe5JKCA3z8UANMJ5zRHNWN8VNKgneFX0csT0zwwJJeO6jQAn8xsDGr3VLxeYNxGMcIJ3tnD42MejxzFkJDo2oa+ffHDHxqGaZsL4LIMRwjIklkrZi/6oTihLxBl9pf9FoczAgMBAAGjggJZMIICVTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFGNOFYVWWqSUAsIWQqSll5o4AleXMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMCcGA1UdEQQgMB6CHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEGBgorBgEEAdZ5AgQCBIH3BIH0APIAdwBByMqx3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAYCeJIwYAAAEAwBIMEYCIQCG8sf4iBitUjNCc1dsxVd5mdRQCKapRqqnTHKxSKHjHgIhAJFGNXEZkCHKygT1T7bE4orpd6p2l1+GmifMEIuRsgHbAHcARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAGAniSMNgAABAMASDBGAiEAoxv1LBn/vfyR7s67kRLB/n1tq3eicuA/8/V0S2YzQCYCIQDXaS3FZbdIVNxQvKxPFxM1awBO/sGxBXafz0lspOoWSDANBgkqhkiG9w0BAQsFAAOCAQEAjSEID5MWonbSiyHbmPYWO8ImCCOjkLGxgY8WJODbrWxFy+xU44UwrWOCkqYZUlv2LRmPqSyZDrIeeHK9VMbGh71oXX+XovikgAr6PpI0Mp897nPWj0XvOBaSYG0s+f+CXMtyt0tWCsQOcl+iT82+Ja71f8gbVL6l7xESewEE78pTKEH8EqD22r8VSD7FNICD8EYQr13v3AuVWObSU/R8Td6SrSVEknw1HgJS4e9nvmrMxBGKOJ+aWrAGiUydehg8M9o2gbGckMhz6D7cwB5l618cYaXKkW1dEOYZHl++qUj1/VPK+FNkiDZOPVNN//PbZuOLwAUIlZvhqGWX5/9PBg==&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SerialNumber&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;04DC139B03FF45372B11E22001D32BA5A16D&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SignatureAlgorithm&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;1.2.840.113549.1.1.11&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;sha256RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;SubjectName&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Name&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=blog.emilianomontesdeoca.com&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Oid&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Value&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;FriendlyName&amp;#34;&lt;/span>:&lt;span style="color:#79c0ff">null&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;RawData&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;MCcxJTAjBgNVBAMTHGJsb2cuZW1pbGlhbm9tb250ZXNkZW9jYS5jb20=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Thumbprint&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;28CF960F772ABFF22AA193C291492C27F8E13D4D&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Version&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">3&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Handle&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;value&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">2658150705632&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Issuer&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=R3, O=Let&amp;#39;s Encrypt, C=US&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;Subject&amp;#34;&lt;/span>:&lt;span style="color:#a5d6ff">&amp;#34;CN=blog.emilianomontesdeoca.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="تاريخ-انتهاء-الصلاحية">تاريخ انتهاء الصلاحية&lt;/h2>
&lt;p>لمعرفة وقت انتهاء الصلاحية، نحتاج إلى إلقاء نظرة على &lt;code>NotAfter&lt;/code> و &lt;code>NotBefore&lt;/code>، الموجودين داخل هذا الكائن:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;NotAfter&amp;#34;&lt;/span>&lt;span style="color:#f85149">:&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;2022-08-05T10:50:35+01:00&amp;#34;&lt;/span>&lt;span style="color:#f85149">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;NotBefore&amp;#34;&lt;/span>&lt;span style="color:#f85149">:&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;2022-05-07T10:50:36+01:00&amp;#34;&lt;/span>&lt;span style="color:#f85149">,&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="تطبيق-وحدة-التحكم">تطبيق وحدة التحكم&lt;/h2>
&lt;p>المقتطف التالي عبارة عن تطبيق وحدة تحكم بسيط مبني على .NET 6، والذي سيؤدي إلى النتيجة التالية، حيث يمكنك التحقق من أي من الشهادات التي تريدها:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Newtonsoft.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Net&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Security.Cryptography.X509Certificates&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text.Json&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> url = &lt;span style="color:#a5d6ff">&amp;#34;https://blog.emilianomontesdeoca.com/&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task&amp;lt;X509Certificate2&amp;gt; CheckCertificateAsync(&lt;span style="color:#ff7b72">string&lt;/span> urlPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> httpClientHandler = &lt;span style="color:#ff7b72">new&lt;/span> HttpClientHandler
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ServerCertificateCustomValidationCallback = (request, cert, chain, policyErrors) =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certificate = &lt;span style="color:#ff7b72">new&lt;/span> X509Certificate2(cert);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">HttpClient&lt;/span> httpClient = &lt;span style="color:#ff7b72">new&lt;/span> HttpClient(httpClientHandler);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> httpClient.SendAsync(&lt;span style="color:#ff7b72">new&lt;/span> HttpRequestMessage(HttpMethod.Head, urlPath));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> certificate;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> cert = &lt;span style="color:#ff7b72">await&lt;/span> CheckCertificateAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> serializedValue = JsonConvert.SerializeObject(cert);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.WriteLine(serializedValue);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Console.ReadLine();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="المشروع-التجريبي">المشروع التجريبي&lt;/h2>
&lt;p>يمكنك العثور على تطبيق وحدة التحكم في موقع Github الخاص بي، في المستودع المسمى &lt;a href="https://github.com/emimontesdeoca/expiration-date-certificate">شهادة تاريخ انتهاء الصلاحية&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>Azure</category></item><item><title>تركيز عنصر في Blazor</title><link>https://emimontesdeoca.github.io/ar/posts/focus-element-blazor/</link><pubDate>Thu, 05 May 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/focus-element-blazor/</guid><description>قم بتعيين التركيز على عناصر HTML في مكونات Blazor باستخدام JavaScript Interop ومراجع العناصر.</description><content:encoded>&lt;p>﻿بعد العمل على لعبة Wordlzor التي قمت بها مؤخرًا، كنت بحاجة إلى إضافة وظيفة بسيطة جدًا: التركيز على اللعبة بأكملها عند الدخول.&lt;/p>
&lt;p>يجب القيام بذلك لأنه يمكن للمستخدم الكتابة فعليًا في اللعبة وليس فقط استخدام لوحة المفاتيح التي تظهر على الشاشة.&lt;/p>
&lt;h2 id="ملف-جافا-سكريبت">ملف جافا سكريبت&lt;/h2>
&lt;p>من أجل القيام بذلك، علينا إنشاء ملف جافا سكريبت يسمى &lt;code>app.js&lt;/code> الذي سيحتوي على وظيفة&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>window.FocusElement &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> (element) =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> element.focus();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد أن قمنا بذلك، نحتاج إلى تضمين البرنامج النصي في ملف &lt;code>index.html&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">script&lt;/span> src&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;js/app.js&amp;#34;&lt;/span>&amp;gt;&amp;lt;/&lt;span style="color:#7ee787">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="مكون-البلازور">مكون البلازور&lt;/h2>
&lt;p>بمجرد تهيئة البرنامج النصي، نحتاج إلى العثور على عنصر للتركيز عليه، لذلك في أي من مكوناتنا، نحتاج إلى إحالة هذا العنصر إلى كائن.&lt;/p>
&lt;p>لذلك، في مكون blazor الخاص بنا، دعنا نضيف div مع &lt;code> خاصية @ref&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#7ee787">div&lt;/span> &lt;span style="color:#f85149">@&lt;/span>ref&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">@elementToFocus&lt;/span> tabindex&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> &amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> This is my focus component
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#7ee787">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد ذلك، في منطق المكون، يمكنك الحصول على الخاصية كـ &lt;code>ElementReference&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Reference to element&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> ElementReference elementToFocus;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="حقن-jsinterop">حقن JSInterop&lt;/h2>
&lt;p>الآن، من أجل استدعاء الوظيفة الموجودة في ملف Javascript لدينا، نحتاج إلى استخدام &lt;code>JSInterop&lt;/code>.&lt;/p>
&lt;p>بادئ ذي بدء، يجب علينا حقنه في المكون بالصيغة التالية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[Inject]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> IJSRuntime JSRuntime { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد إدخال الخدمة، أصبحنا الآن قادرين على استدعاء أي من التوابع الموجودة بها، مثل &lt;code>InvokeVoidAsync&lt;/code>، والتي ستستدعي الدالة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task Focus()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Focus when initializing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JSRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;window.FocusElement&amp;#34;&lt;/span>, elementToFocus);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وعندما تستدعي funciton &lt;code>Focus()&lt;/code>، فإنه سيركز على العناصر التي أنشأناها، ومن الواضح أنه يمكنك إعادة بناء العنصر نفسه وتمريره كمعلمة.&lt;/p>
&lt;p>إذا كنت تريد إلقاء نظرة على كيفية تنفيذ ذلك، فقم بإلقاء نظرة على كود المصدر &lt;a href="https://github.com/emimontesdeoca/Wordlzor">Wordlzor&lt;/a>، الذي استخدمته للتنبيهات والتركيز عند إغلاق نموذج التعليمات.&lt;/p></content:encoded><category>.NET</category><category>Blazor</category></item><item><title>قم بإنشاء ملفات *.dacpac من مشروع قاعدة بيانات VS في إجراءات GitHub</title><link>https://emimontesdeoca.github.io/ar/posts/generate-dacpacs-github-actions/</link><pubDate>Mon, 18 Apr 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/generate-dacpacs-github-actions/</guid><description>أتمتة إنشاء ملفات dacpac من مشاريع قاعدة بيانات Visual Studio باستخدام خطوط أنابيب GitHub Actions.</description><content:encoded>&lt;p>﻿إذا كنت بحاجة إلى العمل مع قواعد البيانات وترغب في القيام بالتطوير السريع، فيجب أن يكون لديك تدفق حيث كلما قمت بإضافة أو تعديل جدول أو وظيفة SP، فإنه سيتم تجميع ذلك وإنشاء ملف النشر لقاعدة البيانات.&lt;/p>
&lt;p>لقد كنت أقوم بهذا التطبيق منذ فترة طويلة الآن، فنحن نقوم بالتغييرات على المستوى المحلي، ونقارنها بمشروع قاعدة البيانات الخاصة بنا، وبعد ذلك عندما نلتزم بالتغييرات وندفعها، يقومون بإنشاء ملف bacpac الذي سينتقل إلى قاعدة البيانات.&lt;/p>
&lt;p>إنه يزيل كل المتاعب التي تواجهك عند القيام بذلك يدويًا، مما قد يتسبب في حدوث خطأ بشري وفي قاعدة البيانات، وهذا خبر سيئ حقًا.&lt;/p>
&lt;p>#مشروع قاعدة البيانات&lt;/p>
&lt;p>عندما نقوم بإنشاء مشروع قاعدة البيانات الخاص بنا على Visual Studio ونقوم باستيراد قاعدة بيانات، فسوف ينتهي الأمر بهذا الشكل&lt;/p>
&lt;img src="https://i.gyazo.com/c0e11b14c707db66b8dbb591031cc527.png" />
&lt;p>عندما نقوم بتشغيل تجميع في هذا المشروع، فإنه سيتم إنشاء ملف &lt;code>dacpac&lt;/code> الذي سيتضمن كل بنيتنا&lt;/p>
&lt;img src="https://i.gyazo.com/c883f3e329c7deb033564f7b5e9be7d4.png" />
&lt;h1 id="خط-أنابيب-جيثب">خط أنابيب جيثب&lt;/h1>
&lt;p>الآن بعد أن تم نشر الكود الخاص بنا في المستودع، نحتاج الآن إلى إنشاء إجراء من شأنه تجميع هذا المشروع وإنشاء ملف &lt;code>dacpac&lt;/code> ووضعه في مكان ما حيث يمكننا تنزيله أو استخدامه لخطوة أخرى.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">DacpacGithubActions project build&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">on&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">push&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">branches&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">main&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">jobs&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">build&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">runs-on&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">windows-latest&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">steps&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/checkout@v2&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Setup MSBuild&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">microsoft/setup-msbuild@v1&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Navigate to Workspace&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">cd $GITHUB_WORKSPACE&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Create Build Directory&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">mkdir artifacts&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Build Solution&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">run&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>|&lt;span style="color:#a5d6ff">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> msbuild.exe /t:DacpacGithubActions /p:DebugSymbols=false /p:DebugType=None /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:OutDir=&amp;#34;../artifacts&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff"> &lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">Upload artifact&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">uses&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">actions/upload-artifact@v1.0.0&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">with&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">DacpacGithubActionsArtifacts&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">path&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;../artifacts&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>سيفعل هذا الإجراء مجموعة من الأشياء، ويبني الحل ويضع النتيجة في مجلد &lt;code>artifacts&lt;/code>، الذي تم إنشاؤه مسبقًا، ثم سيقوم بتحميل الملفات من هذا المجلد إلى القطع الأثرية.&lt;/p>
&lt;h1 id="تشغيل-خط-الأنابيب">تشغيل خط الأنابيب&lt;/h1>
&lt;p>الآن قم ببدء البناء، يجب أن تكون النتيجة ما يلي&lt;/p>
&lt;img src="https://i.gyazo.com/9fe0b7a44be0f07bcc41fe0862183a54.png" />
&lt;p>إذا قمنا بالفعل بتنزيل القطعة الأثرية وإلقاء نظرة على المحتوى، فهذا ما نحتاجه للمستقبل، عندما ننفذ خطوة نشر مستمرة، ملف &lt;code>dacpac&lt;/code>!&lt;/p>
&lt;img src="https://i.gyazo.com/287a392df0c5e966958262644e335149.png" />
&lt;h1 id="الكود">الكود&lt;/h1>
&lt;p>هذا المشروع بأكمله موجود على Github ويمكنك العثور عليه &lt;a href="https://github.com/emimontesdeoca/dacpac-github-actions">هنا&lt;/a>!&lt;/p>
&lt;p>إذا كانت لديك أي مشاكل أو أسئلة، فلا تتردد في الاتصال بي على أي من وسائل التواصل الاجتماعي على @emimontesdeoca (في تويتر هو في الواقع &lt;code>@emimontesdeocaa&lt;/code> مع اثنين من &lt;code>aa&lt;/code> في النهاية). يمكنك أيضًا العثور على معظم وسائل التواصل الاجتماعي الخاصة بي على رأس المدونة.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! سيا!&lt;/p></content:encoded><category>CI/CD</category></item><item><title>تبديل السمات باستخدام Javascript Interop في Blazor</title><link>https://emimontesdeoca.github.io/ar/posts/blazor-toggle-darkmode/</link><pubDate>Fri, 01 Apr 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/blazor-toggle-darkmode/</guid><description>قم بتنفيذ تبديل السمات الفاتحة والداكنة في Blazor باستخدام سمات بيانات JavaScript Interop وCSS.</description><content:encoded>&lt;p>﻿بالنسبة للأشخاص مثلي الذين يعانون من ذكريات الماضي في كل مرة أفتح فيها صفحة ويب، فقد توصلت إلى حل بسيط للغاية حول كيفية التبديل بين الوضع الفاتح والوضع الداكن باستخدام Javascript واستدعائه من Blazor باستخدام Javascript Interop.&lt;/p>
&lt;img src="https://media-exp1.licdn.com/dms/image/C4D22AQEYLTFA1e7i9A/feedshare-shrink_800/0/1641493691714?e=1651708800&amp;v=beta&amp;t=j6RxTrY--qUwxOcbt8Xh4QE9nYYF1zlJBMZP9dDLuC8" />
&lt;h1 id="تحديد-السمة-الأصل">تحديد السمة الأصل&lt;/h1>
&lt;p>بادئ ذي بدء، يتعين علينا تحديد العنصر الأصلي بمعرف، وذلك لأننا نغير الألوان اعتمادًا على قيمة ذلك المعرف.&lt;/p>
&lt;p>وللقيام بذلك في Javascript، سيتعين علينا تشغيل هذا الأمر&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>document.documentElement.setAttribute(&lt;span style="color:#a5d6ff">&amp;#39;data-theme&amp;#39;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;YOUR_IDFENTIFIER&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>في حالتنا، نريد أن يكون لدينا معرفان لكل نوع من الأنماط، وهما &lt;code>light&lt;/code> و &lt;code>dark&lt;/code>.&lt;/p>
&lt;h2 id="إنشاء-ملف-جافا-سكريبت-للتعامل-مع-المنطق">إنشاء ملف جافا سكريبت للتعامل مع المنطق&lt;/h2>
&lt;p>لدينا الآن وظيفة تحديث المعرف، والآن لنقم بإنشاء ملف Javascript بوظيفة تقوم بتشغيل هذا المنطق. سيتم استدعاء ملفنا &lt;code>app.js&lt;/code>&lt;/p>
&lt;img src="https://i.gyazo.com/c481aa0ed8329e8592832e9da2921cea.png" />
&lt;p>في هذا الملف، سيكون لدينا وظيفة تستدعي الكود الذي ذكرناه سابقًا.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> toggleTheme &lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span> &lt;span style="color:#ff7b72">function&lt;/span> (identifier) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> document.documentElement.setAttribute(&lt;span style="color:#a5d6ff">&amp;#39;data-theme&amp;#39;&lt;/span>, identifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="إضافة-ملف-جافا-سكريبت-إلى-blazor">إضافة ملف جافا سكريبت إلى Blazor&lt;/h2>
&lt;p>أضف البرنامج النصي إلى تطبيق Blazor عن طريق إضافته حيث توجد البرامج النصية.&lt;/p>
&lt;p>&lt;code>&amp;lt;script src=&amp;quot;~/js/app.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code>&lt;/p>
&lt;p>#إنشاء الخدمة&lt;/p>
&lt;p>الآن بعد أن حصلنا على كود Javascript، نحتاج إلى إنشاء خدمة تسمى &lt;code>ThemeToggleService&lt;/code>.&lt;/p>
&lt;img src="https://i.gyazo.com/e6480acaf46eef68f65b16a0738683ce.png" />
&lt;p>ستتعامل هذه الخدمة مع منطق التبديل بين السمة &lt;code>light&lt;/code> و &lt;code>dark&lt;/code>.&lt;/p>
&lt;h2 id="حقن-jsinterop">حقن JSInterop&lt;/h2>
&lt;p>من أجل استدعاء أي Javascipt، علينا استدعاء JSInterop، لذلك يتعين علينا إنشاء خاصية سيتم حقنها.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.JSInterop&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">BlazorDarkmodeToggle.Data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IJSRuntime jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ThemeToggleService(IJSRuntime jSRuntime)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.jSRuntime = jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="وظيفة-لتعيين-المعرف">وظيفة لتعيين المعرف&lt;/h2>
&lt;p>الآن بعد أن تم إنشاء الخدمة، فلنقم بإنشاء الوظيفة لتعيين المعرف، ستقوم هذه الوظيفة فقط بتحديث &lt;code>data-theme&lt;/code> في الصفحة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.JSInterop&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">BlazorDarkmodeToggle.Data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ThemeToggleService&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">readonly&lt;/span> IJSRuntime jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> ThemeToggleService(IJSRuntime jSRuntime)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.jSRuntime = jSRuntime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">bool&lt;/span> IsLightTheme { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; } = &lt;span style="color:#79c0ff">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetIdentifier =&amp;gt; IsLightTheme ? &lt;span style="color:#a5d6ff">&amp;#34;light&amp;#34;&lt;/span> : &lt;span style="color:#a5d6ff">&amp;#34;dark&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task ToggleTheme() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IsLightTheme = !IsLightTheme;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> jSRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;toggleTheme&amp;#34;&lt;/span>, GetIdentifier);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="إضافة-خدمة-لبدء-التشغيل">إضافة خدمة لبدء التشغيل&lt;/h2>
&lt;p>ولإتاحة الخدمة لجميع المكونات والصفحات، علينا إضافتها إلى ملف &lt;code>Program.cs&lt;/code>.&lt;/p>
&lt;p>&lt;code>builder.Services.AddSingleton&amp;lt;ThemeToggleService&amp;gt;();&lt;/code>&lt;/p>
&lt;p>وبذلك سينتهي الملف بهذا الشكل&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">BlazorDarkmodeToggle.Data&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.AspNetCore.Components.Web&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> builder = WebApplication.CreateBuilder(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Add services to the container.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddRazorPages();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddServerSideBlazor();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddSingleton&amp;lt;WeatherForecastService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>builder.Services.AddSingleton&amp;lt;ThemeToggleService&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> app = builder.Build();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Configure the HTTP request pipeline.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">if&lt;/span> (!app.Environment.IsDevelopment())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseExceptionHandler(&lt;span style="color:#a5d6ff">&amp;#34;/Error&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> app.UseHsts();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseHttpsRedirection();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseStaticFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.UseRouting();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapBlazorHub();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.MapFallbackToPage(&lt;span style="color:#a5d6ff">&amp;#34;/_Host&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app.Run();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ضع في اعتبارك أن هذا يمكن أن يتغير اعتمادًا على ما إذا كنت تستخدم Blazor &lt;code>Server&lt;/code> أو Blazor &lt;code>WebAssembly&lt;/code>.&lt;/p>
&lt;h1 id="خبز-css">خبز CSS&lt;/h1>
&lt;p>الآن بعد أن أصبح لدينا جزء من الكود جاهزًا، علينا أن نبدأ العمل على CSS. ما يتعين علينا القيام به هو تعيين جميع خصائص CSS التي تتعامل مع ألوان CSS والخلفية وما إلى ذلك إلى متغير.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>:&lt;span style="color:#d2a8ff;font-weight:bold">root&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--background-color&lt;/span>:&lt;span style="color:#a5d6ff">#ffffff&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--text-color&lt;/span>:&lt;span style="color:#a5d6ff">#000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بمجرد القيام بذلك، باستخدام المعرف، يمكننا التغيير بسهولة بين المعرفات، وسوف يستخدم أيًا منهما بقيمة أخرى.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">[&lt;/span>&lt;span style="color:#7ee787">data-theme&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">=&lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;dark&amp;#34;&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">]&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--background-color&lt;/span>: &lt;span style="color:#a5d6ff">#000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#79c0ff">--text-color&lt;/span>: &lt;span style="color:#a5d6ff">#ffffff&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لتلاشي الرسوم المتحركة الرائعة، سنضيف فقط خاصية &lt;code>transition&lt;/code> إلى الكل، حتى تبدو جيدة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">*&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">transition&lt;/span>: &lt;span style="color:#79c0ff">all&lt;/span> &lt;span style="color:#a5d6ff">250&lt;/span>&lt;span style="color:#ff7b72">ms&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن سنقوم بتعيين هذه المتغيرات لبعض فئات CSS، حتى نتمكن من رؤيتها على صفحة Blazor.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">app-background&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">background-color&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>background&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">width&lt;/span>: &lt;span style="color:#a5d6ff">200&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">height&lt;/span>: &lt;span style="color:#a5d6ff">200&lt;/span>&lt;span style="color:#ff7b72">px&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#f0883e;font-weight:bold">app-text&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">color&lt;/span>: &lt;span style="color:#d2a8ff;font-weight:bold">var&lt;/span>(&lt;span style="color:#ff7b72;font-weight:bold">--&lt;/span>&lt;span style="color:#79c0ff">text&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&lt;span style="color:#79c0ff">color&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تذكر إضافة الملف إلى تطبيق Blazor عن طريق إضافته على &lt;code>head&lt;/code>.&lt;/p>
&lt;p>&lt;code>&amp;lt;link href=&amp;quot;css/app.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;&lt;/code>&lt;/p>
&lt;h1 id="اتصال-من-بليزر">اتصال من بليزر&lt;/h1>
&lt;p>إذن كيف سنختبر هذا؟ لتبسيط الأمر، سنقوم فقط بإضافة div مع فئة &lt;code>app-background&lt;/code>، وبداخله سيكون هناك &lt;code>p&lt;/code> مع فئة &lt;code>app-text&lt;/code>.لذلك دعونا ننشئ صفحة تسمى &lt;code>Theme.razor&lt;/code> ضمن المجلد &lt;code>Pages&lt;/code> ونضيف بعض التعليمات البرمجية لذلك&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/theme&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using BlazorDarkmodeToggle.Data
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject ThemeToggleService ThemeToggleService
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span> @onclick=Toggle&amp;gt;Toggle theme&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;br /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;app-background&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;p class=&lt;span style="color:#a5d6ff">&amp;#34;app-text&amp;#34;&lt;/span>&amp;gt;Hello world!&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task Toggle()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> &lt;span style="color:#ff7b72">this&lt;/span>.ThemeToggleService.ToggleTheme();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يمكننا اختبار ذلك بالانتقال إلى &lt;code>/theme&lt;/code>، ولكن دعونا نضيفه أيضًا إلى شريط التنقل&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;nav-item px-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;NavLink class=&lt;span style="color:#a5d6ff">&amp;#34;nav-link&amp;#34;&lt;/span> href=&lt;span style="color:#a5d6ff">&amp;#34;theme&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;span class=&lt;span style="color:#a5d6ff">&amp;#34;oi oi-list-rich&amp;#34;&lt;/span> aria-hidden=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span>&amp;gt;&amp;lt;/span&amp;gt; Theme togle
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/NavLink&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="الاختبار">الاختبار&lt;/h1>
&lt;img src="https://i.gyazo.com/a4894edb345cfd8c9f0cf2de869dba32.gif" />
&lt;p>كما ترون، بمجرد تبديلنا لتغييرات السمة والخلفية ولون الخط، يمكنك الآن معرفة مدى قوة ذلك، إذا قمت بالفعل بتطوير الصفحة بأكملها باستخدام هذه المتغيرات في CSS، فيمكن أن يكون لديك سمات مختلفة وتبديلها فقط!&lt;/p>
&lt;p>كم هو رائع!!&lt;/p>
&lt;h1 id="الكود">الكود&lt;/h1>
&lt;p>هذا المشروع بأكمله موجود على Github ويمكنك العثور عليه &lt;a href="https://github.com/emimontesdeoca/BlazorDarkmodeToggle">هنا&lt;/a>!&lt;/p>
&lt;p>إذا كانت لديك أي مشاكل أو أسئلة، فلا تتردد في الاتصال بي على أي من وسائل التواصل الاجتماعي على @emimontesdeoca (في تويتر هو في الواقع &lt;code>@emimontesdeocaa&lt;/code> مع اثنين من &lt;code>aa&lt;/code> في النهاية). يمكنك أيضًا العثور على معظم وسائل التواصل الاجتماعي الخاصة بي على رأس المدونة.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب!&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-6.0">استدعاء وظائف JavaScript من أساليب .NET في ASP.NET Core Blazor&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>Docker</category></item><item><title>تحميل الملفات إلى Azure Blob Storage في Blazor</title><link>https://emimontesdeoca.github.io/ar/posts/uploading-files-az-blob-blazor/</link><pubDate>Wed, 23 Mar 2022 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/uploading-files-az-blob-blazor/</guid><description>قم بتحميل الملفات إلى Azure Blob Storage من تطبيق Blazor باستخدام إدخال ملف HTML5 الأصلي.</description><content:encoded>&lt;p>﻿تعد خدمة تخزين Azure Blob واحدة من الخدمات الأكثر استخدامًا على نظام Azure البيئي، فهي تتيح لك تحميل الكثير من الملفات إلى السحابة بينما تكون رخيصة للغاية. كما أن لديها شبكة ويب بديهية يمكنك الوصول إليها أو تنزيلها عبر رابط مباشر.&lt;/p>
&lt;p>بشكل عام، خدمة جيدة جدًا، لقد كنت أستخدمها في كل من مشاريع العمل والمشاريع الشخصية وبصراحة أفضل شيء هو بساطة كيفية إنجاحها.&lt;/p>
&lt;p>معظم الحالات التي استخدمت فيها Azure Blob Storage تكون في الواجهة الخلفية، حيث يتم تحميل شيء ما مباشرة نتيجة لعملية أو شيء من هذا القبيل. لذلك لم أقم حقًا بأي نوع من تحميل ملف من موقع ويب أو نحو ذلك. سيئ الحظ!&lt;/p>
&lt;p>نظرًا لأنني معجب بـ Blazor، فأنا أقدم لك طريقة لتحميل الملفات إلى Azure Blob Storage باستخدام إدخال الملف الأصلي من HTML5.&lt;/p>
&lt;p>#المتطلبات&lt;/p>
&lt;ul>
&lt;li>حساب Azure (إذا لم يكن لديك، فانتقل &lt;a href="aka.ms/free">هنا&lt;/a> إلى مجموعة من $$$).&lt;/li>
&lt;li>IDE (VS، Code، أي شيء سيعمل)&lt;/li>
&lt;li>.NET Core 3.0 أو أعلى&lt;/li>
&lt;/ul>
&lt;h1 id="إنشاء-وحدة-تخزين-azure-blob">إنشاء وحدة تخزين Azure Blob&lt;/h1>
&lt;p>انتقل إلى بوابة Azure الخاصة بك ودعنا نتابع إنشاء حساب تخزين.&lt;/p>
&lt;p>ابحث أولاً عن &lt;code>Storage accounts&lt;/code> وانقر على النتيجة.&lt;/p>
&lt;img src="https://i.gyazo.com/dfc7db88129cd5e2a5015a7bfd846685.png" />
&lt;p>بعد التحميل، انقر على &lt;code>Create&lt;/code>.&lt;/p>
&lt;p>سيظهر نموذج يحتوي على مجموعة من الخطوات، لذا قم بملئها.&lt;/p>
&lt;img src="https://i.gyazo.com/2293db6fea38aa8b9c61980d088c56c4.png" />
&lt;p>بعد ملء كل شيء بالإعدادات التي تريدها، قم بتمرير التحقق من الصحة وإنشاء المورد.&lt;/p>
&lt;img src="https://i.gyazo.com/f416db52c958744f8e9ca1dbe904a790.png" />
&lt;p>بعد إنشائه، انقر على &lt;code>Go to resource&lt;/code>. سنحصل على سلسلة اتصال تتيح لنا اللعب باستخدام واجهة برمجة التطبيقات (API).&lt;/p>
&lt;img src="https://i.gyazo.com/713702e57e8c18db16ec214ea999507f.png" />
&lt;h1 id="احصل-على-مفاتيح-الموارد">احصل على مفاتيح الموارد&lt;/h1>
&lt;p>بعد أن وصلنا إلى المورد الذي تم إنشاؤه حديثًا، انتقل إلى &lt;code>Acccess keys&lt;/code> ضمن &lt;code>Security + networking&lt;/code>. بعد التحميل سترى &lt;code>Connection string&lt;/code> و &lt;code>Key&lt;/code>، انسخهما لأننا سنحتاج إليهما لاحقًا!&lt;/p>
&lt;img src="https://i.gyazo.com/87af79ff72e5973f3fbcdf22547d2c54.png" />
&lt;h1 id="إنشاء-مشروع-رائع-لخادم-blazor">إنشاء مشروع رائع لخادم Blazor&lt;/h1>
&lt;p>سأستخدم Visual Studio 2022 لهذا البرنامج التعليمي، ولكن كما قلت من قبل، يمكنك استخدام أي بيئة تطوير متكاملة (IDE) أخرى وإنشاء المشروع باستخدام &lt;code>dotnet&lt;/code> CLI.&lt;/p>
&lt;p>ثم دعونا نمضي قدمًا وننشئ مشروع Blazor بسرعة كبيرة باستخدام Visual Studio في مجموعة من الخطوات فقط.&lt;/p>
&lt;img src="https://i.gyazo.com/fab7bbabc9601132c8554c0b83ff4f58.png" />
&lt;img src="https://i.gyazo.com/dd015b5c8f1a46c2df0fa5af7cfc08e4.png" />
&lt;img src="https://i.gyazo.com/e7225a77fb3a91c2fb5d39877165e9b8.png" />
&lt;p>إذا قمنا بتشغيل ما أنشأناه للتو، فسيبدو هكذا، مجرد تطبيق Blazor عادي.&lt;/p>
&lt;img src="https://i.gyazo.com/95785a6c6cc68050d9989c489df0f599.png" />
&lt;p>الآن بعد أن أنشأنا المشروع، فلنقم ببعض عناصر واجهة المستخدم حتى يكون لدينا صفحة جديدة تحتوي على مجموعة من المدخلات حتى نتمكن من ملئها بالمفاتيح من المورد، و&lt;code>InputFile&lt;/code> للملف (الملفات)، وزر للقيام بشيء ما، ورسالة ستنتج عن الإجراء.&lt;/p>
&lt;h2 id="إنشاء-النموذج">إنشاء النموذج&lt;/h2>
&lt;p>سنحتاج إلى بعض النماذج للقيام بذلك بشكل صحيح. أولاً سيكون لدينا فئة &lt;code>BlobRequest&lt;/code> التي ستتعامل مع سلسلة الاتصال واسم الحاوية والملفات.&lt;/p>
&lt;p>أيضًا، للحفاظ على كل شيء بشكل مرتب، سنقوم بإنشاء فصل يسمى &lt;code>BlobFile&lt;/code> حيث سنقوم بتخزين &lt;code>Name&lt;/code> و &lt;code>Data&lt;/code>.&lt;/p>
&lt;p>لقد قمت بإنشاء مجلد باسم &lt;code>Models&lt;/code> لذلك تم فصل كل شيء.&lt;/p>
&lt;p>الطبقات بسيطة جدا.```csharp
using System.ComponentModel.DataAnnotations;&lt;/p>
&lt;p>namespace UploadingFilesAzBlobBlazor.Models {
public class BlobRequest
{
[Required(ErrorMessage = &amp;ldquo;Connection string is required&amp;rdquo;)]
public string? ConnectionString { get; set; }
[Required(ErrorMessage = &amp;ldquo;Container name is required&amp;rdquo;)]
public string? Container { get; set; }
public List&lt;BlobFile>? Files { get; set; }
}
}&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f85149">```&lt;/span>csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>namespace UploadingFilesAzBlobBlazor&lt;span style="color:#ff7b72;font-weight:bold">.&lt;/span>Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public &lt;span style="color:#ff7b72">class&lt;/span> BlobFile
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public string&lt;span style="color:#f85149">?&lt;/span> Name { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> public byte[]&lt;span style="color:#f85149">?&lt;/span> Data { get; set; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="تعديل-واجهة-المستخدم">تعديل واجهة المستخدم&lt;/h2>
&lt;p>الآن بعد أن قمنا بإنشاء نموذجنا، فلنمضي قدمًا ونقوم ببعض السحر على واجهة المستخدم باستخدامه!&lt;/p>
&lt;p>أولاً، قم بإنشاء صفحة ضمن المجلد &lt;code>Pages&lt;/code> المسمى &lt;code>Upload.razor&lt;/code>.&lt;/p>
&lt;p>في هذه الصفحة، سنضيف نموذجًا صغيرًا يملأ فئة &lt;code>BlobRequest&lt;/code>، لذلك يتعين علينا إضافة مدخلات لـ &lt;code>ConnectionString&lt;/code>، و&lt;code>Container&lt;/code>، و&lt;code>Files&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@page &lt;span style="color:#a5d6ff">&amp;#34;/upload&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using UploadingFilesAzBlobBlazor.Models
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@inject IJSRuntime JsRuntime
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;PageTitle&amp;gt; Upload&amp;lt;/PageTitle&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;h1&amp;gt; Azure blob storage uploader!&amp;lt;/h1&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;EditForm Model=&lt;span style="color:#a5d6ff">&amp;#34;@modal&amp;#34;&lt;/span> OnValidSubmit=&lt;span style="color:#a5d6ff">&amp;#34;@HandleValidSubmit&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;DataAnnotationsValidator /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;ValidationSummary /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;connectionString&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt; Connection &lt;span style="color:#ff7b72">string&lt;/span>&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;InputText class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;connectionString&amp;#34;&lt;/span> @bind-Value=&lt;span style="color:#a5d6ff">&amp;#34;modal.ConnectionString&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;container&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt; Container name&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;InputText class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;container&amp;#34;&lt;/span> @bind-Value=&lt;span style="color:#a5d6ff">&amp;#34;modal.Container&amp;#34;&lt;/span> /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;label &lt;span style="color:#ff7b72">for&lt;/span>=&lt;span style="color:#a5d6ff">&amp;#34;formFileMultiple&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-label&amp;#34;&lt;/span>&amp;gt; Files&amp;lt;/label&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;InputFile OnChange=&lt;span style="color:#a5d6ff">&amp;#34;OnInputFileChange&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;form-control&amp;#34;&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;formFileMultiple&amp;#34;&lt;/span> multiple /&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;div class=&lt;span style="color:#a5d6ff">&amp;#34;mb-3&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;button type=&lt;span style="color:#a5d6ff">&amp;#34;submit&amp;#34;&lt;/span> class=&lt;span style="color:#a5d6ff">&amp;#34;btn btn-primary&amp;#34;&lt;/span>&amp;gt; Submit&amp;lt;/button&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/EditForm&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@code {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> BlobRequest modal = &lt;span style="color:#ff7b72">new&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> EditContext editContext;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IReadOnlyList &amp;lt;IBrowserFile&amp;gt; selectedFiles;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> OnInputFileChange(InputFileChangeEventArgs e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> selectedFiles = e.GetMultipleFiles();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span> .StateHasChanged();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task HandleValidSubmit() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// This is where we are going to upload stuff ! &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JsRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Files uploaded!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>إنه جزء من التعليمات البرمجية ولكنه في الأساس سيعرض النموذج ويتعامل مع التحقق من الصحة.&lt;/p>
&lt;img src="https://i.gyazo.com/00d0ad6d23fa93bdb0fb354218018c5f.png"/>
&lt;p>إذا ضغطت على الزر للتحميل وكان كل شيء على ما يرام، فسيتم عرض تنبيه.&lt;/p>
&lt;img src="https://i.gyazo.com/27a3ff831fa9936bac21d8aa8ff60936.gif"/>
&lt;h2 id="التحميل-إلى-مساحة-تخزين-azure-blob">التحميل إلى مساحة تخزين Azure Blob&lt;/h2>
&lt;p>الآن بعد أن انتهينا من معظم واجهة المستخدم، فلنثبت الآن الحزمة التي ستتعامل مع واجهة برمجة تطبيقات Azure Blob Storage وستسمح لنا بتحميل الملفات. بسيطة جدا.&lt;/p>
&lt;h3 id="أضف-حزمة-nuget">أضف حزمة NuGet&lt;/h3>
&lt;p>دعونا ندير حزم NuGet من المشروع ونضيف &lt;code>Azure.Storage.Blob&lt;/code>.&lt;/p>
&lt;img src="https://i.gyazo.com/c6ecd7b13c0a63a50f38342407acee49.png"/>
&lt;h3 id="التحميل-إلى-azure-blob-stoare">التحميل إلى Azure Blob Stoare&lt;/h3>
&lt;p>الآن دعونا نقوم بالمنطق الخاص بتحميل الملف فعليًا، عادةً ما نستخدم سلسلة الاتصال والمفتاح من app.config ولكن من أجل هذا البرنامج التعليمي، نستخدم المدخلات ونوفر البيانات هناك.&lt;/p>
&lt;p>أولاً، دعونا نضيف &lt;code>using&lt;/code> لفئة تخزين Azure Blob&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>@using Azure.Storage.Blobs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@using Azure.Storage.Blobs.Models
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد ذلك، سنعمل على طريقة &lt;code>HandleValidSubmit&lt;/code>، والتي ستتصل بمخزن البيانات الثنائية الكبيرة، وننشئ الحاوية إذا لم تكن موجودة ثم نقوم بتحميل الملفات.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task HandleValidSubmit()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Instantiate container&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> container = &lt;span style="color:#ff7b72">new&lt;/span> BlobContainerClient(modal.ConnectionString, modal.Container);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create container if not exists&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> createResponse = &lt;span style="color:#ff7b72">await&lt;/span> container.CreateIfNotExistsAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Set access policy&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (createResponse != &lt;span style="color:#79c0ff">null&lt;/span> &amp;amp;&amp;amp; createResponse.GetRawResponse().Status == &lt;span style="color:#a5d6ff">201&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> container.SetAccessPolicyAsync(Azure.Storage.Blobs.Models.PublicAccessType.Blob);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// For each file that we have uploaded&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> file &lt;span style="color:#ff7b72">in&lt;/span> selectedFiles)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// New blob&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> blob = container.GetBlobClient(file.Name);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Delete any blob with the same name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> blob.DeleteIfExistsAsync(Azure.Storage.Blobs.Models.DeleteSnapshotsOption.IncludeSnapshots);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Create a file stream and use the UploadSync method to upload the Blob.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> fileStream = file.OpenReadStream())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> blob.UploadAsync(fileStream, &lt;span style="color:#ff7b72">new&lt;/span> BlobHttpHeaders { ContentType = file.ContentType });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Display success message&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JsRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;Files uploaded!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Display error message&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> JsRuntime.InvokeVoidAsync(&lt;span style="color:#a5d6ff">&amp;#34;alert&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;An error ocurred!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="اختبار-الكود">اختبار الكود&lt;/h3>
&lt;p>حان الوقت الآن لاختبار الكود، وللقيام بذلك، ما عليك سوى ملء النموذج والنقر على &amp;ldquo;إرسال&amp;rdquo;.&lt;/p>
&lt;img src="https://i.gyazo.com/4ce26ec6487038f3bc70c8e977b16215.png"/>
&lt;p>سنعرض تنبيهًا ويجب تحميل الملف إلى Azure Blob Storage، وانتقل إلى Azure Portal وألقِ نظرة.&lt;/p>
&lt;img src="https://i.gyazo.com/8c070489d4aa6a9f917d685a2ba670f3.png"/>
&lt;p>كما ترون، تم إنشاء الحاوية أيضًا، والآن إذا دخلنا إلى الحاوية، نرى الملف الذي قمنا بتحميله.&lt;/p>
&lt;img src="https://i.gyazo.com/166076b94e065578a3aaa4012d3b5c37.png"/>
&lt;p>عمل جيد!&lt;/p>
&lt;h3 id="إعادة-البناء">إعادة البناء&lt;/h3>
&lt;p>الآن بعد أن تم كل شيء، دعنا ننقل الكود من صفحة &lt;code>.razor&lt;/code> إلى فئة &lt;code>.razor.cs&lt;/code> بحيث يبدو أفضل.&lt;/p>
&lt;p>إذا كنت تستخدم Visual Studio 2022، فهناك مصباح كهربائي عند التمرير مباشرة في &lt;code>@code&lt;/code>، وسيُظهر لك خيارًا لـ &lt;code>Extract block to code behind&lt;/code>، وسيفعل كما هو مذكور!&lt;/p>
&lt;img src="https://i.gyazo.com/1a4b52c9adf1728860b1965bc9b11bdd.png"/>
&lt;p>الآن سوف يتم فصل كل شيء وإنجازه!&lt;/p>
&lt;h1 id="الكود">الكود&lt;/h1>
&lt;p>هذا المشروع بأكمله موجود على Github ويمكنك العثور عليه &lt;a href="https://github.com/emimontesdeoca/UploadingFilesAzBlobBlazor">هنا&lt;/a>!&lt;/p>
&lt;p>إذا كانت لديك أي مشاكل أو أسئلة، فلا تتردد في الاتصال بي على أي وسيلة تواصل اجتماعية على @emimontesdeoca (في تويتر هو في الواقع &lt;code>@emimontesdeocaa&lt;/code> مع اثنين من &lt;code>aa&lt;/code> في النهاية). يمكنك أيضًا العثور على معظم وسائل التواصل الاجتماعي الخاصة بي على رأس المدونة.&lt;/p>
&lt;p>آمل أن تكونوا قد أحببت هذا المنصب! سيا!&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.nuget.org/packages/Azure.Storage.Blobs">https://www.nuget.org/packages/Azure.Storage.Blobs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet">https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet&lt;/a>&lt;/li>
&lt;/ul></content:encoded><category>.NET</category><category>Blazor</category><category>Azure</category><category>NuGet</category><category>Docker</category></item><item><title>تجاوز وحدة التحكم الأساسية في Pimcore</title><link>https://emimontesdeoca.github.io/ar/posts/override-method-pimcore-core-bundles/</link><pubDate>Thu, 10 Dec 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/override-method-pimcore-core-bundles/</guid><description>تجاوز وحدات تحكم Pimcore الأساسية عن طريق إنشاء حزم Symfony مخصصة مع تكوين الخدمة.</description><content:encoded>&lt;p>﻿لقد كنت أعمل في مشروع Pimcore منذ بعض الوقت، ولم أتطرق مطلقًا إلى PHP وSymfony في حياتي، لذلك كان ذلك تحديًا كبيرًا.&lt;/p>
&lt;p>التوثيق رائع، أعني، إنه رائع حقًا، ولكن يبدو أنه ليس مخصصًا للمبتدئين في اللغة/إطار العمل، لذا كل ما قمت به كان علي أن أقوم بتدوين ملاحظات عنه.&lt;/p>
&lt;p>بعد أن تعلمت أنه يمكنك توسيع Pimcore باستخدام الحزم، قضيت ساعات وساعات في محاولة لتجاوز وحدات التحكم وطرق جافا سكريبت والمزيد من الأشياء.&lt;/p>
&lt;p>لذا سأشرح في هذا المنشور كيف تمكنت من تجاوز وحدة التحكم. كيفية تجاوز ملفات جافا سكريبت ستأتي لاحقًا 😉.&lt;/p>
&lt;h1 id="إنشاء-الحزمة">إنشاء الحزمة&lt;/h1>
&lt;p>أولاً، نحتاج إلى إنشاء حزمة لها، الأمر بسيط جدًا كما هو موثق في &lt;a href="https://pimcore.com/docs/pimcore/current/Development_Documentation/Extending_Pimcore/Bundle_Developers_Guide/index.html">وثائق Pimcore&lt;/a>، نحتاج فقط إلى تشغيل &lt;code>bin/console pimcore:generate:bundle --namespace=EmiDemo/EmiDemoBundle&lt;/code> في مجلد المشروع.&lt;/p>
&lt;p>سوف يطرح بعض الأسئلة ولكن لا شيء يدعو للقلق.&lt;/p>
&lt;div style="text-align:center">&lt;img src="https://i.gyazo.com/9aa04169e668506d18638388d0061910.png" />&lt;/div>
&lt;p>سيتم إنشاء مجلد جديد ضمن المجلد &lt;code>src&lt;/code> بمساحة الاسم التي أعلناها من قبل، وفي الداخل سيكون لدينا جميع الملفات الضرورية للحزمة.&lt;/p>
&lt;div style="text-align:center">&lt;img src="https://i.gyazo.com/4d3329c303bb43012faaae43290c613b.png" />&lt;/div>
&lt;p>أيضًا، سيتم اكتشاف المكون الإضافي من خلال موقع إدارة Pimcore، ولكن سيتم تعطيله، يجب عليك تمكينه لبدء استخدامه.&lt;/p>
&lt;div style="text-align:center">&lt;img src="https://i.gyazo.com/e0170db9111df69a00bdb3f10473c9a7.png" />&lt;/div>
&lt;h1 id="تجاوز-الطريقة-من-وحدة-التحكم">تجاوز الطريقة من وحدة التحكم&lt;/h1>
&lt;p>لتجاوز طريقة ما في وحدة التحكم، من الواضح أنك تحتاج أولاً إلى العثور على الإجراء الذي تريد تحديثه، يبدو هذا سهلاً ولكني سأعطيك الطريقة التي أفعلها عادةً (تعلمت من زميلي في الفريق &lt;a href="https://twitter.com/cesabreu">سيزار&lt;/a>).&lt;/p>
&lt;p>انتقل أولاً إلى الصفحة التي تعتقد أن وحدة التحكم تتخذ إجراءً، وفي حالتي أريد التحقق من الصفحة التي يتم تحميلها عندما نفتح أحد الأصول&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/df3833858806b14a39f50a0707a19dcd">&lt;img src="https://i.gyazo.com/df3833858806b14a39f50a0707a19dcd.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>بعد ذلك، ما عليك سوى فتح وحدة التحكم والانتقال إلى علامة تبويب الشبكة، وإجراء نفس الخطوات مرة أخرى ومحاولة العثور على الإجراء&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/5fbd9496d585d145bea3f9a3b950de73">&lt;img src="https://i.gyazo.com/5fbd9496d585d145bea3f9a3b950de73.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>باستخدام هذه المعلومات، يمكنك أن ترى أن الإجراء الذي يقوم بتحميل الأصل هو &lt;code>http://localhost/admin/asset/get-data-by-id?_dc=1607601778450&amp;amp;id=2&amp;amp;type=image&lt;/code>. من ذلك يمكننا أن نرى أن إجراء وحدة التحكم هو &lt;code>get-data-by-id&lt;/code>.&lt;/p>
&lt;h2 id="ابحث-عن-الإجراء-في-وحدة-التحكم-الأساسية">ابحث عن الإجراء في وحدة التحكم الأساسية&lt;/h2>
&lt;p>بالنسبة لي، فإن أبسط طريقة للقيام بذلك هي العثور على جميع الملفات في Visual Studio Code&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/96cbeea01a1174d45e5a263a997c882a">&lt;img src="https://i.gyazo.com/96cbeea01a1174d45e5a263a997c882a.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>من المحتمل أن تجد أكثر من واحد لذا يجب عليك إلقاء نظرة وتحديد أي منها هو، في حالتنا نحن نعمل مع الأصول لذا فمن الواضح أننا نريد استخدام &lt;code>AssetController.php&lt;/code>.&lt;/p>
&lt;h2 id="تعديل-وحدة-التحكم-الأساسية">تعديل وحدة التحكم الأساسية&lt;/h2>
&lt;p>يعتمد الأمر على المطور، وأنا لا أفعل ذلك عادةً لأنه من الأسرع تجاوز وحدة التحكم والتطور من هناك. لكنني أوصي أولاً بتحديث وحدة التحكم في الحزمة الأساسية لمعرفة ما إذا كانت التغييرات التي أجريتها تعمل أم لا.&lt;/p>
&lt;p>في حالتنا، سأقوم فقط بإرجاع رسالة في بداية الطريقة للتأكد من أنها تعمل.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#79c0ff">$this&lt;/span>&lt;span style="color:#ff7b72;font-weight:bold">-&amp;gt;&lt;/span>adminJson([&lt;span style="color:#a5d6ff">&amp;#39;success&amp;#39;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#ff7b72">false&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#39;message&amp;#39;&lt;/span> &lt;span style="color:#ff7b72;font-weight:bold">=&amp;gt;&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;Overriding the getDataByIdAction in the core!!&amp;#34;&lt;/span>]);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://gyazo.com/32139d67b9e5369d5ab38daed1b229ea">&lt;img src="https://i.gyazo.com/32139d67b9e5369d5ab38daed1b229ea.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>ثم دعونا نعيد تحميل أصولنا ونتحقق من علامة تبويب الشبكة&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/d1653f36e7bece9da6232ede0a431c05">&lt;img src="https://i.gyazo.com/d1653f36e7bece9da6232ede0a431c05.png" alt="صورة من جيازو">&lt;/a>دعونا نحتفظ بما لدينا هناك، حتى نعلم لاحقًا أننا نستخدم رسالة الحزمة بدلاً من الرسالة الأساسية.&lt;/p>
&lt;p>الآن دعنا ننسخ ذلك إلى ملف مؤقت لتتبع ما فعلناه.&lt;/p>
&lt;h2 id="انقل-هذه-التغييرات-إلى-الحزمة">انقل هذه التغييرات إلى الحزمة&lt;/h2>
&lt;p>الآن، لنتمكن من نقل هذه التغييرات إلى مجموعتنا، لكننا نحتاج أولاً إلى بعض العناصر من وحدة التحكم الأساسية لدينا:&lt;/p>
&lt;ul>
&lt;li>مساحة الاسم&lt;/li>
&lt;li>الواردات&lt;/li>
&lt;li>اسم وحدة التحكم&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://gyazo.com/416b3a527be397aaf6d9f89073c02428">&lt;img src="https://i.gyazo.com/416b3a527be397aaf6d9f89073c02428.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>ومن الواضح أن الطريقة &lt;code>getDataByIdAction&lt;/code> هي الطريقة التي نريد تجاوزها.&lt;/p>
&lt;h2 id="فضح-وحدة-التحكم">فضح وحدة التحكم&lt;/h2>
&lt;p>نحتاج إلى الكشف عن وحدة التحكم، وللقيام بذلك، عليك الانتقال إلى الملف &lt;code>/src/EmiDemo/EmiDemoBundle/Resources/config/pimcore/routing.yml&lt;/code> وإضافة&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">options&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">expose&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#79c0ff">true&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نحتاج أيضًا إلى تغيير &lt;code>prefix&lt;/code> إلى الذي سنتجاوزه، في حالتنا، وحدة التحكم &lt;code>admin&lt;/code>.&lt;/p>
&lt;p>لذلك في النهاية سوف تبدو مثل هذا&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">emi_demo_emi_demo&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">resource&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;@EmiDemoEmiDemoBundle/Controller/&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">type&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">annotation&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">prefix&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">/admin&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">options&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">expose&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#79c0ff">true&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="defaultcontrollerphp">DefaultController.php&lt;/h2>
&lt;p>في ملفنا، سنقوم بإضافة الواردات وتوسيع وحدة التحكم مع تلك التي سنتجاوزها&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/b8d798436ed111d4676312f8aeba443d">&lt;img src="https://i.gyazo.com/b8d798436ed111d4676312f8aeba443d.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>بعد ذلك سنقوم فقط بنسخ الطريقة من وحدة التحكم الأساسية، وفي حالتنا، نقوم بتحديث الرسالة التي نعيدها.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/dcdd9f8166e4ba8820cb8e285c43dec8">&lt;img src="https://i.gyazo.com/dcdd9f8166e4ba8820cb8e285c43dec8.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>إذا كنت تتذكر، لدينا رسالة في وحدة التحكم الأساسية بالفعل: &lt;code>Overriding the getDataByIdAction in the core!!&lt;/code> والآن يجب أن نرى &lt;code>Overriding the getDataByIdAction in the bundle!!&lt;/code>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/8e16b1de2a40292c843c51576acf43c4">&lt;img src="https://i.gyazo.com/8e16b1de2a40292c843c51576acf43c4.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>دعونا لا نعيد أي شيء ونرى أنه يمكننا الآن رؤية الصفحة كما كانت من قبل&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/dda8df5f6d721f5ba25d6a056ac7f9bf">&lt;img src="https://i.gyazo.com/dda8df5f6d721f5ba25d6a056ac7f9bf.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>قم بالتعليق على بيان الإرجاع وأعد التحميل&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/2fdbff7f45c38fb2bc56a3fc73661077">&lt;img src="https://i.gyazo.com/2fdbff7f45c38fb2bc56a3fc73661077.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="هذا-كل-شيء">هذا كل شيء&lt;/h2>
&lt;p>ومن خلال هذا العرض التوضيحي الصغير، يمكنك معرفة مدى سهولة تجاوز وحدة التحكم الأساسية الموجودة في Pimcore. هناك بعض الخطوات التي عليك اتباعها ولكنها ليست صعبة. فقط تأكد من كشف وحدة التحكم ومسح ذاكرة التخزين المؤقت من وقت لآخر أثناء إجراء التغييرات، وفي بعض الأحيان يتم تخزينها مؤقتًا وتظل عالقًا معتقدًا أنها لا تعمل وأنها مخزنة مؤقتًا فقط.&lt;/p>
&lt;p>إذا كنت تتساءل عن كيفية تجاوز ملفات جافا سكريبت، فسوف يأتي في برنامج تعليمي آخر 😁.&lt;/p></content:encoded></item><item><title>قم بتكوين Apple Magic Keyboard 2 على نظام التشغيل Windows 10</title><link>https://emimontesdeoca.github.io/ar/posts/configure-apple-keyboard-windows/</link><pubDate>Wed, 11 Nov 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/configure-apple-keyboard-windows/</guid><description>قم بتثبيت برامج التشغيل وتكوين Apple Magic Keyboard 2 للعمل بشكل صحيح على نظام التشغيل Windows 10.</description><content:encoded>&lt;p>&lt;a href="https://gyazo.com/9c8641cdd22bd528b2141bad1322c74a">&lt;img src="https://i.gyazo.com/9c8641cdd22bd528b2141bad1322c74a.jpg" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>بالأمس فقط اشتريت Apple Magic Keyboard 2، حتى لو كان لدي 5 لوحات مفاتيح ميكانيكية، لأنني أردت تجربتها وكانت لاسلكية.&lt;/p>
&lt;p>نظام التشغيل الرئيسي لدي هو Windows 10، وأنا أحبه ولا أريد تغييره، لذا مع أخذ ذلك في الاعتبار، علمت أنه سيكون من الضروري القيام ببعض الأشياء لجعل لوحة المفاتيح تعمل بشكل مثالي. أعرف كيف تعمل شركة Apple وكيف ترغب في الحفاظ على أجهزتها في نظامها البيئي.&lt;/p>
&lt;h2 id="القضايا">القضايا&lt;/h2>
&lt;p>إذا قمت بإقران لوحة المفاتيح، فسوف تتعرف على بعض الأشياء:&lt;/p>
&lt;ul>
&lt;li>مفاتيح الوظائف لا تعمل&lt;/li>
&lt;li>تم تعيين بعض المفاتيح بشكل خاطئ (حدث هذا لي في النسخة الإسبانية)&lt;/li>
&lt;/ul>
&lt;h2 id="التوثيق">التوثيق&lt;/h2>
&lt;p>لكي أجعله يعمل، كان علي أن أقرأ الكثير من الويب، ولكن هذين الرابطين هما اللذان ساعداني على إنجاحه:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.bluetoothgoodies.com/info/apple-devices/">الاستفادة الكاملة من لوحة المفاتيح/الماوس/لوحة التتبع Apple Magic على نظام التشغيل Windows&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://superuser.com/questions/82826/how-do-i-use-my-f-1-f12-keys-without-pressing-fn-on-windows-7-using-bootcamp-o">كيف يمكنني استخدام المفاتيح f-1 - f12 دون الضغط على FN في نظام التشغيل Windows 7 باستخدام Bootcamp على جهاز Macbook Pro؟&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="تثبيت-برنامج-تشغيل-لوحة-مفاتيح-apple">تثبيت برنامج تشغيل لوحة مفاتيح Apple&lt;/h2>
&lt;p>بعض هذه الخطوات مأخوذة من الوثائق المذكورة من قبل:&lt;/p>
&lt;ol>
&lt;li>قم بتثبيت &lt;a href="https://www.7-zip.org/">7zip&lt;/a> على جهاز الكمبيوتر الخاص بك إذا لم يكن لديك.&lt;/li>
&lt;li>قم بتثبيت &lt;a href="https://www.python.org/downloads/">Python (الإصدار 2.x)&lt;/a> على جهاز الكمبيوتر الخاص بك إذا لم يكن لديك.
&lt;ul>
&lt;li>هام: أحدث إصدار من لغة Python هو 3.x. لكنك تحتاج إلى الإصدار 2.x لأن البرنامج النصي العميد غير متوافق مع الإصدار 3.x.&lt;/li>
&lt;li>(اختياري) لا يضيف برنامج التثبيت، افتراضيًا، python.exe إلى المسار الخاص بك. إذا كنت تريد، تحتاج إلى تمكين هذا الخيار. (انظر لقطة الشاشة على اليمين)&lt;/li>
&lt;li>إذا كان لديك بالفعل إصدار آخر من Python، فمن المحتمل أنك لا تريد تمكين هذا الخيار.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>قم بتنزيل العميد (برنامج نصي بلغة Python يساعدك على تنزيل أحدث إصدار من Boot Camp).&lt;/li>
&lt;li>يرجى النقر بزر الماوس الأيمن على الرابط التالي وحفظ الملف باستخدام &amp;ldquo;حفظ الرابط باسم&amp;hellip;&amp;rdquo;. &lt;a href="https://raw.githubusercontent.com/timsutton/brigadier/master/brigadier">https://raw.githubusercontent.com/timsutton/brigadier/master/brigadier&lt;/a>&lt;/li>
&lt;li>افتح نافذة موجه الأوامر (ويعرف أيضًا باسم مربع DOS) وقم بتغيير الدليل إلى المكان الذي قمت بتنزيل البرنامج النصي العميد فيه.&lt;/li>
&lt;li>بافتراض أنه تم حفظ البرنامج النصي العميد باسم &amp;ldquo;brigadier.txt&amp;rdquo;، يرجى تشغيل الأمر التالي:
&lt;ul>
&lt;li>إذا كان إصدار Python 2.x موجودًا في المسار الخاص بك: python brigadier.txt &amp;ndash;model=MacBook13,2&lt;/li>
&lt;li>بخلاف ذلك: [المسار إلى إصدار Python 2.x]\python.exe brigadier.txt &amp;ndash;model=MacBook13,2&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>سيتم تنزيل حزمة كبيرة تحتوي على جميع برامج التشغيل من Bootcamp&lt;/li>
&lt;li>قم بإنشاء مجلد يسمى &lt;code>BootCamp&lt;/code> وانسخ &lt;code>BootCamp-xxx-yyyyyy\BootCamp\Drivers\Apple\BootCamp.msi&lt;/code> و &lt;code>BootCamp-xxx-yyyyyy\BootCamp\Drivers\Apple\AppleKeyboardMagic2&lt;/code> فيه.&lt;/li>
&lt;li>قم بتشغيل Powershell المسؤول وقم بتنفيذ &lt;code>BootCamp.msi&lt;/code>، وسيقوم بتثبيت بعض الأشياء ولكننا بحاجة إلى تحديث برنامج التشغيل باستخدام محتويات المجلد &lt;code>AppleKeyboardMagic2&lt;/code>&lt;/li>
&lt;li>ابدأ تشغيل إدارة الأجهزة (&lt;code>devmgmt.msc&lt;/code>)&lt;/li>
&lt;li>قم بتوسيع العقدة &lt;code>Human Interface Devices&lt;/code>&lt;/li>
&lt;li>ابحث عن &lt;code>Bluetooth HID Device&lt;/code>&lt;/li>
&lt;li>قم بتحديث برنامج التشغيل باستخدام محتويات المجلد &lt;code>AppleKeyboardMagic2&lt;/code>.&lt;/li>
&lt;li>إعادة تشغيل الكمبيوتر&lt;/li>
&lt;/ol>
&lt;p>من المفترض أن تشاهد الآن لوحة مفاتيح البلوتوث التي تم اكتشافها على أنها لوحة مفاتيح Apple&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/278f6bd3e419d6688ccfadf6918ff309">&lt;img src="https://i.gyazo.com/278f6bd3e419d6688ccfadf6918ff309.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h3 id="تحديث-سلوك-مفاتيح-fnإذا-قمت-بتثبيت-كل-شيء-بشكل-صحيح-ستلاحظ-أن-مفاتيح-fn-ممكنة-افتراضيا-فهذا-يعني-أنك-بحاجة-إلى-الضغط-على-fn--f5-للضغط-فعليا-على-الزر-f5">تحديث سلوك مفاتيح FNإذا قمت بتثبيت كل شيء بشكل صحيح ستلاحظ أن مفاتيح FN ممكّنة افتراضيًا، فهذا يعني أنك بحاجة إلى الضغط على &lt;code>fn&lt;/code> + &lt;code>F5&lt;/code> للضغط فعليًا على الزر &lt;code>F5&lt;/code>.&lt;/h3>
&lt;p>لإصلاح ذلك، وجدت حلاً، مذكورًا في قسم الوثائق، يعمل عن طريق تغيير بعض الإدخالات في regedit.&lt;/p>
&lt;ol>
&lt;li>افتح رجديت&lt;/li>
&lt;li>انتقل إلى &lt;code>HKEY_CURRENT_USER\SOFTWARE\Apple Inc.\Apple Keyboard Support&lt;/code>&lt;/li>
&lt;li>قم بإنشاء أو تحديث &lt;code>OSXFnBehavior&lt;/code> وتعيينه على &lt;code>0&lt;/code>&lt;/li>
&lt;li>إعادة تشغيل الكمبيوتر&lt;/li>
&lt;/ol>
&lt;h3 id="تحديث-تعيين-المفاتيح">تحديث تعيين المفاتيح&lt;/h3>
&lt;p>إذا كانت لديك مشكلة في التعيينات، فيمكنك استخدام &lt;a href="https://www.randyrants.com/category/sharpkeys/">SharpKeys&lt;/a> وتحديثها.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ee0301205ffeddaae4241db40002864d">&lt;img src="https://i.gyazo.com/ee0301205ffeddaae4241db40002864d.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>إنه حقًا سهل الاستخدام، ولكن تذكر تسجيل الخروج أو إعادة تشغيل الكمبيوتر لتمكين التحديثات، لأنه يقوم بتحديث السجل.&lt;/p>
&lt;p>بالنسبة لحالتي، كان علي تحديث المفاتيح &lt;code>Windows&lt;/code>، &lt;code>alt&lt;/code>، &lt;code>º&lt;/code> و &lt;code>&amp;lt;&amp;gt;&lt;/code>.&lt;/p></content:encoded></item><item><title>إضافة المكتبات المترجمة إلى حزمة NuGet</title><link>https://emimontesdeoca.github.io/ar/posts/adding-compiled-dll-to-nuget/</link><pubDate>Thu, 01 Oct 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/adding-compiled-dll-to-nuget/</guid><description>قم بإصلاح ملفات DLL المفقودة في حزم NuGet من خلال تضمين مراجع المكتبة المترجمة في ملف nuspec.</description><content:encoded>&lt;p>منذ بضعة أيام وجدت خطأً بشأن &lt;code>NullReferenceException&lt;/code> في مكتبة مفقودة كان من المفترض أن يكون موجودًا لأنه يأتي من حزمة nuget التي قمنا بتحميلها.&lt;/p>
&lt;p>في الأساس، قمت بتجميع فصل مكتبة يشير إلى بعض المشاريع. في مرحلة الإنشاء، سيتم إضافة ملفات &lt;code>dll&lt;/code> إلى المجلد &lt;code>bin&lt;/code> ولكن عندما يكون &lt;code>packaging&lt;/code> كحزمة nuget وتثبيتها في حل آخر، فإن ملفات &lt;code>dll&lt;/code> التي أشرت إليها والتي يجب نسخها إلى مجلد bin، ليست موجودة.&lt;/p>
&lt;h3 id="كيفية-حلها">كيفية حلها&lt;/h3>
&lt;p>حل هذه المشكلة بسيط جدًا، يجب عليك تحديث ملف &lt;code>nuspec&lt;/code> ولكل مكتبة تريد نسخها، أضف &lt;code>file&lt;/code> في الجزء &lt;code>files&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;package&lt;/span> &lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;metadata&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;id&amp;gt;&lt;/span>$id$&lt;span style="color:#7ee787">&amp;lt;/id&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;version&amp;gt;&lt;/span>$version$&lt;span style="color:#7ee787">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;title&amp;gt;&lt;/span>$title$&lt;span style="color:#7ee787">&amp;lt;/title&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;authors&amp;gt;&lt;/span>$author$&lt;span style="color:#7ee787">&amp;lt;/authors&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;owners&amp;gt;&lt;/span>$author$&lt;span style="color:#7ee787">&amp;lt;/owners&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;requireLicenseAcceptance&amp;gt;&lt;/span>false&lt;span style="color:#7ee787">&amp;lt;/requireLicenseAcceptance&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;license&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;expression&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>MIT&lt;span style="color:#7ee787">&amp;lt;/license&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;projectUrl&amp;gt;&lt;/span>$projectUrl$&lt;span style="color:#7ee787">&amp;lt;/projectUrl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;iconUrl&amp;gt;&lt;/span>$projectIconUrl$&lt;span style="color:#7ee787">&amp;lt;/iconUrl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;description&amp;gt;&lt;/span>$description$&lt;span style="color:#7ee787">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;releaseNotes&amp;gt;&amp;lt;/releaseNotes&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;copyright&amp;gt;&lt;/span>$copyright$&lt;span style="color:#7ee787">&amp;lt;/copyright&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;tags&amp;gt;&amp;lt;/tags&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/metadata&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;files&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile1.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile2.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile3.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;file&lt;/span> src=&lt;span style="color:#a5d6ff">&amp;#34;bin\Release\MyFile4.dll&amp;#34;&lt;/span> target=&lt;span style="color:#a5d6ff">&amp;#34;lib\net47&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/files&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/package&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content:encoded><category>NuGet</category></item><item><title>قم بإجراء bash تحميل المحطة الطرفية على الدليل الرئيسي لـ WSL</title><link>https://emimontesdeoca.github.io/ar/posts/bash-straight-to-wsl-machine-with-terminal/</link><pubDate>Tue, 22 Sep 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/bash-straight-to-wsl-machine-with-terminal/</guid><description>قم بتكوين Windows Terminal لفتح جلسات WSL مباشرة في دليل Linux الرئيسي عبر .bashrc.</description><content:encoded>&lt;p>كنت أحب WSL للتطوير والاختبار منذ يوم ظهوره، ثم توقف التطبيق الطرفي ولم أستطع أن أكون أكثر سعادة، فهو يبدو جيدًا ويعمل بشكل مثالي وأداء رائع.&lt;/p>
&lt;p>سيكون من الغباء عدم استخدامه مع توزيعة WSL بدلاً من استخدام وحدة التحكم التي تمنحك.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/245fb4ff4fbd5297b2a8d9917dbee236">&lt;img src="https://i.gyazo.com/245fb4ff4fbd5297b2a8d9917dbee236.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>من الواضح أن وحدة التحكم هذه جيدة، ولكن بعد استخدام الوحدة الطرفية، لن يكون هناك مجال للتراجع. إنه أفضل بكثير، وكذلك &lt;em>c o l o r s&lt;/em>.&lt;/p>
&lt;p>الآن، إذا قمت بتشغيل الجهاز وفتح علامة تبويب جديدة لتحميل توزيعة WSL الخاصة بك، فسيتم تحميل مجلد المستخدم المثبت لديك.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/dac1264f7b8dbae1f64e769b64c551da">&lt;img src="https://i.gyazo.com/dac1264f7b8dbae1f64e769b64c551da.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>هذه ليست مشكلة حقًا، لأنه إذا قمت فقط بـ &lt;code>cd ~&lt;/code>، فسيتم فقط تحميل مجلد مستخدم التوزيعة.&lt;/p>
&lt;p>الأمر هو أنني لا أريد القيام بذلك مرارًا وتكرارًا، لذا دعونا نقوم بتحديث الملف للقيام بذلك بنفسه.&lt;/p>
&lt;h2 id="باشرك">.باشرك&lt;/h2>
&lt;p>قم بتشغيل الجهاز أو وحدة التحكم، وانتقل إلى مجلد ملف التعريف الخاص بك ثم قم بتحرير ملف &lt;code>.bashrc&lt;/code> باستخدام محرر النصوص المفضل لديك.&lt;/p>
&lt;p>أضف &lt;code>cd ~&lt;/code> في نهاية الملف قبل التعليمات الأخيرة.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/b601aba59b9e877bc3926ab9ceb2b98c">&lt;img src="https://i.gyazo.com/b601aba59b9e877bc3926ab9ceb2b98c.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>احفظ وأعد فتح وحدة تحكم WSL باستخدام الجهاز ويجب أن تكون في مجلد الملفات الشخصية.&lt;/p>
&lt;p>ضع في اعتبارك أنه سيتعين عليك القيام بذلك لجميع عمليات تثبيت WSL الخاصة بك، نظرًا لأننا نقوم بتحديث ملف &lt;code>bashrc&lt;/code> لملف واحد.&lt;/p></content:encoded></item><item><title>إنشاء كائن ديناميكي باستخدام ExpandoObject</title><link>https://emimontesdeoca.github.io/ar/posts/dynamic-object-generation-expando-object/</link><pubDate>Fri, 06 Mar 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/dynamic-object-generation-expando-object/</guid><description>استخدم ExpandoObject لإنشاء كائنات ديناميكيًا بخصائص محددة في وقت التشغيل لتصدير البيانات المرنة.</description><content:encoded>&lt;p>لقد كنت في حاجة إلى تحويل ملفات Excel ذات الأعمدة المحددة الخاصة بها إلى ملف جديد ذو أعمدة ديناميكية، وكنت في حيرة من أمري بشأن كيفية القيام بذلك بشكل صحيح دون بعض مشكلات الأداء الخطيرة.&lt;/p>
&lt;p>توصلت إلى برنامج تعليمي يمكنك العثور عليه &lt;a href="https://www.oreilly.com/content/building-c-objects-dynamically/">هنا&lt;/a> يستخدم .NET &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.dynamic.expandoobject?view=netframework-4.8">ExpandoObject&lt;/a> والذي يتيح لك إلى حد كبير إنشاء كائن وإضافة أعضاء ديناميكيين.&lt;/p>
&lt;h2 id="كائن-اكسباندو">كائن اكسباندو&lt;/h2>
&lt;p>تعريف مايكروسوفت هو:&lt;/p>
&lt;blockquote>
&lt;p>يمثل كائنًا يمكن إضافة أعضائه وإزالتهم ديناميكيًا في وقت التشغيل.&lt;/p>&lt;/blockquote>
&lt;p>وله بعض الملاحظات:&lt;/p>
&lt;blockquote>
&lt;p>تمكنك فئة ExpandoObject من إضافة وحذف أعضاء مثيلاتها في وقت التشغيل وأيضًا تعيين قيم هؤلاء الأعضاء والحصول عليها. تدعم هذه الفئة الربط الديناميكي، والذي يمكّنك من استخدام بناء الجملة القياسي مثل SampleObject.sampleMember بدلاً من بناء الجملة الأكثر تعقيدًا مثل SampleObject.GetAttribute(&amp;ldquo;sampleMember&amp;rdquo;).&lt;/p>&lt;/blockquote>
&lt;h2 id="السلوك-الحالي">السلوك الحالي&lt;/h2>
&lt;p>لدينا &lt;code>ExcelExportService&lt;/code> والذي، من خلال تمرير &lt;code>List&amp;lt;T&amp;gt;&lt;/code>، في هذه الحالة &lt;code>ExcelItem&lt;/code>، سيستخدم &lt;code>Reflection&lt;/code> لإنشاء ملف &lt;code>xlsx&lt;/code>.&lt;/p>
&lt;p>حتى الآن، يبدو الكود الخاص بنا كما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">ExcelItem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Surname { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Age { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> CreatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> UpdatedAt { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>ExcelItem&lt;/code> هو الكائن الذي يحتوي على جميع الخصائص المستخدمة لإنشاء ملف Excel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">byte&lt;/span>[] GetExcelBytes() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;ExcelItem&amp;gt; excelItems = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;ExcelItem&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExcelExportService exportService = &lt;span style="color:#ff7b72">new&lt;/span> ExcelExportService();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> items &lt;span style="color:#ff7b72">in&lt;/span> itemsToExcel)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> excelItems.Add(&lt;span style="color:#ff7b72">new&lt;/span> ExcelItem()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Id = item.Id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name = item.Name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Surname = item.Surname,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Age = item.Age,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CreatedAt = item.CreatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UpdatedAt = item.UpdatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> exportService.ExportToExcel(excelItems);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يعمل هذا الأسلوب بشكل مثالي، حيث يتم إنشاء الملف &lt;code>xlsx&lt;/code> دون مشاكل حيث يكون كل عمود هو كل خاصية، يمكنك استخدام طريقة &lt;code>GetExcelBytes&lt;/code> مثل هذا، على سبيل المثال، لحفظه في ملف:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>File.WriteAllBytes(&lt;span style="color:#a5d6ff">&amp;#34;path-to-somewhere/my-file.xlsx&amp;#34;&lt;/span>, GetExcelBytes());
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="المشكلة">المشكلة&lt;/h2>
&lt;p>نظرًا لأن ما نفعله بشكل صحيح هو استخدام جميع خصائص الكائن &lt;code>ExcelItem&lt;/code>، فكل ما أريده هو عدم استخدام كل هذه الخصائص، فقط ربما استخدم 2 أو 3 منها.&lt;/p>
&lt;p>من المتطلبات أيضًا أنني لا أريد تغيير الكود، يجب أن يتم كل شيء بواسطة &lt;code>administrator&lt;/code>، الذي سيقرر الأعمدة التي يجب أن تظهر، وقد يعرف أو لا يعرف الخصائص الموجودة في الكود.&lt;/p>
&lt;p>TLDR: يجب أن يكون كل شيء ديناميكيًا، ولدينا كائن ذو خصائص ويجب علينا التأكد من أن الملف الذي تم إنشاؤه يحتوي على خصائص &lt;code>N&lt;/code> لذلك الكائن، ولكن من الواضح أنه ليس مشفرًا.&lt;/p>
&lt;h2 id="الأعمدة-الديناميكية">الأعمدة الديناميكية&lt;/h2>
&lt;p>سيكون التغيير هو استخدام كائن &lt;code>dynamic&lt;/code>، لأننا نرغب في تعيين خصائص الكائن التي سيتم استخدامها لإنشاء قائمة الأعمدة.&lt;/p>
&lt;p>لنفترض أن لدينا كائنًا يحتوي على الكثير من الخصائص، مثل &lt;em>طن&lt;/em> منها، ولا نريد حقًا تغيير كائن &lt;code>ExcelItem&lt;/code> في كل مرة نقوم فيها بإجراء تغيير، نقوم بإنشاء جدول &lt;code>ColumnExcelItem&lt;/code>، والذي سيتم استخدامه لإنشاء هذا &lt;code>ExcelItem&lt;/code>.&lt;/p>
&lt;h3 id="هيكل-قاعدة-البيانات">هيكل قاعدة البيانات&lt;/h3>
&lt;p>نحفظ هذا التعريف في قاعدة البيانات الخاصة بنا بشيء من هذا القبيل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Id PropertyName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 Name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 Surname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3 Age
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لاحظ أن لدينا قيمًا أقل من الخصائص الموجودة لكائن &lt;code>ExcelItem&lt;/code>، حيث يحتوي على خصائص &lt;code>6&lt;/code> ولدينا فقط &lt;code>3&lt;/code> مدرج في قاعدة البيانات.&lt;/p>
&lt;p>لاحظ أيضًا أن &lt;code>PropertyName&lt;/code> يجب أن يتطابق مع اسم خاصية الكائن الذي سأستخدمه للمهمة الديناميكية.&lt;/p>
&lt;h2 id="بناء-الكائن">بناء الكائن&lt;/h2>
&lt;p>الآن بعد أن أصبح لدينا جدول قاعدة البيانات، نحتاج إلى إنشاء المستودع للحصول عليه ولنكون قادرين على استخدامه في الكود.لن أقوم بإنشاء برنامج تعليمي لهذا الجزء، وسأقوم بالتخطي مباشرة عند بداية وظيفة &lt;code>GetExcelBytes()&lt;/code> الجديدة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">byte&lt;/span>[] GetExcelBytes(List&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; columns) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;ExcelItem&amp;gt; excelItems = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;ExcelItem&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&amp;lt;&lt;span style="color:#ff7b72">dynamic&lt;/span>&amp;gt; objectsToExcel = &lt;span style="color:#ff7b72">new&lt;/span> List&amp;lt;&lt;span style="color:#ff7b72">dynamic&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExcelExportService exportService = &lt;span style="color:#ff7b72">new&lt;/span> ExcelExportService();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72">in&lt;/span> itemsToExcel)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">dynamic&lt;/span> newObject = &lt;span style="color:#ff7b72">new&lt;/span> ExpandoObject();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> col &lt;span style="color:#ff7b72">in&lt;/span> columns)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">this&lt;/span>.AddProperty(newObject, col, product.GetType().GetProperty(col).GetValue(item, &lt;span style="color:#79c0ff">null&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> objectsToExcel.Add(newObject);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> exportService.ExportToExcel(objectsToExcel);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>سنقوم الآن بإنشاء كائن يحتوي على بعض خصائص &lt;code>ExcelItem&lt;/code>، ولكنه ديناميكي تمامًا. بدلاً من استخدام جميع الخصائص الستة، نستخدم فقط ثلاثة منها، وهي الخاصية التي نريد إرسالها فقط.&lt;/p>
&lt;p>الآن لنفترض أن هذا الكائن &lt;code>ExcelItem&lt;/code> يحتوي على 200 خاصية، &lt;em>مجنون&lt;/em>، ولكن من الممكن أن يحدث ذلك.&lt;/p>
&lt;p>الشيء الوحيد الذي يجب علي فعله هو إدراج تلك الخصائص التي أريد عرضها في ملف Excel إلى جدول &lt;code>ColumnExcelItem&lt;/code> وهذا كل شيء.&lt;/p>
&lt;p>##حالات&lt;/p>
&lt;p>في هذه الحالة، استخدمنا حالة واحدة فقط، ولكن لنفترض أنك تريد الحصول على تقارير مختلفة لبعض المستخدمين. لنفترض أن الدور &lt;code>admin&lt;/code> يجب أن يحصل على جميع الخصائص، ثم ستقوم بإنشاء شيء مثل هذه البنية في قاعدة البيانات&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>ReportingTemplates
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Id PropertyName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 Admin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 Users
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>ReportingTemplateItems
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Id TemplateId PropertyName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1 1 Id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2 1 Name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3 1 Surname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>4 1 Age
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>5 1 CreatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>6 1 UpdatedAt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>7 2 Name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>8 2 Surname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>9 2 Age
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>بعد ذلك، من خلال الحصول على القالب، يمكن أن يكون لديك أعمدة مختلفة، كلها ديناميكيًا ولا تتعلق بأي تعليمات برمجية.&lt;/p>
&lt;p>إذا كان لدينا مستخدم بالدور &lt;code>Admins&lt;/code>، فسيحتوي الملف على أعمدة &lt;code>Id&lt;/code>، &lt;code>Name&lt;/code>، &lt;code>Surname&lt;/code>، &lt;code>Age&lt;/code>، &lt;code>CreatedAt&lt;/code>، &lt;code>UpdatedAt&lt;/code>.&lt;/p>
&lt;p>في حالة إنشائه باستخدام الدور &lt;code>Users&lt;/code>، سيكون له &lt;code>Name&lt;/code>، &lt;code>Age&lt;/code>. &lt;code>Surname&lt;/code>.&lt;/p>
&lt;h2 id="الخلاصة">الخلاصة&lt;/h2>
&lt;p>هذه طريقة رائعة لتعلم كيفية عمل &lt;code>dynamic&lt;/code> في .NET، وهي حل جيد حقًا عندما تحتاج إلى أدوار أو قوالب مختلفة لمستخدمين مختلفين، ولا تهتم حقًا باستثمار الوقت في أشياء البرمجة الثابتة.&lt;/p></content:encoded><category>.NET</category></item><item><title>التسليم المستمر لحزمة NuGet باستخدام TravisCI</title><link>https://emimontesdeoca.github.io/ar/posts/automated-nuget-deployment-travis-ci/</link><pubDate>Tue, 21 Jan 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/automated-nuget-deployment-travis-ci/</guid><description>أتمتة تجميع حزمة NuGet واختبارها ونشرها باستخدام مسارات التسليم المستمر لـ Travis CI.</description><content:encoded>&lt;p>﻿لقد قمت مؤخرًا بعمل &lt;a href="https://emimontesdeoca.github.io/2020/ci-dotnet-core-and-travis-ci/">برنامج تعليمي&lt;/a> حول كيفية استخدام Travis CI كأداة لاختبار الكود الذي دفعته للتو أو حاولت دفعه إلى فرع &lt;code>master&lt;/code> تلقائيًا. مما يجعل تجميعه واختباره وإبلاغك أخيرًا بحالة هذا التغيير.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/e4c3f9019fdb8a8d80b2649f4c4bbbde">&lt;img src="https://i.gyazo.com/e4c3f9019fdb8a8d80b2649f4c4bbbde.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>ولكن يمكننا الاستمرار في تحسينها **على سبيل المثال، المكتبة الأساسية التي تم إنشاؤها في هذا البرنامج التعليمي الأخير، أريد أن أجعلها عامة باستخدام موجز &lt;a href="https://www.nuget.org/">NuGet&lt;/a>، حتى يتمكن الجميع من إجراء عملية سهلة في مكتباتهم!&lt;/p>
&lt;p>&lt;strong>ولكن كيف؟&lt;/strong> كيف سنقوم بتجميع هذه الحزمة واختبارها ثم نشرها في الموجز ليتمكن الجميع من تنزيلها في مشاريعهم؟&lt;/p>
&lt;p>حسنًا، هذا هذا البرنامج التعليمي لـ 😁!&lt;/p>
&lt;h1 id="ما-الجديد">ما الجديد؟&lt;/h1>
&lt;p>لا توجد أي تغييرات تقريبًا فيما يتعلق بالكود، والأشياء التي نحتاج إلى تغييرها هي التعبئة والنشر ومن ثم كيف يمكننا أتمتتها.&lt;/p>
&lt;h1 id="net-cli-الأساسية">.NET CLI الأساسية&lt;/h1>
&lt;p>سنستخدم &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/tools/?tabs=netcore2x">.NET Core CLI&lt;/a>، الذي استخدمناه في البرنامج التعليمي السابق مع أوامر مثل &lt;code>dotnet restore&lt;/code>، &lt;code>dotnet build&lt;/code> و &lt;code>dotnet test&lt;/code>.&lt;/p>
&lt;p>الآن سوف نستخدم مجموعة جديدة من الأوامر!&lt;/p>
&lt;h1 id="dotnet-nuget">&lt;code>dotnet nuget&lt;/code>&lt;/h1>
&lt;p>يمكنك التحقق من الوثائق الخاصة بمجموعة الأدوات هذه &lt;a href="https://docs.microsoft.com/en-gb/nuget/reference/dotnet-commands">هنا&lt;/a>، ولكن مع شرح مختصر:&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>dotnet nuget&lt;/code> عبارة عن مجموعة من الأدوات الأساسية التي تتضمن تثبيت الحزم واستعادتها وإزالتها ونشرها.&lt;/p>&lt;/blockquote>
&lt;h1 id="تغذية-نوجيت">تغذية نوجيت&lt;/h1>
&lt;p>&lt;a href="https://www.nuget.org/">NuGet هو مدير الحزم لـ .NET&lt;/a>. توفر أدوات عميل NuGet القدرة على إنتاج الحزم واستهلاكها. يعد NuGet Gallery مستودع الحزم المركزي الذي يستخدمه جميع مؤلفي الحزم والمستهلكين.&lt;/p>
&lt;p>NuGet مجاني، لذا يمكنك التسجيل والتقاط مفتاح API الخاص بك والبدء في تحميل الحزم. سيتم تحميلها والتحقق منها ثم إدراجها في &lt;strong>الجميع&lt;/strong>.&lt;/p>
&lt;h1 id="احصل-على-مفتاح-api">احصل على مفتاح API&lt;/h1>
&lt;p>لتحميل الحزمة الخاصة بك، ستحتاج إلى الحصول على مفتاح واجهة برمجة التطبيقات (API) بعد التسجيل، لذا انتقل إلى &lt;a href="https://www.nuget.org/account/apikeys">صفحة مفاتيح واجهة برمجة التطبيقات&lt;/a> وأنشئ واحدة جديدة.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/7509632471a35fb6b5b909699dec2520">&lt;img src="https://i.gyazo.com/7509632471a35fb6b5b909699dec2520.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>&lt;strong>ضع في اعتبارك أن مفتاح واجهة برمجة التطبيقات (API) تنتهي صلاحيته لمدة 365 يومًا.&lt;/strong>&lt;/p>
&lt;p>ثم انسخ مفتاحك واحفظه في مكان ما، كما هو مذكور في الصفحة، فلن تتمكن من رؤيته مرة أخرى، وإذا لم تقم بحفظه فسيتعين عليك إعادة إنشائه.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/2feb401c84034706d12a59f03bd30136">&lt;img src="https://i.gyazo.com/2feb401c84034706d12a59f03bd30136.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h1 id="إنشاء-حزمة-nuget">إنشاء حزمة NuGet&lt;/h1>
&lt;p>الآن بعد أن حصلنا على المفتاح &lt;code>API&lt;/code>، فإن خطوتنا التالية هي إنشاء حزمة NuGet من الحل، لذا تابع وافتح الحل حيث توجد مكتبة الفصل الخاصة بك.&lt;/p>
&lt;p>سأستخدم الحل من &lt;a href="https://emimontesdeoca.github.io/2020/ci-dotnet-core-and-travis-ci/">البرنامج التعليمي الأخير&lt;/a>، والذي يحتوي على فئة مكتبة .NET Core تسمى &lt;code>CalculatorCLI.Core&lt;/code>.&lt;/p>
&lt;p>انتقل الآن إلى &lt;strong>خصائص المشروع&lt;/strong> ثم &lt;strong>الحزمة&lt;/strong>، وسترى الكثير من المعلومات المتعلقة بالحزمة مثل إصدار الحزمة والمؤلفين والأوصاف والمزيد.&lt;/p>
&lt;p>التالي هو ملء هذا، ليس عليك حقًا ملء كل شيء، ولكن العناصر المهمة والإلزامية هي &lt;code>Package id&lt;/code>، &lt;code>Package version&lt;/code>، &lt;code>Authors&lt;/code> و &lt;code>Description&lt;/code>. وأيضًا إذا حاولت تحميل الملف بنفسك، فسوف يطلب &lt;code>License&lt;/code>، وسنحتاج إلى إضافته أيضًا.&lt;a href="https://gyazo.com/6becdace2915c40fa2091ed9916fe517">&lt;img src="https://i.gyazo.com/6becdace2915c40fa2091ed9916fe517.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>ثم يمكنك الحفظ، انقر بزر الماوس الأيمن في المشروع وحدد &lt;code>Pack&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">1&lt;/span>&amp;gt;&lt;span style="color:#ff7b72;font-weight:bold">------&lt;/span> Build started: Project: CalculatorCLI.Core, Configuration: Debug Any CPU &lt;span style="color:#ff7b72;font-weight:bold">------&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">1&lt;/span>&amp;gt;CalculatorCLI.Core &lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>&amp;gt; D:&lt;span style="color:#f85149">\&lt;/span>Development&lt;span style="color:#f85149">\&lt;/span>Personal&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>demo&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core&lt;span style="color:#f85149">\&lt;/span>bin&lt;span style="color:#f85149">\&lt;/span>Debug&lt;span style="color:#f85149">\&lt;/span>netstandard2&lt;span style="color:#a5d6ff">.1&lt;/span>&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core.dll
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a5d6ff">1&lt;/span>&amp;gt;Successfully created &lt;span style="color:#ff7b72">package&lt;/span> &lt;span style="color:#f85149">&amp;#39;&lt;/span>D:&lt;span style="color:#f85149">\&lt;/span>Development&lt;span style="color:#f85149">\&lt;/span>Personal&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>demo&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core&lt;span style="color:#f85149">\&lt;/span>bin&lt;span style="color:#f85149">\&lt;/span>Debug&lt;span style="color:#f85149">\&lt;/span>CalculatorCLI.Core&lt;span style="color:#a5d6ff">.1.0.0.1&lt;/span>.nupkg&lt;span style="color:#f85149">&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72;font-weight:bold">==========&lt;/span> Build: &lt;span style="color:#a5d6ff">1&lt;/span> succeeded, &lt;span style="color:#a5d6ff">0&lt;/span> failed, &lt;span style="color:#a5d6ff">0&lt;/span> up&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>to&lt;span style="color:#ff7b72;font-weight:bold">-&lt;/span>date, &lt;span style="color:#a5d6ff">0&lt;/span> skipped &lt;span style="color:#ff7b72;font-weight:bold">==========&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>سيُظهر الإخراج أنه إذا كان كل شيء على ما يرام، فسيتم طباعة &lt;code>Successfully created package&lt;/code>.&lt;/p>
&lt;h1 id="استخدام-dotnet">استخدام &lt;code>dotnet&lt;/code>&lt;/h1>
&lt;p>كما في البرنامج التعليمي السابق، استخدمنا أوامر مختلفة لبناء الحل واختباره. الآن، سنقوم بإضافة المزيد من الأوامر لحزم الحزم ونشرها.&lt;/p>
&lt;p>تلك الأوامر هي:&lt;/p>
&lt;ol>
&lt;li>&lt;code>dotnet pack -c &amp;lt;configuration&amp;gt;&lt;/code>&lt;/li>
&lt;li>&lt;code>dotnet nuget push &amp;lt;package&amp;gt; &amp;lt;k &amp;lt;apikey&amp;gt; -s &amp;lt;source&amp;gt;&lt;/code>&lt;/li>
&lt;/ol>
&lt;h1 id="البرامج-النصية-للنشر">البرامج النصية للنشر&lt;/h1>
&lt;p>نظرًا لأننا سنقوم بتغيير الطريقة التي سيكون بها البناء، فأنا أعتقد أن وجود ملفات مختلفة، كل منها يحتوي على تعريف بناء اعتمادًا على البيئة، سيكون أفضل فكرة.&lt;/p>
&lt;p>لذلك دعونا ننشئ مجلدًا يسمى &lt;code>scripts&lt;/code> بثلاثة نصوص &lt;code>bash&lt;/code> في جذر المستودع.&lt;/p>
&lt;p>&lt;code>scripts\compile.sh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Restoring...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet restore &lt;span style="color:#a5d6ff">&amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Building...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet build &lt;span style="color:#a5d6ff">&amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span> -c Release
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>scripts\test.sh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Testing...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet test &lt;span style="color:#a5d6ff">&amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span> -c Release -v n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>scripts\push.sh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Packing...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet pack ./CalculatorCLI/CalculatorCLI.Core/CalculatorCLI.Core.csproj -c Release
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#a5d6ff">&amp;#34;Pushing...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet nuget push ./CalculatorCLI/CalculatorCLI.Core/bin/Release/*.nupkg -s &lt;span style="color:#a5d6ff">&amp;#34;https://nuget.org&amp;#34;&lt;/span> -k &lt;span style="color:#79c0ff">$NUGET_API_KEY&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="تحسين-ملف-travisyml">تحسين ملف &lt;code>.travis.yml&lt;/code>&lt;/h1>
&lt;p>الآن بعد أن قمنا بتحديث الكود المصدري، أصبحنا نعرف الأوامر التي نحتاج إلى استخدامها، ونحتاج إلى دمج التسليم المستمر مع التكامل المستمر. وبهذا لا يتعين علينا فعل أي شيء سوى البرمجة والاختبار والمراجعة والدفع.&lt;/p>
&lt;p>ما سنفعله الآن بالملف &lt;code>.travis.yml&lt;/code> هو ما يلي:&lt;/p>
&lt;ol>
&lt;li>أضف &lt;code>stages&lt;/code> مختلفًا&lt;/li>
&lt;li>كل &lt;code>stage&lt;/code> يعتمد على الفرع&lt;/li>
&lt;li>إذا كان الإصدار الخاص بك هو الإصدار الرئيسي، فهذا يعني أنه يجب تحديث الحزمة، لذلك سنقوم بدفع الحزمة الخاصة بنا إلى خلاصات NuGet&lt;/li>
&lt;li>إذا كان البناء عبارة عن طلب سحب، فمازلنا سنتحقق مما إذا كان البناء قد تم تجميعه واختباره، ولكننا لن ننشره.&lt;/li>
&lt;/ol>
&lt;h2 id="المراحل">المراحل&lt;/h2>
&lt;p>من وثائقهم:&lt;/p>
&lt;blockquote>
&lt;p>يمكنك تصفية ورفض الإصدارات والمراحل والوظائف عن طريق تحديد الشروط في تكوين الإصدار الخاص بك (ملف .travis.yml الخاص بك).&lt;/p>&lt;/blockquote>
&lt;p>يمكنك العثور على مزيد من المعلومات بخصوص TravisCI &lt;code>stages&lt;/code> في &lt;a href="https://docs.travis-ci.com/user/conditional-builds-stages-jobs">صفحة الوثائق الخاصة بهم&lt;/a>.&lt;/p>
&lt;p>لذلك سنقوم بتغيير الملف وإضافة المراحل التي تنتهي بهذا الشكل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">language&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">csharp&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">sudo&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">required&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">mono&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">none &lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">dotnet&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">3.0&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">os&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">linux&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">jobs&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">include&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">stage&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">compile&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bash scripts/compile.sh&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">stage&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">test&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bash scripts/test.sh&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#7ee787">stage&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">deploy-prod&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">if&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">branch = master AND type = push&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">name&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">&amp;#34;Deploy to NuGet&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">bash scripts/push.sh&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كما ترون لدينا ثلاث مراحل مختلفة &lt;code>compile&lt;/code>، &lt;code>test&lt;/code> و &lt;code>push&lt;/code>، والتي سيتم تشغيل المرحلة الأخيرة فقط عندما يكون &lt;code>branch&lt;/code> هو &lt;code>master&lt;/code> وليس &lt;code>pull request&lt;/code> لها، فقط &lt;code>push&lt;/code>.&lt;/p>
&lt;p>إذا لم تفهم هذا الأمر، فما عليك سوى الانتقال إلى الوثائق وسيتم شرحه بسهولة تامة.&lt;/p>
&lt;p>مع أخذ ذلك في الاعتبار، لن يقوم كل &lt;code>pull request&lt;/code> بدفع أي شيء إلى موجز NuGet.&lt;/p>
&lt;h1 id="تعيين-مفتاح-api-كمتغير-بيئة">تعيين مفتاح API كمتغير بيئة&lt;/h1>
&lt;p>انتقل إلى إعدادات إنشاء المستودع الخاص بك وأضف متغير بيئة جديد يسمى &lt;code>NUGET_API_KEY&lt;/code> مع القيمة التي تمثل مفتاح واجهة برمجة التطبيقات المنسوخ من صفحة NuGet.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/68a4bcc4ce57eccd6bb25b7d62901c84">&lt;img src="https://i.gyazo.com/68a4bcc4ce57eccd6bb25b7d62901c84.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="إنشاء-pull-request">إنشاء &lt;code>pull request&lt;/code>&lt;/h2>
&lt;p>الآن بعد أن تم إعداد كل شيء، حان الوقت لإجراء &lt;code>pull request&lt;/code> والتحقق مما إذا كان التجميع لطلب السحب هذا يتجاهل آخر &lt;code>stage&lt;/code>.&lt;/p>
&lt;h2 id="التحقق-من-البنيات">التحقق من البنيات&lt;/h2>
&lt;p>بمجرد قيامك بتقديم طلب السحب، سيتم وضع البنية في قائمة الانتظار في لوحة معلومات TravisCI، وكما ترى أن لدينا نسختين مختلفتين هناك وليس ثلاثة.&lt;a href="https://gyazo.com/11a209e72a121aef82a5b5a80d77a2f0">&lt;img src="https://i.gyazo.com/11a209e72a121aef82a5b5a80d77a2f0.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>الآن دعونا نقبل &lt;code>pull request&lt;/code> ونتحقق من البناء مرة أخرى لنرى أن لدينا الآن ثلاث وظائف بدلاً من اثنتين.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/46dad20d00defb8c14279332a946e73e">&lt;img src="https://i.gyazo.com/46dad20d00defb8c14279332a946e73e.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>وبمجرد أن ترى البناء في قائمة الانتظار، يمكنك رؤية المهام الثلاث بما في ذلك &lt;code>Deploy-prod&lt;/code> في النهاية.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/d24344e42f2b45225acb618ac030fd8f">&lt;img src="https://i.gyazo.com/d24344e42f2b45225acb618ac030fd8f.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>بمجرد اكتمال هذا البناء بالكامل، يمكنك أن ترى في السجلات أنه تم دفع الحزمة إلى المصادر.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/77b1e44b4f9059cb7266fb18fbace8a7">&lt;img src="https://i.gyazo.com/77b1e44b4f9059cb7266fb18fbace8a7.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>ومن الواضح أنه يمكنك رؤيته في صفحة NuGet&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/09421094730ea2e087cc5da639069ec4">&lt;img src="https://i.gyazo.com/09421094730ea2e087cc5da639069ec4.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/a7905ddfd6665bb6e8e62e160f21630d">&lt;img src="https://i.gyazo.com/a7905ddfd6665bb6e8e62e160f21630d.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h1 id="هذا-كل-شيء">هذا كل شيء&lt;/h1>
&lt;p>اكتشفنا في هذا البرنامج التعليمي كيفية أتمتة تسليم حزم NuGet الخاصة بنا. استخدمنا بعض الأدوات مثل &lt;code>TravisCI&lt;/code>، &lt;code>stages&lt;/code> و &lt;code>dotnet nuget&lt;/code>.&lt;/p>
&lt;p>في رأيي، حتى لو كان الأمر قد يستغرق وقتًا لإعداده بشكل صحيح ويمكن أن يكون معقدًا للغاية في بعض الحالات. استثمار الوقت في هذا النوع من التقنيات من أجل ترك كل شيء مثاليًا وآليًا يستحق العناء.&lt;/p>
&lt;p>يمكنك العثور على الكود المصدري، مع ملف &lt;code>.travis.yml&lt;/code> مباشرة &lt;a href="https://github.com/emimontesdeoca/CalculatorCLI-demo">هنا&lt;/a>، إذا كانت لديك أي أسئلة فلا تتردد في الاتصال بي على &lt;a href="https://twitter.com/emimontesdeocaa">twitter&lt;/a>!&lt;/p></content:encoded><category>.NET</category><category>NuGet</category><category>CI/CD</category></item><item><title>التكامل المستمر لمشروع .NET Core 3.0 باستخدام TravisCI</title><link>https://emimontesdeoca.github.io/ar/posts/ci-dotnet-core-and-travis-ci/</link><pubDate>Tue, 14 Jan 2020 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/ci-dotnet-core-and-travis-ci/</guid><description>قم بإعداد التكامل المستمر لمشاريع .NET Core باستخدام Travis CI مع الإصدارات والاختبارات الآلية.</description><content:encoded>&lt;p>قررت في نهاية الأسبوع الماضي أنني أريد أن أبدأ بشكل صحيح مشروع أداة تنزيل الكاشطة والمدقق الذي كنت أقوم به في مستودعات مختلفة.&lt;/p>
&lt;p>بعد البدء بمشروع آخر، يجب أن يكون هذا رائعًا، &lt;strong>مثل الرائع الحقيقي&lt;/strong>، باستخدام CI/CD، وطلب السحب، والوثائق، والشارات في الملف التمهيدي، وكل ما رأيته رائعًا وهو بالفعل أفضل الممارسات.&lt;/p>
&lt;p>وبعد عطلة نهاية أسبوع جيدة، انتهيت من إنشاء &lt;a href="https://github.com/Dramarr">Dramarr&lt;/a>، وهي مجموعة من الأدوات التي تقوم بإلغاء العروض وتنزيلها من مصادر مختلفة.&lt;/p>
&lt;p>يحتوي على مستودعات مختلفة في المؤسسة ومعظمها عبارة عن مكتبات يتم تجميعها واختبارها ونشرها في طلب السحب وعند دمجها في الفرع الرئيسي.&lt;/p>
&lt;p>وهذا ما يطلق عليه CI/CD أو التكامل المستمر/التسليم المستمر.&lt;/p>
&lt;p>ولكن في هذا البرنامج التعليمي سنتحدث فقط عن CI.&lt;/p>
&lt;h1 id="التكامل-المستمر">التكامل المستمر&lt;/h1>
&lt;h2 id="ما-هو">ما هو؟&lt;/h2>
&lt;p>مأخوذة من [مدونة] مارتن فاولر(&lt;a href="https://martinfowler.com/articles/continuousIntegration.html">https://martinfowler.com/articles/continuousIntegration.html&lt;/a>)، وهو أفضل شرح قرأته:&lt;/p>
&lt;blockquote>
&lt;p>التكامل المستمر هو ممارسة لتطوير البرمجيات حيث يقوم أعضاء الفريق بدمج عملهم بشكل متكرر، وعادةً ما يتكامل كل شخص يوميًا على الأقل - مما يؤدي إلى عمليات تكامل متعددة يوميًا.
يتم التحقق من كل تكامل من خلال إنشاء تلقائي (بما في ذلك الاختبار) لاكتشاف أخطاء التكامل في أسرع وقت ممكن.&lt;/p>&lt;/blockquote>
&lt;h2 id="الأدوات">الأدوات&lt;/h2>
&lt;p>هناك العديد من الأدوات لدمج سير العمل الخاص بك مع CI/CD، ولكن في هذا البرنامج التعليمي سنستخدم &lt;a href="https://github.com/">Github&lt;/a> لتخزين التعليمات البرمجية الخاصة بنا وأدوات &lt;a href="https://travis-ci.org/">TravisCI&lt;/a> لإعداد CI. فيما يتعلق باللغة والأطر، سوف نستخدم C# وnew.NET Core 3.0.&lt;/p>
&lt;p>#المتطلبات&lt;/p>
&lt;p>لإنجاز هذا العمل، تحتاج إلى ثلاثة أشياء بسيطة:&lt;/p>
&lt;ol>
&lt;li>أحدث إصدار من فيجوال ستوديو 2019&lt;/li>
&lt;li>حساب جيثب&lt;/li>
&lt;li>حساب Travis-CI مرتبط بحساب Github الخاص بك&lt;/li>
&lt;/ol>
&lt;p>#مشروع&lt;/p>
&lt;p>من أجل هذا البرنامج التعليمي، سنقوم بعمل آلة حاسبة بسيطة. سنقوم بإنشاء مكتبة وأداة سطر أوامر ومشروع اختبار لاختبار كل شيء.&lt;/p>
&lt;p>سيتم أيضًا تشغيل مشروع الاختبار هذا عندما نقوم بإعداد CI، مما يعني أنه إذا قمنا في المستقبل بإجراء تغيير على الكود ولم تنجح الاختبارات التي أنشأناها في البداية، فسنتلقى إشعارًا أو يمكننا ببساطة رفض طلب السحب.&lt;/p>
&lt;h2 id="إنشاء-مستودع-جيثب">إنشاء مستودع جيثب&lt;/h2>
&lt;p>أولاً، سنقوم بإنشاء مستودع Github، لذا تواصل مع Github وقم بإنشاء المستودع واستنساخه في بيئتك المحلية. قررت استدعاء هذا المستودع الجديد &lt;code>CalculatorCLI-demo&lt;/code>.&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/0657eb2bdeb3c331b9e4585d7deed5ef.png" >
&lt;h2 id="خلق-الحل">خلق الحل&lt;/h2>
&lt;p>لنقم الآن بإنشاء حل فارغ يسمى &lt;code>CalculatorCLI&lt;/code>، في المجلد الجذر للمستودع المستنسخ&lt;/p>
&lt;h2 id="المكتبة-الأساسية">المكتبة الأساسية&lt;/h2>
&lt;p>كما هو الحال في مشروع العالم الحقيقي، سنقوم بتخزين منطقنا في مشروع منفصل يقوم بإنشاء مكتبة، لذلك دعونا نقوم بإنشائها.&lt;/p>
&lt;p>اذهب وأنشئ &lt;code>Class Library (.NET Standard)&lt;/code> وقم بتسميته &lt;code>CalculatorCLI.Core&lt;/code>&lt;/p>
&lt;h3 id="نسخة-net-الأساسيةبمجرد-إنشاء-المشروع-انتقل-إلى-خصائص-المشروع-وقم-بتغيير-target-framework-إلى-net-standard-21-لجعله-متوافقا-مع-المشاريع-المضمنة-في-net-core-30">نسخة NET الأساسيةبمجرد إنشاء المشروع، انتقل إلى خصائص المشروع وقم بتغيير &lt;code>Target framework&lt;/code> إلى &lt;code>.NET Standard 2.1&lt;/code>، لجعله متوافقًا مع المشاريع المضمنة في &lt;code>.NET Core 3.0&lt;/code>.&lt;/h3>
&lt;h3 id="الكود">الكود&lt;/h3>
&lt;p>من أجل البرنامج التعليمي، دعونا ننشئ فصلًا بسيطًا يتعامل مع العمليات.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">ConsoleCalculator.Core&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">enum&lt;/span> OperatorsEnum
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ADD,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SUBSTRACT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MULTIPLY,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DIVIDE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Operation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> OperatorsEnum OperatorEnum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> LeftValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> RightValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Operation(&lt;span style="color:#ff7b72">string&lt;/span> operatorString, &lt;span style="color:#ff7b72">int&lt;/span> leftValue, &lt;span style="color:#ff7b72">int&lt;/span> rightValue)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (operatorString)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.ADD;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.MULTIPLY;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.DIVIDE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator invalid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LeftValue = leftValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RightValue = rightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> DoOperation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (OperatorEnum)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.ADD:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue + RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.SUBSTRACT:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue - RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.MULTIPLY:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue * RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.DIVIDE:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue / RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator is not valid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="كلي">كلي&lt;/h2>
&lt;p>الآن بعد أن أصبح لدينا المشروع الأساسي، فلنقم بإنشاء التطبيق. في هذه الحالة، سيكون تطبيق وحدة تحكم بسيطًا يقبل الوسائط ويظهر نتيجة.&lt;/p>
&lt;p>لذلك دعونا نمضي قدمًا وننشئ &lt;code>Console App (.NET Core)&lt;/code> جديدًا، لقد أسميته &lt;code>CalculatorCLI.CLI&lt;/code>.&lt;/p>
&lt;h3 id="نسخة-net-الأساسية">نسخة NET الأساسية&lt;/h3>
&lt;p>كما فعلنا من قبل، بمجرد إنشاء المشروع، انتقل إلى خصائص المشروع وقم بتغيير &lt;code>Target framework&lt;/code> إلى &lt;code>.NET Core 3.0&lt;/code>، إذا لم يكن الأمر كذلك بالفعل.&lt;/p>
&lt;p>ثم أضف المرجع إلى &lt;code>ConsoleCLI.Core&lt;/code> إلى مشروعنا الذي تم إنشاؤه حديثًا.&lt;/p>
&lt;h3 id="الكود-1">الكود&lt;/h3>
&lt;p>الآن بالنسبة للكود، أصبح هذا أبسط من ذي قبل.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">ConsoleCalculator.Core&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text.RegularExpressions&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">ConsoleCalculator.CLI&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Program&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> Main(&lt;span style="color:#ff7b72">string&lt;/span>[] args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (args.Length == &lt;span style="color:#a5d6ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PrintUsage();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> joinedArgs = &lt;span style="color:#ff7b72">string&lt;/span>.Join(&lt;span style="color:#a5d6ff">&amp;#34; &amp;#34;&lt;/span>, args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> regex = &lt;span style="color:#a5d6ff">@&amp;#34;-op [\+\-\*\/] -l [-0-9]+ -r [-0-9]+&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (Regex.IsMatch(joinedArgs, regex))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span> _left = Int32.Parse(args[&lt;span style="color:#a5d6ff">3&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">int&lt;/span> _right = Int32.Parse(args[&lt;span style="color:#a5d6ff">5&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> _operator = args[&lt;span style="color:#a5d6ff">1&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> _operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(_operator, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> _result = _operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Result is: {_result}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PrintUsage();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> PrintUsage()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Welcome to ConsoleCalculator!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;-op Operator, it must be +,-,*,/&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;-l Left number&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;-r Left number&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;Example usage: -op + -l 5 -r 6&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>سوف نستخدم هذا التطبيق من أمر مثل، لذا لكي نجعله يعمل، علينا أن نسميه تمرير بعض المعلمات. على سبيل المثال:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ConsoleCalculator.CLI.exe -op + -l &lt;span style="color:#a5d6ff">10&lt;/span> -r &lt;span style="color:#a5d6ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>والذي يترجم إلى:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ConsoleCalculator.CLI.exe -operator + -leftValue &lt;span style="color:#a5d6ff">10&lt;/span> -rightValue &lt;span style="color:#a5d6ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>رمز ذلك بسيط جدًا، إذا لم يتطابق مع نمط regex معين، فهو استدعاء خاطئ ويستدعي &lt;code>PrintUsage()&lt;/code>. هذا يعني أننا إذا أدخلنا شيئًا مختلفًا عن الرقم، نظرًا لأنه تم ضبطه على التعبير العادي، فلن يحاول حتى إجراء عملية حسابية.&lt;/p>
&lt;p>يعني لو سميناها هكذا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ConsoleCalculator.CLI.exe -operator + -leftValue asdfg -rightValue ghjk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لن يتم إدخاله أبدًا داخل منطق العمليات ونحن نقوم بحفظ عمليات التحقق المستقبلية مثل &lt;code>TryParse&lt;/code> من القيم.&lt;/p>
&lt;h2 id="اختبار">اختبار&lt;/h2>
&lt;p>لدينا المكتبة الأساسية وسطر الأوامر، لكننا نحتاج الآن إلى الاختبار، لأن هذا ما نريد القيام به في CI.&lt;/p>
&lt;p>لذلك دعونا نمضي قدمًا وننشئ &lt;code>MSTest Test Project (.NET Core)&lt;/code> جديدًا ونسميه &lt;code>CalculatorCLI.Tests&lt;/code>.&lt;/p>
&lt;h3 id="نسخة-net-الأساسية-1">نسخة NET الأساسية&lt;/h3>
&lt;p>كما فعلنا من قبل، بمجرد إنشاء المشروع، انتقل إلى خصائص المشروع وقم بتغيير &lt;code>Target framework&lt;/code> إلى &lt;code>.NET Core 3.0&lt;/code>، إذا لم يكن الأمر كذلك بالفعل.&lt;/p>
&lt;p>ثم أضف المرجع إلى &lt;code>ConsoleCLI.Core&lt;/code> و &lt;code>ConsoleCLI.Core&lt;/code> إلى مشروعنا الاختباري الذي تم إنشاؤه حديثًا.&lt;/p>
&lt;h3 id="الكود-2">الكود&lt;/h3>
&lt;p>سنقوم بتقسيم الاختبار إلى ملفين مختلفين: &lt;code>CoreTests.cs&lt;/code> و &lt;code>CLITests.cs&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Core&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.VisualStudio.TestTools.UnitTesting&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Collections.Generic&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Tests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestClass]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CoreTests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> _left = &lt;span style="color:#a5d6ff">2&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> _right = &lt;span style="color:#a5d6ff">2&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldAdd()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldSubstract()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldMultiply()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldDivide()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> expectedResult = &lt;span style="color:#a5d6ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, _left, _right);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> functionResult = operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.AreEqual(functionResult, expectedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ExpectedException(typeof(System.DivideByZeroException))]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldThrowExceptionForDivideByZero()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, _left, &lt;span style="color:#a5d6ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [ExpectedException(typeof(System.Exception), &amp;#34;Operator invalid&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldThrowExceptionForWrongOperator()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> operation = &lt;span style="color:#ff7b72">new&lt;/span> Operation(&lt;span style="color:#a5d6ff">&amp;#34;text&amp;#34;&lt;/span>, _left, &lt;span style="color:#a5d6ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> operation.DoOperation();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.VisualStudio.TestTools.UnitTesting&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Collections.Generic&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System.Text&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Tests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestClass]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CLITests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _left = &lt;span style="color:#a5d6ff">&amp;#34;2&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> _right = &lt;span style="color:#a5d6ff">&amp;#34;2&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldAdd()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldSubstract()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldMultiply()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> ShouldDivide()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> args = &lt;span style="color:#ff7b72">new&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span>[] { &lt;span style="color:#a5d6ff">&amp;#34;-op&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-l&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;45&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;-r&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;96&amp;#34;&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CalculatorCLI.CLI.Program.Main(args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>مع كل شيء تم إنشاؤه سننتهي بحل مثل هذا:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/ffecc23a14d796af9a46dbb390c0d072.png" />
&lt;p>وبهذا يمكننا الآن إجراء الاختبارات، لذا انتقل إلى &lt;code>Test Explorer&lt;/code> في Visual Studio وقم بتشغيلها!&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/17d7ac9b2f1e7fb2666a68fc87882eed.png" />
&lt;p>#ترافيس سي.آي&lt;/p>
&lt;p>إذا لم تقم بذلك بالفعل، فإن TravisCI هو نظام مستضاف للتكامل والنشر المستمر.&lt;/p>
&lt;p>هناك بعض الخطوات التي يتعين علينا القيام بها هنا، ولكن أولاً سنقوم بربط مستودع Github الخاص بنا ليستمع إليه وكلاء TravisCI من أجل بناء مشروعنا واختباره.&lt;/p>
&lt;h2 id="تمكين-المستودع">تمكين المستودع&lt;/h2>
&lt;p>للقيام بذلك، قم بتسجيل الدخول إلى صفحة Travis CI وانتقل إلى المستودعات الخاصة بك، ثم قم بتصفية المشروع الذي قمت بإنشائه وقم بتمكينه، من خلال النقر على شريط التمرير الموجود بجوار اسم المستودع.&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/28b366dddd3f5caa9100ca6b6d200764.png" >
&lt;h2 id="إنشاء-travisymlنحتاج-إلى-إنشاء-ملف-يسمى-travisyml-في-جذر-مشروعك-وذلك-بسبب-كما-هو-مذكور-في-الوثائق">إنشاء .travis.ymlنحتاج إلى إنشاء ملف يسمى &lt;code>.travis.yml&lt;/code> في جذر مشروعك، وذلك بسبب &lt;a href="https://docs.travis-ci.com/user/tutorial/">كما هو مذكور في الوثائق&lt;/a>:&lt;/h2>
&lt;blockquote>
&lt;p>يقوم Travis فقط بتشغيل الإصدارات بناءً على الالتزامات التي تدفعها بعد إضافة ملف .travis.yml.&lt;/p>&lt;/blockquote>
&lt;p>لذا اذهب وأنشئ ملف &lt;code>.travis.yml&lt;/code> في جذر المستودع باستخدام الأسطر التالية:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">language&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">csharp&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">sudo&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">required&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">mono&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">none &lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">dotnet&lt;/span>:&lt;span style="color:#6e7681"> &lt;/span>&lt;span style="color:#a5d6ff">3.0&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">os&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">linux&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">before_script&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">dotnet restore &amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34;&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681">&lt;/span>&lt;span style="color:#7ee787">script&lt;/span>:&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">dotnet build &amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34; -c Release&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6e7681"> &lt;/span>- &lt;span style="color:#a5d6ff">dotnet test &amp;#34;.\CalculatorCLI\CalculatorCLI.sln&amp;#34; -c Release -v n&lt;/span>&lt;span style="color:#6e7681">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لن أخوض في صياغة كيفية عمل الملف &lt;code>.travis.yml&lt;/code>، ولكن دعنا نراجع ما يفعله:&lt;/p>
&lt;ol>
&lt;li>قمنا بإعداد أن اللغة ستكون &lt;code>csharp&lt;/code>.&lt;/li>
&lt;li>لن نستخدم &lt;code>mono&lt;/code> لأن &lt;code>.NET Core 3.0&lt;/code> سيتم تشغيله بشكل أصلي في Linux.&lt;/li>
&lt;li>قمنا بتعيين إصدار &lt;code>dotnet&lt;/code> على &lt;code>3.0&lt;/code>.&lt;/li>
&lt;li>قمنا بتعيين &lt;code>os&lt;/code>، افتراضيًا هو &lt;code>linux&lt;/code> ولكنني أضفته على أي حال.&lt;/li>
&lt;li>الآن لدينا &lt;code>before_script&lt;/code> والذي سيتم تشغيله قبل المنطق الرئيسي .هنا، لذلك ما وضعته هو تشغيل &lt;code>dotnet restore&lt;/code> إلى الحل بحيث يتم تحميل كل شيء بشكل مثالي لاحقًا.&lt;/li>
&lt;li>الآن في &lt;code>script&lt;/code>، سنقوم بإجراء &lt;code>dotnet builld&lt;/code> و &lt;code>dotnet test&lt;/code> للحل الخاص بنا، وهذا سيتحقق من تجميعه ثم تشغيل الاختبارات.&lt;/li>
&lt;/ol>
&lt;p>لقد انتهينا!&lt;/p>
&lt;h1 id="تحميل-إلى-الماجستير">تحميل إلى الماجستير&lt;/h1>
&lt;p>الآن نحتاج فقط إلى دفع كل شيء لإتقانه.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cmd" data-lang="cmd">&lt;span style="display:flex;">&lt;span>git add --all
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git commit -m &lt;span style="color:#a5d6ff">&amp;#34;Initial files&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git push
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="التحقق-من-التكامل-المستمر">التحقق من التكامل المستمر&lt;/h1>
&lt;p>يمكننا التحقق من حالة CI للدفع إلى &lt;code>master&lt;/code> التي قمنا بها في كل من صفحة المستودع أو لوحة معلومات TravisCI.&lt;/p>
&lt;h2 id="في-التقدم">في التقدم&lt;/h2>
&lt;img align="center" src="https://i.gyazo.com/977ea42a90adccf0736464b6603867a5.png" >
&lt;br/>
&lt;br/>
&lt;img align="center" src="https://i.gyazo.com/52a5f9356df5436c862b7df6fe66a9f4.png" >
&lt;p>##انتهى&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/c3d3521925a3e20bcf55bf5f6a2a711d.png" >
&lt;br/>
&lt;br/>
&lt;img align="center" src="https://i.gyazo.com/8c749c2ce44837a39fc5cd3e8838a798.png" >
&lt;p>#دعونا نكسرها&lt;/p>
&lt;p>الآن لنرى مدى قوة هذا، دعونا نكسر الكود ونغير المكتبة الأساسية حتى نجعلها تفشل.&lt;/p>
&lt;h2 id="تغييرات-الكود">تغييرات الكود&lt;/h2>
&lt;p>لذا انتقل إلى &lt;code>Operation.cs&lt;/code> وقم بتغيير شيء من شأنه أن يؤدي إلى كسر بعض الاختبارات.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">System&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">namespace&lt;/span> &lt;span style="color:#ff7b72">CalculatorCLI.Core&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">enum&lt;/span> OperatorsEnum
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ADD,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SUBSTRACT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MULTIPLY,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DIVIDE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Operation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> OperatorsEnum OperatorEnum { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> LeftValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> RightValue { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Operation(&lt;span style="color:#ff7b72">string&lt;/span> operatorString, &lt;span style="color:#ff7b72">int&lt;/span> leftValue, &lt;span style="color:#ff7b72">int&lt;/span> rightValue)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (operatorString)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;-&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;*&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.MULTIPLY;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;/&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.DIVIDE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator invalid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LeftValue = leftValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RightValue = rightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">int&lt;/span> DoOperation()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (OperatorEnum)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.ADD:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue + RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.SUBSTRACT:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue - RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.MULTIPLY:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue * RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> OperatorsEnum.DIVIDE:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> LeftValue / RightValue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">throw&lt;/span> &lt;span style="color:#ff7b72">new&lt;/span> Exception(&lt;span style="color:#a5d6ff">&amp;#34;Operator is not valid&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وإذا قمنا بإجراء الاختبار مرة أخرى، لأننا قمنا بإرجاع الحالة إلى عملية الجمع، فسوف تفشل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;+&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OperatorEnum = OperatorsEnum.SUBSTRACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">break&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كما هو متوقع، فشل الأمر بالنسبة لحالة &lt;code>ShouldAdd&lt;/code>:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/a57d0a0f8c07cc1ab6e4f55a8466cbbd.png" >
&lt;p>الآن قم بالالتزام بهذا التغيير وادفعه لإتقانه، وانتظر النتائج من وكيل TravisCI.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git add --all
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git commit -m &lt;span style="color:#a5d6ff">&amp;#34;Breaking changes&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git push
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="بناء">بناء&lt;/h2>
&lt;p>لننتقل الآن إلى سجلات TravisCI وسنرى أننا نجحنا في كسر المشروع، لأن اختبار التكامل فشل وكانت حالة البناء خاطئة.&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/397befe9b7f5a32b6e97511733296b00.png" >
&lt;p>في نهاية السجل يمكننا رؤية الخطأ نفسه:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/e5cbf32c5dd7a08bf6628d81edff3130.png" >
&lt;h2 id="دعونا-نصلح-الأمر-مرة-أخرى">دعونا نصلح الأمر مرة أخرى!&lt;/h2>
&lt;p>الآن ارجع عما فعلناه وادفع الكود لإتقانه، وتحقق من حالة البناء الجديد.&lt;/p>
&lt;p>الاختبارات تمر بنجاح:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/02deb8fcb4fca618ff2d79f1c27c6df5.png" >
&lt;p>والبناء أيضًا ناجح:&lt;/p>
&lt;img align="center" src="https://i.gyazo.com/38a227f0634c6040c6608f8c51f36cd3.png" >
&lt;p>#الخلاصة&lt;/p>
&lt;p>&lt;strong>إنه قوي جدًا حقًا&lt;/strong>، لقد كان CI وCD موجودين منذ وقت طويل، ولكن الآن أصبح من السهل جدًا تشغيله في كل مشروع على حدة، لا يهم مدى صغره أو بساطته.من وجهة نظري، يجب على الجميع على الأقل إعداد CI لكل مشروع من مشاريعهم، لأنها ممارسة جيدة وستوفر لك في النهاية الوقت في تصحيح الأخطاء والعثور على الخطأ الذي لا ينبغي أن يحدث إذا قمت بتعيين &lt;code>tests&lt;/code> و CI بشكل صحيح.&lt;/p>
&lt;h1 id="هذا-كل-شيء">هذا كل شيء&lt;/h1>
&lt;p>يتعلق الأمر بكيفية إنشاء حل .NET Core 3.0 الذي يتمتع بتكامل مستمر في كل بنية باستخدام TravisCI وتخزين التعليمات البرمجية في Github.&lt;/p>
&lt;p>يمكنك العثور على الكود المصدري لهذا المشروع &lt;a href="https://github.com/emimontesdeoca/CalculatorCLI-demo">هنا&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>CI/CD</category></item><item><title>رأيي في ذاكرة التخزين المؤقت في الذاكرة</title><link>https://emimontesdeoca.github.io/ar/posts/my-take-on-in-memory-cache/</link><pubDate>Tue, 03 Sep 2019 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/my-take-on-in-memory-cache/</guid><description>أنشئ تطبيقًا مخصصًا لذاكرة التخزين المؤقت في الذاكرة مع دعم انتهاء الصلاحية باستخدام C# والأدوية العامة.</description><content:encoded>&lt;p>لقد كنت أعمل على بعض الأشياء التي تعاملت مع كمية كبيرة من البيانات. أثناء القيام بذلك، أدركت أن جزءًا كبيرًا منه لا يتغير أبدًا، أو على الأقل لبعض الوقت المحدد، لا يتغير.&lt;/p>
&lt;p>لذلك اعتقدت أنه سيكون من المفيد إنشاء مستودع ذاكرة تخزين مؤقت شخصي، بالطبع هذا ليس جديدًا، قبل بضعة أسابيع قرأت عن هذا في &lt;a href="https://nickcraver.com/blog/2019/08/06/stack-overflow-how-we-do-app-caching/#in-memory--redis-cache">منشور&lt;/a> في StackOverflow الذي كتبه &lt;a href="https://nickcraver.com/">Nick Craver&lt;/a> حول كيفية إدارة ذاكرة التخزين المؤقت للتطبيق.&lt;/p>
&lt;p>كما كنت أرغب دائمًا في العمل مع ذاكرة التخزين المؤقت، وكيف تعمل، والمنطق، وكيف يمكنني تشغيلها، &lt;em>ف&amp;hellip; لماذا لا!&lt;/em>&lt;/p>
&lt;h2 id="التدفق">التدفق&lt;/h2>
&lt;p>فيما يلي تدفق سريع لكيفية تصرف الفئة التي تتحكم في ذاكرة التخزين المؤقت عندما يطلب المستخدم قيمة.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/830d5a91089c3344c8b406c66ea547b8">&lt;img src="https://i.gyazo.com/830d5a91089c3344c8b406c66ea547b8.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="التنفيذ">التنفيذ&lt;/h2>
&lt;h3 id="قبل-أن-نبدأ">قبل أن نبدأ&lt;/h3>
&lt;p>أعرف، أعرف. يوجد &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache?view=netframework-4.8">System.Runtime.Caching&lt;/a> الذي يتعامل مع ذاكرة التخزين المؤقت للذاكرة. لكنني قررت أن أقوم ببنائه بنفسي، إذا كنت تريد استخدام هذا الفصل، فراجع &lt;a href="https://stackoverflow.com/search?q=System.Runtime.Caching">هنا&lt;/a> لمعرفة الطريقة.&lt;/p>
&lt;h3 id="عنصر-ذاكرة-التخزين-المؤقت">عنصر ذاكرة التخزين المؤقت&lt;/h3>
&lt;p>الخطوة الأولى هي أن يكون لديك فئة تخزن قيمة الكائن وتاريخ انتهاء الصلاحية. ربما هناك طريقة أفضل للقيام بذلك، ولكن هذا ما فكرت به، لذا إليك ما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CacheItem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Identifier { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">object&lt;/span> Value { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> DateTime ValidUntil { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> CacheItem(&lt;span style="color:#ff7b72">string&lt;/span> identifier, &lt;span style="color:#ff7b72">object&lt;/span> &lt;span style="color:#ff7b72">value&lt;/span>, TimeSpan valid)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Identifier = identifier;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value = &lt;span style="color:#ff7b72">value&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ValidUntil = DateTime.UtcNow.Add(valid);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="مستودع-التخزين-المؤقت">مستودع التخزين المؤقت&lt;/h3>
&lt;p>ثم نحتاج إلى فئة تتعامل مع الكائنات وتحفظها في مكان ما (مثل &lt;code>CacheItem&lt;/code>). أحب التعامل مع جميع البيانات/النماذج في الفئات التي تحتوي على اللاحقة &lt;code>Repository&lt;/code>، &lt;em>ولكن ليس عليك القيام بذلك&lt;/em>، لذلك دعونا نبني واحدًا للتخزين المؤقت.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CacheRepository&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; Cache = &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T Set&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key, Func&amp;lt;T&amp;gt; lookup, TimeSpan durationMinutes)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> item = &lt;span style="color:#ff7b72">new&lt;/span> Models.Item(key, lookup(), durationMinutes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> Save&amp;lt;T&amp;gt;(key, item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T Save&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key, Item item)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Cache[key] = JsonConvert.SerializeObject(item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> (T)item.Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T Get&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> cached = Cache.FirstOrDefault(x =&amp;gt; x.Key == key).Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> item = &lt;span style="color:#ff7b72">string&lt;/span>.IsNullOrEmpty(cached) ? &lt;span style="color:#79c0ff">null&lt;/span> : JsonConvert.DeserializeObject&amp;lt;Item&amp;gt;(cached);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> (item == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? &lt;span style="color:#ff7b72">default&lt;/span> : (item.ValidUntil &amp;gt; DateTime.UtcNow)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ? JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(JsonConvert.SerializeObject(item.Value)) : &lt;span style="color:#ff7b72">default&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T GetOrSet&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> key, Func&amp;lt;T&amp;gt; lookup, TimeSpan durationMinutes)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> cache = Get&amp;lt;T&amp;gt;(key);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> cache == &lt;span style="color:#79c0ff">null&lt;/span> ? Set(key, lookup, durationMinutes) : cache;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>لدينا حتى الآن قاموس ثابت يسمى &lt;code>Cache&lt;/code> حيث سيتم تخزين جميع العناصر. تذكر أن هذا لن يستمر إلا أثناء تشغيل التطبيق، ولهذا السبب يحتوي هذا العنوان &lt;em>البرنامج التعليمي&lt;/em> على ذاكرة تخزين مؤقت في الذاكرة.&lt;/p>
&lt;p>&lt;em>ضع في اعتبارك أنه سيتم تهيئة العنصر &lt;code>Cache&lt;/code> بمجرد تحميل فئة &lt;code>CacheRepository&lt;/code>.&lt;/em>&lt;/p>
&lt;p>الطريقة الوحيدة المتاحة عند استدعاء فئة CacheRepository هي &lt;code>GetOrSet(string key, Func&amp;lt;T&amp;gt; lookup, TimeSpan durationMinutes)&lt;/code> التي تحتاج إلى ثلاث معلمات:&lt;/p>
&lt;ol>
&lt;li>&lt;code>key&lt;/code>: معرف الكائن المراد حفظه.&lt;/li>
&lt;li>&lt;code>lookup&lt;/code>: وظيفة رد الاتصال في حالة انتهاء صلاحية ذاكرة التخزين المؤقت أو أنها فارغة.&lt;/li>
&lt;li>&lt;code>durationMinutes&lt;/code>: المدة بالدقائق والتي سيتم إضافتها إلى الوقت الحالي (بالتوقيت العالمي).&lt;/li>
&lt;/ol>
&lt;h2 id="حان-الوقت-للتخزين-المؤقت">حان الوقت للتخزين المؤقت&lt;/h2>
&lt;p>الآن، استخدم مستودع التخزين المؤقت الخاص بنا للحصول على بعض البيانات من مكان ما.&lt;/p>
&lt;p>لكي يكون كل هذا منطقيًا، فلنقم بإنشاء كائن مثال مع بعض الخصائص، ثم مستودعًا لجلب قائمة بهذا الكائن وملؤها.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">User&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Guid Id { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Email { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">UserRepository&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;User&amp;gt; Get()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Code to get users&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نظرًا لأن لدينا طريقة لملء قائمة &lt;code>User&lt;/code>، فلنستخدم فئة &lt;code>CacheRepository&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> UserRepository _userRepository = &lt;span style="color:#ff7b72">new&lt;/span> UserRepository();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> List&amp;lt;User&amp;gt; _user;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;User&amp;gt; User
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">get&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _user = CacheRepository.GetOrSet(&lt;span style="color:#a5d6ff">$&amp;#34;users&amp;#34;&lt;/span>, usersRepo.Get, TimeSpan.FromMinutes(&lt;span style="color:#a5d6ff">10&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> _user;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وهكذا، في كل مرة تقوم فيها بالوصول إلى المتغير &lt;code>User&lt;/code>، سيطلب من &lt;code>CacheRepository&lt;/code> قيمة الكائن الذي يحتوي على المفتاح &lt;code>users&lt;/code>.&lt;/p>
&lt;p>إذا كان هذا المفتاح موجودًا، فسوف يتحقق من تاريخ انتهاء الصلاحية. إذا كان أي من هذه الشروط خاطئًا، فسيتم استخدام رد الاتصال لتعيين قيمة الكائن (باستخدام &lt;code>usersRepo.Get&lt;/code>) وحفظه في ذاكرة التخزين المؤقت مع تعيين تاريخ انتهاء الصلاحية على &lt;code>DateTime.UtcNow + TimeSpan.FromMinutes(10)&lt;/code> وإعادته.&lt;/p></content:encoded><category>.NET</category></item><item><title>التقط لقطات شاشة للصفحة باستخدام Devtools</title><link>https://emimontesdeoca.github.io/ar/posts/google-chrome-screenshot-tool/</link><pubDate>Thu, 09 May 2019 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/google-chrome-screenshot-tool/</guid><description>التقط لقطات شاشة للصفحة الكاملة والمنطقة باستخدام أوامر لقطة الشاشة المضمنة في Chrome DevTools.</description><content:encoded>&lt;p>كانت هناك أوقات كنت بحاجة فيها إلى عرض لقطة شاشة كاملة للعميل أو زميل الفريق للحالة الحالية لموقع الويب أو حتى جزء منه، لذلك كنت أستخدم &lt;a href="https://chrome.google.com/webstore/detail/full-page-screen-capture/fdpohaocaechififmbbbbbknoalclacl?hl=en">Full Page Screen Capture&lt;/a>، ولأي نوع آخر من لقطات الشاشة، &lt;a href="https://support.microsoft.com/en-us/help/13776/windows-use-snipping-tool-to-capture-screenshots">أداة Snippet الخاصة بنظام Windows&lt;/a> أو &lt;a href="https://www.techsmith.com/store/snagit">Snaggit&lt;/a>. إذا كنت أرغب في استضافته عبر الإنترنت كما هو موضح في هذا المنشور &lt;a href="https://gyazo.com">Gyazo&lt;/a>، والذي يحتوي أيضًا على أداة إنشاء صور GIF.&lt;/p>
&lt;p>اكتشفت مؤخرًا أن &lt;strong>Devtools&lt;/strong> يحتوي على أداة يمكنها التقاط لقطة شاشة للصفحة بأكملها، دون الحاجة إلى أي إضافات!&lt;/p>
&lt;p>هذه ليست فكرة جديدة، فقد تم تضمينها في &lt;a href="https://developers.google.com/web/updates/2017/04/devtools-release-notes">تحديث Devtools في أبريل 2017&lt;/a>، &lt;em>ولكن يبدو أنني أعيش تحت صخرة ولم أعرف ذلك حتى الآن&amp;hellip;&lt;/em>&lt;/p>
&lt;h2 id="أدوات-التطوير">أدوات التطوير&lt;/h2>
&lt;p>كما هو مذكور في &lt;a href="https://developers.google.com/web/tools/chrome-devtools/?hl=en">صفحة Devtools الرسمية&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>Chrome DevTools عبارة عن مجموعة من أدوات مطوري الويب المضمنة مباشرة في متصفح Google Chrome. يمكن أن تساعدك أدوات DevTools على تحرير الصفحات بسرعة وتشخيص المشكلات بسرعة، مما يساعدك في النهاية على إنشاء مواقع ويب أفضل وبشكل أسرع.&lt;/p>&lt;/blockquote>
&lt;h2 id="تشغيل-الأوامر-في-devtools">تشغيل الأوامر في Devtools&lt;/h2>
&lt;p>أنت تعرف بالفعل كيفية فتح Devtools ولكن في حالة نسيانك للتو و &lt;em>من أجل هذا المنشور&lt;/em>، افتحه باستخدام المفتاح &lt;code>F12&lt;/code> أو الاختصار &lt;code>Ctrl + Shift + I&lt;/code>. ثم يتعين عليك فتح &lt;code>Run command&lt;/code> في القائمة داخل Devtools أو استخدام &lt;code>Ctrl + Shift + P&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/8209d7d3132efba1843a3d51e4ad2183">&lt;img src="https://i.gyazo.com/8209d7d3132efba1843a3d51e4ad2183.gif" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="أداة-لقطة-الشاشة">أداة لقطة الشاشة&lt;/h2>
&lt;p>&lt;a href="https://gyazo.com/25ea6c9d258a1117efca5a2d92f715e2">&lt;img src="https://i.gyazo.com/25ea6c9d258a1117efca5a2d92f715e2.gif" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>إذا كتبت &lt;code>screenshot&lt;/code> فسيتم تصفية الأوامر، وسيكون هناك 4 أنواع من طرق لقطات الشاشة التي يمكنك استخدامها:&lt;/p>
&lt;ol>
&lt;li>لقطة شاشة للمنطقة&lt;/li>
&lt;li>لقطة الشاشة بالحجم الكامل&lt;/li>
&lt;li>لقطة شاشة للعقدة&lt;/li>
&lt;li>التقاط لقطة للشاشة&lt;/li>
&lt;/ol>
&lt;p>قبل التحقق من جميع طرق لقطات الشاشة، وبعد انتهاء أدوات التطوير من معالجة الصورة، &lt;strong>سيتم تنزيلها تلقائيًا باسم صفحة الويب&lt;/strong>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/89a4935eb0ddb1a06ae997551fd19677">&lt;img src="https://i.gyazo.com/89a4935eb0ddb1a06ae997551fd19677.gif" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/e2e29c7fd7e90bb8bfd36ef130793394">&lt;img src="https://i.gyazo.com/e2e29c7fd7e90bb8bfd36ef130793394.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h3 id="لقطة-شاشة-للمنطقة">لقطة شاشة للمنطقة&lt;/h3>
&lt;p>سيتيح لك &lt;code>area screenshot&lt;/code> تحديد جزء من موقع الويب والتقاط لقطة شاشة.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/f508a479893b6e8af9234d6a77404b26">&lt;img src="https://i.gyazo.com/f508a479893b6e8af9234d6a77404b26.gif" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>النتيجة:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/2b096e1ca2974157e478587a4902c1d2">&lt;img src="https://i.gyazo.com/2b096e1ca2974157e478587a4902c1d2.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h3 id="لقطة-شاشة-بالحجم-الكامل">لقطة شاشة بالحجم الكامل&lt;/h3>
&lt;p>سيقوم &lt;code>full size screenshot&lt;/code> بالتقاط لقطة شاشة لصفحة الويب بأكملها، من البداية إلى الأسفل.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/e6c30b8eafbef04091bf66c16429017c">&lt;img src="https://i.gyazo.com/e6c30b8eafbef04091bf66c16429017c.gif" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>النتيجة:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/9519ca32c0029a36432b700268741280">&lt;img src="https://i.gyazo.com/9519ca32c0029a36432b700268741280.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h3 id="لقطة-شاشة-للعقدة">لقطة شاشة للعقدة&lt;/h3>
&lt;p>سيسمح لك &lt;code>node screenshot&lt;/code> بعمل لقطة شاشة للعقدة المحددة في عنصر الفحص.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/5b936026735964bb789c4bcae02bb792">&lt;img src="https://i.gyazo.com/5b936026735964bb789c4bcae02bb792.gif" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>النتيجة:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/56089c3d89f9b059dfb018d18572276d">&lt;img src="https://i.gyazo.com/56089c3d89f9b059dfb018d18572276d.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h3 id="التقاط-لقطة-للشاشة">التقاط لقطة للشاشة&lt;/h3>
&lt;p>سيقوم &lt;code>capture screenshot&lt;/code> بالتقاط لقطة شاشة للصفحة الحالية دون التمرير وحجم المتصفح.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/6dc4f654e8f218e44fcc1fb51ce4c9f5">&lt;img src="https://i.gyazo.com/6dc4f654e8f218e44fcc1fb51ce4c9f5.gif" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>النتيجة:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/3f15d4a004cba6152d1f9606c20540cc">&lt;img src="https://i.gyazo.com/3f15d4a004cba6152d1f9606c20540cc.png" alt="صورة من جيازو">&lt;/a>&lt;/p></content:encoded></item><item><title>لغات مخصصة باستخدام الموارد المضمنة والخارجية في .NET Framework</title><link>https://emimontesdeoca.github.io/ar/posts/embedded-resources-and-external-resources/</link><pubDate>Mon, 06 May 2019 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/embedded-resources-and-external-resources/</guid><description>تنفيذ دعم متعدد اللغات باستخدام ملفات الموارد .resx المضمنة والخارجية في .NET Framework.</description><content:encoded>&lt;h1 id="مقدمة">مقدمة&lt;/h1>
&lt;p>في العمل، توصلنا إلى مشكلة عادية وأنا متأكد من أن الكثير من المطورين يجب عليهم التعامل معها، وهي اللغات واللغات المخصصة من العملاء الذين يقدمون خدماتنا لعملائهم.&lt;/p>
&lt;p>على سبيل المثال، لنفترض أن اللغة الأساسية تستخدم لغة غير رسمية وأن العميل يريد لغة رسمية لأن عملائه هم من كبار السن.&lt;/p>
&lt;p>لاحظ أيضًا أن هذه الحلول مخصصة لعدة لغات، وسنستخدم الإنجليزية والإسبانية.&lt;/p>
&lt;p>&lt;em>لغتي الأم ليست الإنجليزية وأعتذر عن أي أخطاء في البرنامج التعليمي. إذا وجدت أخطاء وتريد إصلاحها، يمكنك فتح طلب سحب على &lt;a href="https://github.com/emimontesdeoca/emimontesdeoca.github.io">هذا الريبو&lt;/a> وسأوافق عليه بكل سرور!&lt;/em>&lt;/p>
&lt;h1 id="الموارد">الموارد&lt;/h1>
&lt;p>الموارد هي ملفات XML ذات الامتداد &lt;code>.resx&lt;/code> والتي تحتوي على بنية مفتاح/قيمة وتبدو كما يلي:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;root&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:schema&lt;/span> id=&lt;span style="color:#a5d6ff">&amp;#34;root&amp;#34;&lt;/span> xmlns=&lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span> xmlns:xsd=&lt;span style="color:#a5d6ff">&amp;#34;http://www.w3.org/2001/XMLSchema&amp;#34;&lt;/span> xmlns:msdata=&lt;span style="color:#a5d6ff">&amp;#34;urn:schemas-microsoft-com:xml-msdata&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:import&lt;/span> namespace=&lt;span style="color:#a5d6ff">&amp;#34;http://www.w3.org/XML/1998/namespace&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;root&amp;#34;&lt;/span> msdata:IsDataSet=&lt;span style="color:#a5d6ff">&amp;#34;true&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:choice&lt;/span> maxOccurs=&lt;span style="color:#a5d6ff">&amp;#34;unbounded&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;metadata&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;value&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> use=&lt;span style="color:#a5d6ff">&amp;#34;required&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;type&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;mimetype&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> ref=&lt;span style="color:#a5d6ff">&amp;#34;xml:space&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;assembly&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;alias&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;data&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;value&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;comment&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;2&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> use=&lt;span style="color:#a5d6ff">&amp;#34;required&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;type&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;3&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;mimetype&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;4&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> ref=&lt;span style="color:#a5d6ff">&amp;#34;xml:space&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;resheader&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:element&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;value&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> minOccurs=&lt;span style="color:#a5d6ff">&amp;#34;0&amp;#34;&lt;/span> msdata:Ordinal=&lt;span style="color:#a5d6ff">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:sequence&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;xsd:attribute&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;name&amp;#34;&lt;/span> type=&lt;span style="color:#a5d6ff">&amp;#34;xsd:string&amp;#34;&lt;/span> use=&lt;span style="color:#a5d6ff">&amp;#34;required&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:choice&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:complexType&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:element&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/xsd:schema&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;resmimetype&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>text/microsoft-resx&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;version&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>2.0&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;reader&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;resheader&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;writer&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/resheader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;data&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;Action_cancel&amp;#34;&lt;/span> xml:space=&lt;span style="color:#a5d6ff">&amp;#34;preserve&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>Finish&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/data&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;data&lt;/span> name=&lt;span style="color:#a5d6ff">&amp;#34;Action_greeting&amp;#34;&lt;/span> xml:space=&lt;span style="color:#a5d6ff">&amp;#34;preserve&amp;#34;&lt;/span>&lt;span style="color:#7ee787">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;value&amp;gt;&lt;/span>Hello&lt;span style="color:#7ee787">&amp;lt;/value&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/data&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/root&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>سنقوم بإنشاء موارد للغتين مختلفتين: &lt;strong>الإنجليزية&lt;/strong> و &lt;strong>الإسبانية&lt;/strong>، وبالتالي فإن تسميات الملفات ستكون: &lt;code>resources.ISOLANGUAGECODE.resx&lt;/code>، على سبيل المثال: &lt;code>resource.es.resx&lt;/code> للغة الإسبانية و&lt;code>resource.resx&lt;/code> للغة الإنجليزية، وفي حالة أردنا إضافة اللغة الألمانية لاحقًا، فسيتم تسمية الملف &lt;code>resource.de.resx&lt;/code>.&lt;/p>
&lt;h2 id="الموارد-المضمنة">الموارد المضمنة&lt;/h2>
&lt;p>الموارد المضمنة، عند تجميع المشروع، ستتم إضافتها داخل &lt;code>dll&lt;/code>.&lt;/p>
&lt;p>فيما يلي صورة تحتوي على مورد مضمن، كما ترون، لا يمكن رؤية الملف &lt;code>Resource.resx&lt;/code> في التجميع.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/b696d4ff1129634477c0fe3d570a05e8">&lt;img src="https://i.gyazo.com/b696d4ff1129634477c0fe3d570a05e8.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="موارد-خارجية">موارد خارجية&lt;/h2>
&lt;p>على الجانب الآخر، الموارد الخارجية أو غير المضمنة هي الموارد التي سيتم إضافتها إلى المجلد بعد التجميع.&lt;/p>
&lt;p>ستكون ملفات الموارد (&lt;code>.resx&lt;/code>) داخل المجلد &lt;code>Properties&lt;/code>&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/db83dc7bb9e3d3fb521cb321eaa8e74a">&lt;img src="https://i.gyazo.com/db83dc7bb9e3d3fb521cb321eaa8e74a.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h1 id="بناء-المشروع">بناء المشروع&lt;/h1>
&lt;p>لنبدأ بمشروع وحدة التحكم في .NET Framework.&lt;/p>
&lt;h2 id="إنشاء-ملف-المورد-المضمن">إنشاء ملف المورد المضمن&lt;/h2>
&lt;p>أضف ملف مورد يحتوي على عدد قليل من المفاتيح وتأكد من أنه مورد مضمن، والذي سنستخدمه لاحقًا للتحديث باستخدام موارد خارجية.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/79af1981a3ee3214089791a3511b94ba">&lt;img src="https://i.gyazo.com/79af1981a3ee3214089791a3511b94ba.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="إنشاء-ملف-الموارد-الخارجية">إنشاء ملف الموارد الخارجية&lt;/h2>
&lt;p>من أجل فصل الموارد الخارجية عن الموارد المدمجة، سنقوم بإضافة الموارد الخارجية داخل مجلد باسم حتى نتمكن من الوصول إليها بسهولة لاحقًا.&lt;/p>
&lt;p>&lt;em>نصيحة: لإضافة مجلد داخل مجلد الخصائص، قم بإنشائه بالخارج ونقله إلى الداخل، لا يسمح لك Visual Studio بإنشائه&lt;/em>&lt;/p>
&lt;p>افعل نفس ما فعلناه مع المورد المضمن، ثم انتقل إلى &lt;code>properties&lt;/code> من الملف وداخل &lt;code>Advanced&lt;/code>، &lt;code>Build action&lt;/code> وقم بتغييره إلى: &lt;code>Content&lt;/code> وقم بتغيير &lt;code>Copy to Output Dictionary&lt;/code> إلى &lt;code>Copy if newer&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/6f7d53c82760b6989d74dc1864bbc2d4">&lt;img src="https://i.gyazo.com/6f7d53c82760b6989d74dc1864bbc2d4.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>هذه هي الطريقة التي ينبغي أن تبدو وكأنها:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/75b2f06b11fa99b01bca7b5ac64b4e35">&lt;img src="https://i.gyazo.com/75b2f06b11fa99b01bca7b5ac64b4e35.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="إضافة-مفتاح-إلى-appconfig">إضافة مفتاح إلى app.config&lt;/h2>
&lt;p>نظرًا لأننا مطورون رائعون ونحب إنجاز كل شيء بشكل جيد و&lt;strong>عدم&lt;/strong> تغيير الكود لكل عميل، فلنضيف مفتاحًا إلى &lt;code>appconfig&lt;/code> الذي سيكون له اسم المجلد الذي سنبحث فيه عن ملفات الموارد&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34; ?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;startup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;supportedRuntime&lt;/span> version=&lt;span style="color:#a5d6ff">&amp;#34;v4.0&amp;#34;&lt;/span> sku=&lt;span style="color:#a5d6ff">&amp;#34;.NETFramework,Version=v4.7.2&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/startup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;appSettings&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;add&lt;/span> key=&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span> value=&lt;span style="color:#a5d6ff">&amp;#34;John&amp;#34;&lt;/span> &lt;span style="color:#7ee787">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;lt;/appSettings&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#7ee787">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>```لذلك، لاحقًا، عندما نبحث عن الموارد، سنبحث في `Properties.John` بدلاً من `Properties.Doe`.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>وهذا أيضًا يجعل من السهل التغيير عندما يتم نشر التطبيق بالفعل حيث يمكنك تغيير app.config بسهولة.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>## الوصول إلى ملفات الموارد المضمنة
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>يعد استخدام .NET Framework أمرًا سهلاً إلى حد ما، لذلك من أجل الوصول إلى الخصائص المضمنة، علينا فقط استخدام كائن `Properties`، الذي سيحتوي على ملفات الموارد كخاصية، وداخل هذا الكائن سيكون هناك كل المفتاح/القيمة الموجودة في ملف `resx`.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>```csharp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>static void Main(string[] args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> var hello = Properties.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> var bye = Properties.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine($&amp;#34;Action_greeting value: {hello}, Action_cancel value: {bye}&amp;#34;);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Read();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>تشغيل ذلك يجب أن يظهر لنا شيئًا مثل هذا:&lt;/p>
&lt;p>&lt;code>Action_greeting value: Hello, Action_cancel value: Cancel&lt;/code>&lt;/p>
&lt;h2 id="الوصول-إلى-ملفات-الموارد-الخارجية">الوصول إلى ملفات الموارد الخارجية&lt;/h2>
&lt;p>هذا الجزء هو نفسه إلى حد كبير، يمكنك الوصول إلى ملفات الموارد عن طريق كائن &lt;code>Properties&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> hello = Properties.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> bye = Properties.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> johnhello = Properties.John.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> johnbye = Properties.John.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doehello = Properties.Doe.Resource.Action_greeting;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> doebye = Properties.Doe.Resource.Action_cancel;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="مشكلة">مشكلة&lt;/h1>
&lt;p>المشكلة الرئيسية في استخدام هذه الطريقة هي أن كل مفتاح هو خاصية داخل الكائن، لذلك علينا أن نسميه كما رأينا من قبل. إذا كنت تريد استدعاء المفتاح &lt;code>Action_greeting&lt;/code> من ملف الموارد الخاص بـ &lt;code>John&lt;/code> فعلينا استخدام &lt;code>Properties.John&lt;/code> التالي ثم &lt;code>Resource.Action_greeting&lt;/code>.&lt;/p>
&lt;p>&lt;strong>هنا المشكلة.&lt;/strong>&lt;/p>
&lt;p>وذلك لأنه إذا كنا نقوم بتطوير تطبيق لعدد كبير من العملاء، فمن السيئ أن نغير طريقة استدعاء ملفات الموارد لكل منهم.&lt;/p>
&lt;p>&lt;em>هل يمكنك تخيل ذلك؟&lt;/em> تجميع التطبيق لكل عميل وتغيير &lt;code>John&lt;/code> إلى &lt;code>Doe&lt;/code> ثم إلى شيء آخر. &lt;strong>هذا جنون!&lt;/strong>&lt;/p>
&lt;h1 id="الحل">الحل&lt;/h1>
&lt;p>فكر قائد فريقنا في طريقة جيدة جدًا، مثل نظام احتياطي، يجب أن يكون لدينا نموذج أساسي للموارد، وبعد ذلك يكون لكل عميل ملف مورد يقوم بتحديث الملف بموارده، وينتهي بنا الأمر بقائمة واحدة من الموارد.&lt;/p>
&lt;p>إذا كان العميل لا يريد موارد مخصصة، فإننا نستخدم الموارد الأساسية، وإذا أرادها، نستخدم موارده.&lt;/p>
&lt;p>لوضع هذا في قائمة التحقق، علينا القيام بما يلي:&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> ابحث عن طريقة لتعيين جميع المفاتيح/القيم إلى قاموس الموارد المضمنة.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> ابحث عن طريقة لتعيين جميع المفاتيح/القيم إلى قاموس الموارد الخارجية&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> امزج كلا الملفين واحصل على قاموس واحد لكل لغة&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> قم بإنشاء طريقة للوصول إلى القاموس وإرجاع القيمة&lt;/li>
&lt;/ul>
&lt;h2 id="رسم-بياني">رسم بياني&lt;/h2>
&lt;p>&lt;a href="https://gyazo.com/c7e9ae6c7792c3bace10ff9b3b2b08ee">&lt;img src="https://i.gyazo.com/c7e9ae6c7792c3bace10ff9b3b2b08ee.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="دعونا-نرمز">دعونا نرمز&lt;/h2>
&lt;p>أولاً، لنقم بإنشاء فئة منفصلة حيث سيكون لدينا كل منطقنا، بدءًا من الحصول على ملفات الموارد، وحتى مزجها وإرجاع القيمة. سيتم استدعاء هذا الفصل &lt;code>CustomResources&lt;/code>.&lt;/p>
&lt;p>هذا ما يبدو:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">CustomResources&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; OverwriteDictionary(Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; currentDictionary, Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; newDictionary, &lt;span style="color:#ff7b72">bool&lt;/span> addIfDoesntExist = &lt;span style="color:#79c0ff">false&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromEmbedded(&lt;span style="color:#ff7b72">string&lt;/span> embedded, &lt;span style="color:#ff7b72">string&lt;/span> cultureInfoCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromFile(&lt;span style="color:#ff7b72">string&lt;/span> file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key, &lt;span style="color:#ff7b72">string&lt;/span> language)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>لاحظ أننا نقوم بتطبيق التحميل البطيء للخصائص مما يساعد على زيادة الأداء ويجعل تحميل القاموس مرة واحدة.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>GetDictionaryFromEmbedded&lt;/code>: يُرجع قاموسًا من الموارد المضمنة.&lt;/li>
&lt;li>&lt;code>GetDictionaryFromFile&lt;/code>: يُرجع قاموسًا من مصادر خارجية.&lt;/li>
&lt;li>&lt;code>OverwriteDictionary&lt;/code>: خلط قاموسين وإرجاع قاموس واحد.&lt;/li>
&lt;li>&lt;code>GetText&lt;/code>: تُرجع قيمة معينة بمفتاح&lt;/li>
&lt;/ul>
&lt;h2 id="من-المورد-المضمن-إلى-القاموس">من المورد المضمن إلى القاموس&lt;/h2>
&lt;p>يتعين علينا الحصول على جميع الخصائص من ملف xml وإرجاع القاموس:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromEmbedded(&lt;span style="color:#ff7b72">string&lt;/span> embedded, &lt;span style="color:#ff7b72">string&lt;/span> cultureInfoCode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; res = &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ResourceManager rm = &lt;span style="color:#ff7b72">new&lt;/span> ResourceManager(embedded, Assembly.GetExecutingAssembly());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> resourceSet = rm.GetResourceSet(&lt;span style="color:#ff7b72">new&lt;/span> CultureInfo(cultureInfoCode), &lt;span style="color:#79c0ff">true&lt;/span>, &lt;span style="color:#79c0ff">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> resourceDictionary = resourceSet.Cast&amp;lt;DictionaryEntry&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .ToDictionary(r =&amp;gt; r.Key.ToString(),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> r =&amp;gt; r.Value.ToString());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> res = resourceDictionary;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> a = e.Message;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Error getting resource file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> res;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>شيئين:- لاحظ أنه يحتاج إلى معامل يسمى &lt;code>embedded&lt;/code>، وهو اسم الملف الذي يمكنك رؤيته في المصمم، في حالتنا هو: &lt;code>resources-demo.Properties.Resource&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>لدينا أيضًا معلمة تسمى CultureInfoCode، وهي رمز اللغة التي سيتم اختيارها. لحسن الحظ بالنسبة لنا، يقوم .NET Framework بالمهمة نيابةً عنا وليس علينا القيام بأي شيء، فقط قم بتعيين ما نريده إما باللغة الإنجليزية أو الإسبانية، وسيختار بين &lt;code>resource.es.resx&lt;/code> أو &lt;code>resource.resx&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="من-الموارد-الخارجية-إلى-القاموس">من الموارد الخارجية إلى القاموس&lt;/h2>
&lt;p>يعد الحصول على الملف أمرًا صعبًا بعض الشيء ولكن ليس صعبًا، يتعين علينا الحصول على الموقع الحالي للملف القابل للتنفيذ، وربط موقع ملف المورد ثم تحليله إلى القاموس.&lt;/p>
&lt;p>لكن عليك أولاً إضافة المرجع إلى &lt;code>System.Windows.Forms&lt;/code>، من أجل الوصول إلى &lt;code>ResXResourceReader&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/5bdf8353acc57b1d95b72acc14af41cd">&lt;img src="https://i.gyazo.com/5bdf8353acc57b1d95b72acc14af41cd.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>الآن إلى طريقة &lt;code>GetDictionaryFromFile&lt;/code> الخاصة بنا:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; GetDictionaryFromFile(&lt;span style="color:#ff7b72">string&lt;/span> file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; res = &lt;span style="color:#ff7b72">new&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> currentPath = (System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) + file).Replace(&lt;span style="color:#a5d6ff">&amp;#34;file:\\&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (ResXResourceReader resxReader = &lt;span style="color:#ff7b72">new&lt;/span> ResXResourceReader(currentPath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (DictionaryEntry entry &lt;span style="color:#ff7b72">in&lt;/span> resxReader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> res.Add((&lt;span style="color:#ff7b72">string&lt;/span>)entry.Key, (&lt;span style="color:#ff7b72">string&lt;/span>)entry.Value);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> a = e.Message;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> res;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>يجب ملء معلمات الملف بالموقع من الملف القابل للتنفيذ إلى ملف المورد، في حالتنا هو: &lt;code>&amp;quot;\\Properties\\John\\Resource.resx&amp;quot;&lt;/code>.&lt;/p>
&lt;h2 id="خلط-القواميس">خلط القواميس&lt;/h2>
&lt;p>لقد انتهينا تقريبًا، أولاً، تذكر إضافة &lt;code>System.Configuration&lt;/code> إلى المراجع حتى تتمكن من الوصول إلى &lt;code>app.settings&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; OverwriteDictionary(Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; currentDictionary, Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; newDictionary, &lt;span style="color:#ff7b72">bool&lt;/span> addIfDoesntExist = &lt;span style="color:#79c0ff">false&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> identifier = ConfigurationManager.AppSettings[&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (String.IsNullOrEmpty(identifier))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> currentDictionary;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> item &lt;span style="color:#ff7b72">in&lt;/span> newDictionary)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentDictionary[item.Key] = item.Value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span>(addIfDoesntExist)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentDictionary.Add(item.Key, item.Value);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> currentDictionary;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="الحصول-على-نص-من-القاموس">الحصول على نص من القاموس&lt;/h2>
&lt;p>قم بإنشاء طريقة عامة تستدعي طريقة خاصة تحدد لغة:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key, &lt;span style="color:#ff7b72">string&lt;/span> language)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">switch&lt;/span> (language)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ResourceSpanish[key];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">case&lt;/span> &lt;span style="color:#a5d6ff">&amp;#34;en&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> ResourceEnglish[key];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;No value with key: {key} and language: {language}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> GetText(&lt;span style="color:#ff7b72">string&lt;/span> key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> GetText(key, Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">catch&lt;/span> (Exception)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> &lt;span style="color:#a5d6ff">$&amp;#34;No value with key: {key}&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>لاحظ أنه يتعين عليك تعديل المفتاح إذا كنت تريد إضافة المزيد من اللغات.&lt;/strong>&lt;/p>
&lt;h2 id="إضافة-التعليمات-البرمجية-إلى-getter-الخصائص">إضافة التعليمات البرمجية إلى getter الخصائص&lt;/h2>
&lt;p>نظرًا لأن لدينا جميع الطرق في الوقت الحالي، فيمكننا تعديل مُحضر الخاصية العامة للحصول على القيم.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesEnglish
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">get&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (_ResourcesEnglish == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> folderIndentifier = ConfigurationManager.AppSettings[&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span>]; ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> baseResources = GetDictionaryFromEmbedded(&lt;span style="color:#a5d6ff">&amp;#34;resources-demo.Properties.Resource&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> customResources = GetDictionaryFromFile(&lt;span style="color:#a5d6ff">$&amp;#34;\\Properties\\{folderIndentifier}\\Resource.resx&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;en&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ResourcesEnglish = OverwriteDictionary(baseResources, customResources);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> _ResourcesEnglish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; _ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> Dictionary&amp;lt;&lt;span style="color:#ff7b72">string&lt;/span>, &lt;span style="color:#ff7b72">string&lt;/span>&amp;gt; ResourcesSpanish
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">get&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (_ResourcesSpanish == &lt;span style="color:#79c0ff">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> folderIndentifier = ConfigurationManager.AppSettings[&lt;span style="color:#a5d6ff">&amp;#34;CustomResources.Folder&amp;#34;&lt;/span>]; ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> baseResources = GetDictionaryFromEmbedded(&lt;span style="color:#a5d6ff">&amp;#34;resources-demo.Properties.Resources&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> customResources = GetDictionaryFromFile(&lt;span style="color:#a5d6ff">$&amp;#34;\\Properties\\{folderIndentifier}\\Resources.es.resx&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _ResourcesSpanish = OverwriteDictionary(baseResources, customResources);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> _ResourcesSpanish;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>أولاً نحصل على المُعرف من app.settings.&lt;/li>
&lt;li>ثم نحصل على الموارد الأساسية، تلك المدمجة.&lt;/li>
&lt;li>بعد ذلك نحصل على الموارد المخصصة ولهذا نحتاج إلى اسم المجلد (وهو المعرف).&lt;/li>
&lt;li>ثم نخلطهم ونعيد القيمة.&lt;/li>
&lt;/ol>
&lt;p>سيتم كل هذا مرة واحدة، وبالتالي التحميل البطيء.&lt;/p>
&lt;h1 id="الاختبار">الاختبار&lt;/h1>
&lt;p>تم الانتهاء من كل ما يتعلق بالكود، فلنختبره الآن، ومن أجل الحصول على قيمة من القاموس علينا استدعاء الأسلوب &lt;code>CustomResources.GetText(string key)&lt;/code> الذي يُرجع القيمة.&lt;/p>
&lt;h2 id="تحديث-ملفات-الموارد-بأكملها">تحديث ملفات الموارد بأكملها&lt;/h2>
&lt;p>هذا الاختبار هو حالة عندما نريد تحديث المفتاح/القيمة الكاملة لملفات الموارد، كما ترون في الصور لدينا نفس المفاتيح ولكن قيم مختلفة.&lt;/p>
&lt;p>سنقوم باختبار &lt;code>John&lt;/code>، ومن أجل ضبط ذلك، سيتم تعيين app.config على &lt;code>&amp;lt;add key=&amp;quot;CustomResources.Folder&amp;quot; value=&amp;quot;John&amp;quot; /&amp;gt;&lt;/code>.&lt;/p>
&lt;p>الآن دعونا نتحقق من ملف المورد الأساسي الخاص بنا (&lt;code>Properties/Resource.es.resx&lt;/code>):&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/26d6cef147ca3cdd93a1d6cc4c60c212">&lt;img src="https://i.gyazo.com/26d6cef147ca3cdd93a1d6cc4c60c212.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>ثم ملف الموارد الخارجية الخاص بنا (&lt;code>Properties/John/Resource.es.resx&lt;/code>):&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/fd6582da0d6a427c2b21cc8b1999f9e0">&lt;img src="https://i.gyazo.com/fd6582da0d6a427c2b21cc8b1999f9e0.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>حسنًا، بعد أن تم ضبط كل شيء، نقوم بتشغيل تطبيق وحدة التحكم، فلنتوقف عند الجزء &lt;code>get&lt;/code> من الخصائص ونتحقق من كل شيء&lt;/p>
&lt;p>&lt;code>folderIdentifier&lt;/code> له قيمة إعداد التطبيقات:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ac30d3deb7abe29a877b368d9300b990">&lt;img src="https://i.gyazo.com/ac30d3deb7abe29a877b368d9300b990.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>&lt;code>baseResources&lt;/code> له قيمة الموارد الأساسية، تلك المضمنة:&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/a61410b365b55d4735f2b2622a18b966">&lt;img src="https://i.gyazo.com/a61410b365b55d4735f2b2622a18b966.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>&lt;code>customResources&lt;/code> لديه قيم الموارد الخارجية داخل المجلد &lt;code>John&lt;/code>:&lt;a href="https://gyazo.com/0e93733fbd616b274a69cdffbc16afb1">&lt;img src="https://i.gyazo.com/0e93733fbd616b274a69cdffbc16afb1.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>وأخيرًا، &lt;code>_ResourcesSpanish&lt;/code> له قيمة مختلطة من الموارد الأساسية إلى الموارد الخارجية.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/0e25a79023364ea1d5be2109dbf6492c">&lt;img src="https://i.gyazo.com/0e25a79023364ea1d5be2109dbf6492c.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="تحديث-واحد-فقط">تحديث واحد فقط&lt;/h2>
&lt;p>الآن دعونا نختبر نفس السيناريو ولكن مع وجود سيناريو مختلف، ما عليك سوى تحديث مفتاح واحد وترك الآخر كما هو.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/9c2a05fb976f1f671b13c9cf77220336">&lt;img src="https://i.gyazo.com/9c2a05fb976f1f671b13c9cf77220336.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>كما ترون، الملفات لها نفس معنى القيمة لـ &lt;code>Action_greeting&lt;/code> ولكن بقيمة مختلفة لـ &lt;code>Action_cancel&lt;/code>، لذلك يجب تحديث &lt;code>Action_cancel&lt;/code> فقط.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/16938859b05bb1f0fe08751a08b1fcad">&lt;img src="https://i.gyazo.com/16938859b05bb1f0fe08751a08b1fcad.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;h2 id="ملف-المورد-مفقود">ملف المورد مفقود&lt;/h2>
&lt;p>إذا لم تقم بتوفير ملف المورد الخارجي، فلا يهم على الإطلاق لأنه كما نتوقع أن يكون لدينا ملف، إذا فشل فإنه سيعود قاموسًا فارغًا وعند خلط كلا القواميس سينتهي الأمر بالقاموس الأساسي.&lt;/p>
&lt;h2 id="زوج-مفقود-في-المورد-المضمن">زوج مفقود في المورد المضمن&lt;/h2>
&lt;p>إذا كان لديك زوج في ملف المورد الخارجي، فلن يضيفه افتراضيًا إلى القاموس النهائي، يمكنك تغيير ذلك عن طريق استدعاء الطريقة &lt;code>OverwriteDictionary()&lt;/code> عند خلط كلا القواميس وتعيين المعلمة &lt;code>addIfDoesntExist&lt;/code> على &lt;code>true&lt;/code>.&lt;/p>
&lt;h2 id="لغات-مختلفة">لغات مختلفة&lt;/h2>
&lt;p>كما ترون لم نحدد أي لغة، لأن كل ذلك يتم بواسطة الوظيفة &lt;code>GetText(string key)&lt;/code> التي تستدعي &lt;code>GetText(string key, string language)&lt;/code>، ويتم ملء المعلمة &lt;code>language&lt;/code> بواسطة &lt;code>TwoLetterISOLanguageName&lt;/code> التي تُرجع اللغة الحالية لموضوعنا.&lt;/p>
&lt;p>في حالتي، لدي اللغة الإسبانية كلغة افتراضية ولهذا السبب تظهر دائمًا باللغة الإسبانية، ولكن يمكننا تجربة استخدام اللغة الإنجليزية أيضًا.&lt;/p>
&lt;p>دعونا نبرمج قليلاً ونبني شيئًا للتحقق من اللغتين الإسبانية والإنجليزية.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> Main(&lt;span style="color:#ff7b72">string&lt;/span>[] args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(&amp;#34;en-US&amp;#34;);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.Threading.Thread.CurrentThread.CurrentCulture = &lt;span style="color:#ff7b72">new&lt;/span> CultureInfo(&lt;span style="color:#a5d6ff">&amp;#34;en-US&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_greeting&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_cancel&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(&amp;#34;es-ES&amp;#34;);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.Threading.Thread.CurrentThread.CurrentCulture = &lt;span style="color:#ff7b72">new&lt;/span> CultureInfo(&lt;span style="color:#a5d6ff">&amp;#34;es-ES&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_greeting&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WriteText(&lt;span style="color:#a5d6ff">&amp;#34;Action_cancel&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.Read();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">private&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> &lt;span style="color:#ff7b72">void&lt;/span> WriteText(&lt;span style="color:#ff7b72">string&lt;/span> key) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(&lt;span style="color:#a5d6ff">$&amp;#34;[Culture: {System.Threading.Thread.CurrentThread.CurrentCulture}] Key: {key} -&amp;gt; value: {CustomResources.GetText(key)}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>عند تشغيل هذا، يجب أن يكون الإخراج شيء من هذا القبيل:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>[Culture: en-US] Key: Action_greeting -&amp;gt; value: Hello
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Culture: en-US] Key: Action_cancel -&amp;gt; value: Finish
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Culture: es-ES] Key: Action_greeting -&amp;gt; value: Hola
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[Culture: es-ES] Key: Action_cancel -&amp;gt; value: Terminar
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://gyazo.com/de7e5efcc2ff5e82529749b1188706ff">&lt;img src="https://i.gyazo.com/de7e5efcc2ff5e82529749b1188706ff.png" alt="صورة من جيازو">&lt;/a>&lt;/p>
&lt;p>أول قيمتين من &lt;code>resources.resx&lt;/code> وهي باللغة الإنجليزية والأخيرة كلاهما باللغة الإسبانية ويتم استرداد القيم من &lt;code>resources.es.resx&lt;/code>.&lt;/p>
&lt;h1 id="هذا-كل-شيء">هذا كل شيء&lt;/h1>
&lt;p>وجدنا في هذا البرنامج التعليمي طريقة لدمج كل من الموارد المضمنة والخارجية للغات، وكان هذا حلاً لمشكلة واجهناها في الفريق، وهي تعمل منذ ذلك الحين دون أي مشاكل.&lt;/p>
&lt;p>يمكنك التحقق من الكود المصدري &lt;a href="https://github.com/emimontesdeoca/resources-demo">هنا&lt;/a>.&lt;/p>
&lt;p>إذا كان لديك أي سؤال فلا تتردد في إرسال تغريدة لي على &lt;a href="https://twitter.com/emimontesdeocaa">@emimontesdeocaa&lt;/a> وسأعود إليك عندما يتوفر لدي الوقت.&lt;/p></content:encoded><category>.NET</category></item><item><title>اختبار التكامل باستخدام Bot Framework وDirectLine لحالات التدفق</title><link>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-with-flow-cases/</link><pubDate>Wed, 25 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-with-flow-cases/</guid><description>قم بتوسيع اختبارات تكامل Bot Framework لدعم سيناريوهات تدفق المحادثة متعددة المنعطفات.</description><content:encoded>&lt;h2 id="مقدمة">مقدمة&lt;/h2>
&lt;p>في منشورات المدونة السابقة، أجرينا بعض اختبارات التكامل لحالات فردية. &amp;ldquo;الحالات الفردية&amp;rdquo; هي تلك الحالات التي بعد سؤال الروبوت عن شيء ما، سوف يرد مرة واحدة فقط ثم نقوم بمقارنة النتائج.&lt;/p>
&lt;p>&lt;strong>لن يشرح هذا الدليل كيفية إجراء المصادقة واستدعاءات واجهة برمجة التطبيقات (API)، إذا كنت تريد التحقق من ذلك، فيرجى مراجعة دليل الحالات الفردية.&lt;/strong>&lt;/p>
&lt;h3 id="ماذا-عن-محادثات-التدفق">ماذا عن محادثات التدفق؟&lt;/h3>
&lt;p>باستخدام الطريقة الحالية، ليس لدينا أي نوع من التدفق في المحادثة، على سبيل المثال، إذا كنت تريد طلب المساعدة ثم تظهر قائمة بخيارات مختلفة، ثم بعد تحديد أحدها، تظهر قائمة أخرى. وهذا من شأنه أن يجعل مثل 2 أو أكثر &lt;code>responses&lt;/code>. &lt;strong>وهذا يعني أن استخدام حل اختبار التكامل الذي استخدمناه من قبل، لن ينجح.&lt;/strong>&lt;/p>
&lt;p>فيما يلي رسم تخطيطي لكيفية عمل اختبار التكامل للحالات الفردية.&lt;/p>
&lt;p>&lt;a href="https://i.gyazo.com/8915f2653033c1143947ef59196403f4.png">!https://gyazo.com/8915f2653033c1143947ef59196403f4&lt;/a>](&lt;a href="https://gyazo.com/8915f2653033c1143947ef59196403f4">https://gyazo.com/8915f2653033c1143947ef59196403f4&lt;/a>)&lt;/p>
&lt;p>وإليك رسم تخطيطي لكيفية قيامنا بتكييف حل اختبار التكامل الحالي مع حالات التدفق.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83">&lt;img src="https://i.gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83.png" alt="https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83">&lt;/a>&lt;/p>
&lt;p>&lt;strong>لن يغطي الشرح التالي جميع المعلومات الأساسية حول كيفية عمل Bot Framework، إذا لم تكن تفهم ذلك، فيرجى الانتقال والتحقق من الوثائق الرسمية.&lt;/strong>&lt;/p>
&lt;h2 id="حالة-مثال">حالة مثال&lt;/h2>
&lt;p>بالنسبة للدليل التالي، سأستخدم روبوتًا يتضمن محادثة سلسة قمت بإنشائها، والتي تطلب المساعدة ثم تحديد خيارات مختلفة.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/0a1104cce67c07331a6e0fbd8e19b3e2">&lt;img src="https://i.gyazo.com/0a1104cce67c07331a6e0fbd8e19b3e2.gif" alt="https://gyazo.com/0a1104cce67c07331a6e0fbd8e19b3e2">&lt;/a>&lt;/p>
&lt;h2 id="بنية-json-جديدة">بنية JSON جديدة&lt;/h2>
&lt;p>الآن بعد أن أصبح لدينا أكثر من &lt;code>request&lt;/code> عند التحدث إلى الروبوت، نحتاج إلى تعديل بنية json لإضافة جميع &lt;code>request&lt;/code> التي سنقوم بها.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;secret&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;direct-line-secret&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineGenerateTokenEndpoint&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/tokens/generate&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineConversationEndpoint&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/conversations/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entries&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;PedirAyuda&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requests&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Ayuda&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Telefono&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Oficina&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Tenerife&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;response&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.901Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;localTimestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T09:04:37+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;serviceUrl&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;http://localhost:50629&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;emulator&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;j98bbdf097a&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Bot&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;conversation&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;eabcie4be8ak&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;recipient&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;922920252&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;attachments&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;replyToId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;47me557ikbf7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;assert&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Request.Text == Response.Text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كما ترون، هناك تغيير مهم، &lt;code>request&lt;/code> أصبح الآن &lt;code>requests&lt;/code>. وهذا يعني أن لدينا الآن &lt;code>List&amp;lt;Activity&amp;gt;&lt;/code> بدلاً من &lt;code>activity&lt;/code> واحد.&lt;/p>
&lt;h2 id="كائنات-جديدة">كائنات جديدة&lt;/h2>
&lt;p>في السابق، قمنا بتعيين كائناتنا لحالات فردية: &lt;code>TestEntry&lt;/code> و&lt;code>TestEntryCollection&lt;/code>. بالنسبة لحالات التدفق، سنقوم بإنشاء كائنات جديدة: &lt;code>TestEntryFlow&lt;/code> و&lt;code>TestEntryFlowCollection&lt;/code>.&lt;/p>
&lt;h3 id="testentryflow">&lt;code>TestEntryFlow&lt;/code>&lt;/h3>
&lt;p>هذا الكائن مخصص لكل إدخال لدينا في المجموعة، انظر إلى أن كائن &lt;code>Requests&lt;/code> أصبح الآن &lt;code>List&amp;lt;Activity&amp;gt;&lt;/code> بدلاً من &lt;code>Activity&lt;/code> كما ذكرت من قبل.&lt;/p>
&lt;p>نظرًا لأننا سنطلب من الروبوت عدة مرات، فنحن بحاجة إلى عدة &lt;code>activities&lt;/code> سيتم إرسالها إلى المحادثة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntryFlow&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entry name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;name&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity requested by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;requests&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;Activity&amp;gt; Requests { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity response expected by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;response&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Response { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert value in string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;assert&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Assert { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="testentirescollection">&lt;code>TestEntiresCollection&lt;/code>&lt;/h3>
&lt;p>سيحتوي هذا الكائن على المعلومات ذات الصلة بـ &lt;code>DirectLine&lt;/code> مثل &lt;code>secret&lt;/code> ونقاط النهاية، بالإضافة إلى قائمة &lt;code>Entries&lt;/code> التي سنختبرها.&lt;/p>
&lt;p>لاحظ أن &lt;code>Entries&lt;/code> الآن عبارة عن قائمة بـ &lt;code>TestEntryFlow&lt;/code> وليس &lt;code>TestEntry&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntryFlowCollection&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// DirectLine Secret&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;secret&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Secret { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint to get the token using the secret for DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineGenerateTokenEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineGenerateTokenEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint for a conversation in DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineConversationEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineConversationEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entries list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;entries&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;TestEntryFlow&amp;gt; Entries { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="إنشاء-testmethod-لحالات-التدفق">إنشاء &lt;code>TestMethod&lt;/code> لحالات التدفق&lt;/h2>
&lt;h3 id="التدفق-الجديد">التدفق الجديد&lt;/h3>
&lt;p>أولاً، قم بإلقاء نظرة مرة أخرى على الرسم البياني مرة أخرى (وهو نفس الرسم البياني الذي نشرته أعلاه).&lt;/p>
&lt;p>&lt;a href="https://i.gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83.png">!https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83&lt;/a>](&lt;a href="https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83">https://gyazo.com/ef77fa168d3b5f8b4a116ff38b5edd83&lt;/a>)&lt;/p>
&lt;p>كما ترون، فإن بنية التدفق لإجراء الاختبار هي نفسها تقريبًا:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>احصل على المعلومات&lt;/p>
&lt;/li>
&lt;li>
&lt;p>المصادقة&lt;/p>
&lt;/li>
&lt;li>
&lt;p>إنشاء محادثة&lt;strong>وهنا ما يتغير، الآن علينا أن نرسل عدة مرات كل الطلب إلى المحادثة. وللقيام بذلك، يتعين علينا إجراء تكرار لكل طلب، وإرساله إلى الروبوت، ثم مقارنة الاستجابة الأخيرة باستجابتنا المتوقعة.&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>إرسال جميع الطلبات&lt;/p>
&lt;/li>
&lt;li>
&lt;p>الحصول على كافة الرسائل&lt;/p>
&lt;/li>
&lt;li>
&lt;p>احصل على أحدث استجابة&lt;/p>
&lt;/li>
&lt;li>
&lt;p>قارن مع الاستجابة المتوقعة&lt;/p>
&lt;/li>
&lt;li>
&lt;p>تأكيد النتيجة&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="الكود">الكود&lt;/h3>
&lt;p>أولًا، نحتاج إلى الحصول على المعلومات من الملف، وهذا هو نفس ما فعلناه من قبل مع الحالات الفردية.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\dataFlow.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntryFlowCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن علينا إجراء حلقة لكل &lt;code>TestEntryFlow&lt;/code>، من &lt;code>data.entries&lt;/code>، وبهذا يمكننا اتباع نفس التدفق الذي فعلناه في الحالات الفردية حتى الجزء الجديد، حيث نقوم بالتكرار في &lt;code>requests&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Activity latestResponse = &lt;span style="color:#ff7b72">new&lt;/span> Activity();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act for step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 - Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الخطوة التالية بسيطة جدًا، علينا تكرار &lt;code>entry.requests&lt;/code> وإرسال كل &lt;code>activity&lt;/code> إلى المحادثة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">foreach&lt;/span> (Activity step &lt;span style="color:#ff7b72">in&lt;/span> entry.Requests)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (step.Type == ActivityTypes.Message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(step));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نستخدم &lt;code>watermark&lt;/code> للحصول على أحدث رسالة، &lt;code>watermark&lt;/code> هي القيمة التي ترجعها واجهة برمجة تطبيقات DirectLine عند طلب معلومات المحادثة.&lt;/p>
&lt;p>بعد ذلك، علينا فقط ملء &lt;code>globals&lt;/code> بـ &lt;code>latestReponse&lt;/code> و &lt;code>expectedResponse&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ولإنهاء الحالة، نقوم بتقييم السلسلة &lt;code>assert&lt;/code> في &lt;code>entry&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="الكود-النهائي">الكود النهائي&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task ShouldTestFlowCases()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\dataFlow.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntryFlowCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Flow: Arrange -&amp;gt; Act -&amp;gt; arrange -&amp;gt; assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (TestEntryFlow entry &lt;span style="color:#ff7b72">in&lt;/span> data.Entries)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Activity latestResponse = &lt;span style="color:#ff7b72">new&lt;/span> Activity();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act for step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 -Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (Activity step &lt;span style="color:#ff7b72">in&lt;/span> entry.Requests)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (step.Type == ActivityTypes.Message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Step&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(step));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="التحسينات">التحسينات&lt;/h2>
&lt;p>أعتقد أن التدفق الأفضل قد يكون ممكنًا، ولكن هذا التحسين سيعني أنه يجب تغيير بنية JSON أيضًا. أيضًا، لتحقيق ذلك، يجب ملء ملف json بشكل أكبر.&lt;/p>
&lt;p>من أجل تحسين هذا الاختبار، يجب أن يكون لدينا &lt;code>response&lt;/code> لكل &lt;code>request&lt;/code>، ويجب التأكيد في كل مرة نرسل فيها رسالة. الطريقة التي نقوم بها الآن هي من خلال تخزين كل &lt;code>requests&lt;/code> و &lt;strong>النهائي &lt;code>response&lt;/code>&lt;/strong> .&lt;/p>
&lt;p>لقد قمت بعمل رسم تخطيطي لإظهار كيف سيبدو هذا.&lt;/p>
&lt;p>&lt;a href="https://i.gyazo.com/dfd4e9f87ff69159f02a0bcc70ae1edc.png">!https://gyazo.com/dfd4e9f87ff69159f02a0bcc70ae1edc&lt;/a>](&lt;a href="https://gyazo.com/dfd4e9f87ff69159f02a0bcc70ae1edc">https://gyazo.com/dfd4e9f87ff69159f02a0bcc70ae1edc&lt;/a>)&lt;/p>
&lt;p>أعتقد بشدة أن هذه الطريقة أفضل بكثير بشكل عام لسلامة الاختبار، نظرًا لأنك تختبر تقريبًا كل سلوك في التدفق بدلاً من مجرد اختبار الاستجابة النهائية.&lt;/p>
&lt;hr>
&lt;p>&lt;strong>حسنًا، هذا كل ما في هذا الدليل، يرجى تذكر أن هذا الدليل يهدف إلى أن يكون استمرارًا لدليل الحالات الفردية، إذا شعرت بالضياع، فراجع هذا الدليل الأطول والذي يحتوي على المزيد من الشرح لكل شيء.&lt;/strong>&lt;/p>
&lt;p>تذكر أنه تم تخزين كافة التعليمات البرمجية في جيثب الخاص بي في مستودع &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">هذا&lt;/a>.&lt;/p>
&lt;p>اتمنى لك يوم جيد!&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category></item><item><title>اختبار التكامل باستخدام Bot Framework و DirectLine (2)</title><link>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-2/</link><pubDate>Tue, 24 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-2/</guid><description>تنفيذ ترخيص DirectLine واستدعاءات واجهة برمجة التطبيقات (API) لاختبار تكامل Bot Framework (الجزء الثاني).</description><content:encoded>&lt;h4 id="في-هذا-الجزء-سنقوم-بترخيص-directline-والحصول-على-القيم-من-استجابة-الروبوت">في هذا الجزء، سنقوم بترخيص DirectLine والحصول على القيم من استجابة الروبوت.&lt;/h4>
&lt;p>الآن بعد أن قمنا بإلغاء التسلسل، حان الوقت للحصول على المعلومات من المجموعة والأجزاء الكاملة التي سنستخدمها للحصول على الترخيص، وإجراء مكالمات API وتأكيد النتيجة.&lt;/p>
&lt;p>&lt;strong>لن يغطي الشرح التالي جميع المعلومات الأساسية حول كيفية عمل Bot Framework، إذا لم تكن تفهم ذلك، فيرجى الانتقال والتحقق من الوثائق الرسمية.&lt;/strong>&lt;/p>
&lt;h2 id="إجراء-مكالمات-api-باستخدام-webclient">إجراء مكالمات API باستخدام WebClient&lt;/h2>
&lt;p>لتسهيل الاتصال بواجهة برمجة التطبيقات، قمت بإنشاء فئة &lt;code>utils&lt;/code>، حيث نحفظ الوظائف التي سنستخدمها عدة مرات، تتضمن هذه الفئة &lt;code>uploadString&lt;/code> لـ POST و &lt;code>downloadString&lt;/code> لـ GET.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Utils&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Uploads to an URL and gets result&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;typeparam name=&amp;#34;T&amp;#34;&amp;gt;Type of object you are receiving&amp;lt;/typeparam&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;bearer&amp;#34;&amp;gt;Token&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;url&amp;#34;&amp;gt;Url&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;serializedJson&amp;#34;&amp;gt;Serialized JSON to send&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T uploadString&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> bearer, &lt;span style="color:#ff7b72">string&lt;/span> url, &lt;span style="color:#ff7b72">string&lt;/span> serializedJson)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> serializedResult = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Webclient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> client = &lt;span style="color:#ff7b72">new&lt;/span> WebClient())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Add headers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Content-Type&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Authorization&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">$&amp;#34;Bearer {bearer}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Upload string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> serializedResult = client.UploadString(url, serializedJson);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Get result and return it as an object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(serializedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Downloads from URL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;typeparam name=&amp;#34;T&amp;#34;&amp;gt;Type of object you are receiving&amp;lt;/typeparam&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;bearer&amp;#34;&amp;gt;Token&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;param name=&amp;#34;url&amp;#34;&amp;gt;Url&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">static&lt;/span> T downloadString&amp;lt;T&amp;gt;(&lt;span style="color:#ff7b72">string&lt;/span> bearer, &lt;span style="color:#ff7b72">string&lt;/span> url)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> serializedResult = &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Webclient&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">using&lt;/span> (&lt;span style="color:#ff7b72">var&lt;/span> client = &lt;span style="color:#ff7b72">new&lt;/span> WebClient())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Add headers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Content-Type&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">&amp;#34;application/json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client.Headers.Add(&lt;span style="color:#a5d6ff">&amp;#34;Authorization&amp;#34;&lt;/span>, &lt;span style="color:#a5d6ff">$&amp;#34;Bearer {bearer}&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Download string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> serializedResult = client.DownloadString(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Get result and return it as an object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">return&lt;/span> JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(serializedResult);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="ترخيص-الخط-المباشر">ترخيص الخط المباشر&lt;/h2>
&lt;p>إذا قرأت الوثائق الرسمية، يمكنك العثور على كيفية القيام بذلك، وهو أمر سهل للغاية، باستخدام وظائفنا يصبح الأمر أسهل. أولًا، تذكر أننا داخل عبارة foreach، نظرًا لأننا نقوم بالمصادقة لكل حالة، في حالة نفاد الوقت، مما يعني فشل الاختبار.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن لدينا الرمز المميز، والذي سيتم استخدامه لإجراء جميع الاستدعاءات التالية إلى نقطة نهاية المحادثة.&lt;/p>
&lt;h2 id="إنشاء-محادثة">إنشاء محادثة.&lt;/h2>
&lt;p>من أجل التحدث إلى الروبوت، نحتاج أولاً إلى إنشاء محادثة، وستعيد هذه المحادثة رمزًا مميزًا جديدًا يتضمن معرف المحادثة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 -Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>نقوم أيضًا بتخزين &lt;code>newToken&lt;/code> و&lt;code>conversationId&lt;/code>، وكلاهما ضروري للمستخدم لإرسال رسائل إلى الروبوت.&lt;/p>
&lt;h2 id="إرسال-النشاط-إلى-المحادثة">إرسال النشاط إلى المحادثة&lt;/h2>
&lt;p>الآن، باستخدام &lt;code>conversationId&lt;/code> و&lt;code>conversationEndpoint&lt;/code>، يمكننا إنشاء نقطة النهاية النهائية لإرسال &lt;code>Activity&lt;/code> وهو &lt;code>request&lt;/code> من ملف json.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(entry.Request));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="احصل-على-أحدث-رسالة">احصل على أحدث رسالة&lt;/h2>
&lt;p>في سجل الرسائل، بعد أن أرسلنا النشاط، من المفترض أن يكون الروبوت قد استجاب بالفعل، لذلك يتعين علينا الحصول على جميع الرسائل التي تحتوي على العلامة المائية ثم استخدام تلك العلامة المائية، لتصفية أحدث رسالة/نشاط.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وهذا كل ما في هذا الجزء، الجزء التالي سيتضمن الجزء الذي نحصل فيه على النص من &lt;code>assert&lt;/code> في json، وتحويله إلى كود مثل استخدام &lt;code>eval()&lt;/code> في Javascript ولكن في C#، ثم استخدام &lt;code>Assert.isTrue()&lt;/code> للحصول على نتيجة الاختبار النهائية.&lt;/p>
&lt;p>تذكر أنه تم تخزين كافة التعليمات البرمجية في جيثب الخاص بي في مستودع &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">هذا&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category></item><item><title>اختبار التكامل باستخدام Bot Framework وDirectLine (1)</title><link>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-1/</link><pubDate>Tue, 24 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-1/</guid><description>قم بإعداد اختبارات التكامل لروبوتات الدردشة Bot Framework باستخدام حالات اختبار DirectLine API وJSON.</description><content:encoded>&lt;p>منذ أن بدأت العمل في إحدى الشركات لفترة قصيرة من الزمن، تم تكليفي بالعمل على اختبار التكامل لإطار عمل الروبوت.&lt;/p>
&lt;p>عملي في المجموعة هو جعل الروبوت مستقرًا قدر الإمكان، ولكن أولاً، ما هو إطار عمل الروبوت؟&lt;/p>
&lt;blockquote>
&lt;p>قم ببناء الروبوتات الذكية وتوصيلها ونشرها وإدارتها للتفاعل بشكل طبيعي مع المستخدمين لديك على موقع ويب وتطبيق وCortana وMicrosoft Teams وSkype وSlack وFacebook Messenger والمزيد. ابدأ بسرعة مع بيئة بناء الروبوتات الكاملة، كل ذلك مع الدفع مقابل ما تستخدمه فقط.&lt;/p>&lt;/blockquote>
&lt;p>يمكنك العثور على مزيد من المعلومات حول Bot Framework مباشرة &lt;a href="https://dev.botframework.com/">هنا&lt;/a>.&lt;/p>
&lt;p>&lt;strong>لن يغطي الشرح التالي جميع المعلومات الأساسية حول كيفية عمل Bot Framework، إذا لم تكن تفهم ذلك، فيرجى الانتقال والتحقق من الوثائق الرسمية.&lt;/strong>&lt;/p>
&lt;h2 id="لماذا-أحتاج-إلى-اختبار-التكامل">لماذا أحتاج إلى اختبار التكامل؟&lt;/h2>
&lt;p>هناك حاجة إلى اختبار التكامل لأنه في كل مرة يقوم أحد زملائي في العمل بدفع إصلاح أو ميزة جديدة أو حتى خطأ جديد، سيتم تشغيل هذه الاختبارات قبل دفع التعليمات البرمجية إلى الإنتاج وإذا فشل أي من الاختبارات، فلن يتم نقل التعليمات البرمجية إلى الإنتاج، مما يعني أن المستخدم النهائي لن يكون لديه الخطأ.&lt;/p>
&lt;h2 id="اختبار-التكامل-في-الروبوتات">اختبار التكامل في الروبوتات؟&lt;/h2>
&lt;p>أعتقد أن اختبار التكامل مهم جدًا في الروبوتات. لا يمكن أن يكون لديك روبوت حيث لا تعمل بعض قوائمه، أو لا تقوم بعض الميزات بإرجاع أي شيء.&lt;/p>
&lt;p>تستخدم الشركات الروبوتات لعملائها لأنها لا تريد أن ينشغل الناس بمشاكلهم، فإذا كان الروبوت قادرًا على مساعدة المستخدم، فسيتمكن العمال الآخرون من استخدام وقتهم للقيام بشيء أكثر أهمية.&lt;/p>
&lt;h2 id="نظرة-عامة-على-الحل">نظرة عامة على الحل.&lt;/h2>
&lt;p>من أجل إنجاز هذا العمل، استخدمت مشروع اختبار في Visual Studio، والذي سيستخدم WebClient لـ API Rest وملف Json، حيث سنقوم بتخزين حالاتنا.&lt;/p>
&lt;h2 id="ملف-جيسون">ملف جيسون&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;secret&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;direct-line-secret&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineGenerateTokenEndpoint&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/tokens/generate&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;directlineConversationEndpoint&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;https://directline.botframework.com/v3/directline/conversations/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entries&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;DecirHola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;request&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Hola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;response&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.901Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;localTimestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T09:04:37+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;serviceUrl&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;http://localhost:50629&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;emulator&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;j98bbdf097a&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Bot&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;conversation&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;eabcie4be8ak&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;recipient&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;No tengo respuesta para eso.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;attachments&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;replyToId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;47me557ikbf7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;assert&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Request.Text == Response.Text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>كما ترون لدينا:&lt;/p>
&lt;ul>
&lt;li>سر الخط المباشر -&amp;gt; سر من الروبوت المنشور&lt;/li>
&lt;li>نقطة نهاية إنشاء الرمز المميز للخط المباشر -&amp;gt; نقطة النهاية للحصول على الرمز المميز باستخدام السر&lt;/li>
&lt;li>نقطة نهاية محادثة الخط المباشر -&amp;gt; نقطة النهاية للعب بالمحادثة&lt;/li>
&lt;li>الدخول -&amp;gt; حالة الاختبار
&lt;ul>
&lt;li>طلب -&amp;gt; ما نرسله إلى المحادثة&lt;/li>
&lt;li>الاستجابة -&amp;gt; ما نتوقع الحصول عليه&lt;/li>
&lt;li>أكد -&amp;gt; ما الذي نقارنه&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="إلغاء-التسلسل">إلغاء التسلسل&lt;/h2>
&lt;p>لدينا ملف json منسق بشكل مثالي، وعلينا الآن تحميله في الحل، لذلك سنستخدم JSON.NET وبعض الفئات. أولاً لدينا مجموعة الإدخالات، التي تحتوي على كل شيء، وبعد ذلك لدينا قائمة إدخالات لكل مجموعة.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Object to parse from the file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntriesCollection&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// DirectLine Secret&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;secret&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Secret { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint to get the token using the secret for DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineGenerateTokenEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineGenerateTokenEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Endpoint for a conversation in DirectLine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;directlineConversationEndpoint&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> DirectLineConversationEndpoint { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entries list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;entries&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> List&amp;lt;TestEntry&amp;gt; Entries { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وهذا هو الكائن الذي يحتوي على الاسم والطلب والاستجابة والتأكيد لحالة الاختبار.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">TestEntry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Entry name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;name&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Name { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity requested by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;request&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Request { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Activity response expected by the entry&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;response&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Response { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert value in string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [JsonProperty(&amp;#34;assert&amp;#34;)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">string&lt;/span> Assert { &lt;span style="color:#ff7b72">get&lt;/span>; &lt;span style="color:#ff7b72">set&lt;/span>; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="التحليل-من-json-إلى-الكائن-في-حالة-الاختبار">التحليل من json إلى الكائن في حالة الاختبار&lt;/h2>
&lt;p>إن الحصول على فئات لكائن التحليل أمر سهل للغاية لأننا نحتاج إلى قراءته ككائن.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\data.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntriesCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>الآن مع هذه المجموعة، سنكون قادرين على تكرارها والحصول على المعلومات باستخدام، على سبيل المثال، foreach.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (TestEntry entry &lt;span style="color:#ff7b72">in&lt;/span> data.Entries)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ....
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>وهذا كل ما في هذا الجزء، الجزء التالي سيتضمن ترخيص DirectLine، تذكر أنه يتم تخزين جميع التعليمات البرمجية في جيثب الخاص بي في مستودع &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">هذا&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category></item><item><title>اختبار التكامل باستخدام Bot Framework وDirectLine (3)</title><link>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-3/</link><pubDate>Tue, 24 Apr 2018 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/posts/integration-test-bot-framework-3/</guid><description>قم بتقييم استجابات الروبوت باستخدام تحليل كود Roslyn في اختبارات تكامل Bot Framework (الجزء 3).</description><content:encoded>&lt;h4 id="هذا-هو-الجزء-الأحدث-من-الدليل-في-هذا-الجزء-سنقوم-بتقييم-النص-من-التأكيد-في-ملف-json">هذا هو الجزء الأحدث من الدليل، في هذا الجزء، سنقوم بتقييم النص من التأكيد في ملف json.&lt;/h4>
&lt;p>الآن بعد أن استخدمنا &lt;code>request&lt;/code> وأرسلنا كـ &lt;code>activity&lt;/code> إلى الروبوت، حصلنا على &lt;code>response&lt;/code> وعلينا مقارنة ما &lt;code>assert&lt;/code> في json (الاستجابة التي قمنا بتخزينها في json، هذه هي الاستجابة المتوقعة) مع الاستجابة التي حصلنا عليها من الروبوت.&lt;/p>
&lt;p>&lt;strong>لن يغطي الشرح التالي جميع المعلومات الأساسية حول كيفية عمل Bot Framework، إذا لم تكن تفهم ذلك، فيرجى الانتقال والتحقق من الوثائق الرسمية.&lt;/strong>&lt;/p>
&lt;h2 id="إضافة-microsoftcodeanalogy-إلى-الحل">إضافة Microsoft.CodeAnalogy إلى الحل&lt;/h2>
&lt;p>أولاً، يتعين علينا تضمين CodeAnalogy كحزمة NuGet.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/ce97a60ecab998f162f6ac7a2ab2c9a7">&lt;img src="https://i.gyazo.com/ce97a60ecab998f162f6ac7a2ab2c9a7.png" alt="https://gyazo.com/ce97a60ecab998f162f6ac7a2ab2c9a7">&lt;/a>&lt;/p>
&lt;p>بعد التثبيت، تذكر إضافة الحزمة إلى ملف &lt;code>.cs&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">using&lt;/span> &lt;span style="color:#ff7b72">Microsoft.CodeAnalysis.CSharp.Scripting&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="إنشاء-كائن-globals">إنشاء كائن &lt;code>Globals&lt;/code>&lt;/h2>
&lt;p>من أجل التقييم، علينا تمرير المعلمات إلى المقيم. يحتاج هذا المقيم إلى ملف التكوين.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Object to pass parameters to Roslyn compiler&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">class&lt;/span> &lt;span style="color:#f0883e;font-weight:bold">Globals&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// ExpectedResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Request;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// ReceivedResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">public&lt;/span> Activity Response;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>هذا الجزء مهم جدًا، في ملف التكوين هذا، سنقوم بتضمين الكائنات التي سنقارنها، لذلك بالنسبة لنا، نحتاج إلى تمرير &lt;strong>الاستجابة المتوقعة&lt;/strong> و &lt;strong>الاستجابة المستلمة&lt;/strong>.&lt;/p>
&lt;p>باستخدام هذه المعلومات، سيتم استخدام &lt;code>assert&lt;/code> في ملف json وسيقوم بتقييم ما يقال.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>** يقوم &lt;code>EvaluateAsync&amp;lt;T&amp;gt;&lt;/code> بتقييم وإرجاع T، في حالتنا نمرر &lt;code>string&lt;/code> للتقييم و&lt;code>globals&lt;/code>، الذي يحتوي على البيانات التي سيتم تقييمها.**&lt;/p>
&lt;p>سأحاول شرح ذلك بمثال، باستخدام إدخال (يحتوي على اسم وطلب واستجابة وتأكيد).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;DecirHola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;request&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Hola&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;textFormat&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;plain&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.195Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelData&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;clientActivityId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;1523261059363.6264723268323733.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;ClientCapabilities&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;requiresBotState&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsTts&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;supportsListening&amp;#34;&lt;/span>: &lt;span style="color:#79c0ff">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;response&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;type&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;message&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T08:04:37.901Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;localTimestamp&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;2018-04-09T09:04:37+01:00&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;serviceUrl&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;http://localhost:50629&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;channelId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;emulator&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;from&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;j98bbdf097a&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;name&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Bot&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;conversation&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;eabcie4be8ak&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;recipient&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;default-user&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;locale&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;es&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;text&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;No tengo respuesta para eso.&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;attachments&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;entities&amp;#34;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;replyToId&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;61hacck8j6jg&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;id&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;47me557ikbf7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#7ee787">&amp;#34;assert&amp;#34;&lt;/span>: &lt;span style="color:#a5d6ff">&amp;#34;Request.Text == Response.Text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>دعنا نحصل على الأشياء المهمة من هذا الإدخال، أولاً التأكيد &lt;strong>&lt;code>&amp;quot;assert&amp;quot;: &amp;quot;Request.Text == Response.Text&amp;quot;&lt;/code>&lt;/strong>، وهذا يعني أنه سيقارن &lt;code>Request.Text&lt;/code> مع &lt;code>Response.Text&lt;/code>، ويعيد القيمة كقيمة منطقية.&lt;/p>
&lt;p>لكن عندما نستدعي الدالة &lt;code>await CSharpScript.EvaluateAsync&amp;lt;bool&amp;gt;(entry.Assert, globals: globals)&lt;/code> فإننا نمرر معلمتين:&lt;/p>
&lt;ul>
&lt;li>&lt;code>string&lt;/code> السلسلة المطلوب تقييمها -&amp;gt; &lt;code>&amp;quot;Request.Text == Response.Text&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;code>globals&lt;/code> بيانات للمقيم -&amp;gt; في هذه الحالة يتعين علينا تقديم &lt;code>Request&lt;/code> و&lt;code>Response&lt;/code>، الطلب هو &lt;strong>ردنا المتوقع&lt;/strong> والرد هو &lt;strong>الرد المستلم&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>بما أننا نقوم بملء البيانات في المقيم، يمكننا الآن استخدام السلسلة وتقييمها، لذلك ستُرجع &lt;code>true&lt;/code> أو &lt;code>false&lt;/code>.&lt;/p>
&lt;h2 id="تم">تم&lt;/h2>
&lt;p>لقد انتهينا، هنا يمكنك رؤية &lt;code>TestMethod&lt;/code> النهائي&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>[TestMethod]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff7b72">public&lt;/span> &lt;span style="color:#ff7b72">async&lt;/span> Task ShouldTestSingleCases()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Load entries from file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> path = System.IO.File.ReadAllText(&lt;span style="color:#a5d6ff">@&amp;#34;C:\data.json&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// Deserialize to object&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> data = JsonConvert.DeserializeObject&amp;lt;TestEntriesCollection&amp;gt;(path);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Flow: Arrange -&amp;gt; Act -&amp;gt; arrange -&amp;gt; assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">foreach&lt;/span> (TestEntry entry &lt;span style="color:#ff7b72">in&lt;/span> data.Entries)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with current requested values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> token, newToken, conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">if&lt;/span> (entry.Request.Type == ActivityTypes.Message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Act&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 1 - Get token using secret from DirectLine in BotFramework panel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> token = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(data.Secret, data.DirectLineGenerateTokenEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>).token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 2 -Create a new conversation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> createdConversation = Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(token, data.DirectLineConversationEndpoint, &lt;span style="color:#a5d6ff">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-style:italic">// This returns a new token and a conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newToken = createdConversation.token;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> conversationId = createdConversation.conversationId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 3 - Send an activity to the conversation with new token and conversationId&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">string&lt;/span> directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + &lt;span style="color:#a5d6ff">&amp;#34;/activities&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Utils.uploadString&amp;lt;DirectLineAuth&amp;gt;(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(entry.Request));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 4 - Get all activities, we get a List&amp;lt;activity&amp;gt; and a watermark&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> getLastActivity = Utils.downloadString&amp;lt;ActivityResponse&amp;gt;(newToken, directlineConversationActivitiesEndpoint);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// 5 - Get the latest activity which is the response we should be expecting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Arrange with new values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">var&lt;/span> globals = &lt;span style="color:#ff7b72">new&lt;/span> Objects.Globals { Request = entry.Response, Response = latestResponse };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8b949e;font-weight:bold;font-style:italic">/// Assert&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Assert.IsTrue(&lt;span style="color:#ff7b72">await&lt;/span> CSharpScript.EvaluateAsync&amp;lt;&lt;span style="color:#ff7b72">bool&lt;/span>&amp;gt;(entry.Assert, globals: globals));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff7b72">await&lt;/span> Task.CompletedTask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="رسم-بياني">رسم بياني&lt;/h2>
&lt;p>فيما يلي رسم تخطيطي لكل التدفق الذي اتبعناه للوصول إلى هذه النقطة، ونأمل أن يوضح الأمر إذا لم تفهم شيئًا ما.&lt;/p>
&lt;p>&lt;a href="https://gyazo.com/1e3b7c9c2286844062878b4b8ca02d2d">&lt;img src="https://i.gyazo.com/1e3b7c9c2286844062878b4b8ca02d2d.png" alt="https://gyazo.com/1e3b7c9c2286844062878b4b8ca02d2d">&lt;/a>&lt;/p>
&lt;p>وهذا كل شيء، ويتم ذلك للحالات الفردية حيث تكون الحالة من 1 إلى 1، ويرسل المستخدم &lt;code>activity&lt;/code> ويعيد الروبوت أغنية واحدة أخرى &lt;code>activity&lt;/code>.&lt;/p>
&lt;p>أتمنى أن تنال إعجابك، &lt;strong>التالي سيكون حالات تدفق الاختبار مع أكثر من استجابة.&lt;/strong>&lt;/p>
&lt;p>&lt;a href="https://i.gyazo.com/d964cfac395ed438a5282e60614863e7.png">!https://gyazo.com/d964cfac395ed438a5282e60614863e7&lt;/a>](&lt;a href="https://gyazo.com/d964cfac395ed438a5282e60614863e7">https://gyazo.com/d964cfac395ed438a5282e60614863e7&lt;/a>)&lt;/p>
&lt;p>تذكر أنه تم تخزين كافة التعليمات البرمجية في جيثب الخاص بي في مستودع &lt;a href="https://github.com/emimontesdeoca/integration-test-directline-bot-framework">هذا&lt;/a>.&lt;/p></content:encoded><category>.NET</category><category>Bot Framework</category><category>NuGet</category></item><item><title>المحاضرات</title><link>https://emimontesdeoca.github.io/ar/speaking/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/speaking/</guid><description>محاضرات ومؤتمرات إيميليانو مونتيسديوكا — مايكروسوفت MVP ومتحدث دولي.</description><content:encoded>&lt;p>أنا متحدث متكرر في المؤتمرات التقنية الدولية، مع التركيز على &lt;strong>.NET&lt;/strong> و&lt;strong>Azure&lt;/strong> و&lt;strong>الذكاء الاصطناعي&lt;/strong> و&lt;strong>التطوير السحابي الأصيل&lt;/strong>. تم تكريمي كـ &lt;strong>أكثر المتحدثين نشاطاً في Sessionize&lt;/strong> في 2023 و2024 و2025، وأحمل شهادة &lt;strong>مايكروسوفت MVP&lt;/strong> في تقنيات المطورين.&lt;/p>
&lt;p>إذا كنت تريدني أن أتحدث في فعاليتك، &lt;a href="mailto:emimontesdeoca@outlook.es">لنتحدث&lt;/a>!&lt;/p>
&lt;hr>
&lt;h2 id="2026">2026&lt;/h2>
&lt;h3 id="blazor-في-2026">Blazor في 2026&lt;/h3>
&lt;p>نظرة على الحالة الراهنة لـ Blazor في 2026 وكيف تطور جنبًا إلى جنب مع .NET 10. نراجع أحدث التغييرات والتحسينات وتأثيرها على تطوير التطبيقات الحقيقية، وأي نموذج استضافة Blazor يناسب كل سيناريو.&lt;/p>
&lt;h3 id="تعزيز-دعم-المستوى-3-بالذكاء-الاصطناعي-azure-functions--openai-في-العمل">تعزيز دعم المستوى 3 بالذكاء الاصطناعي: Azure Functions + OpenAI في العمل&lt;/h3>
&lt;p>كيف تعمل Azure Functions وتنبيهات Azure Monitor وAzure OpenAI معًا لتسريع دعم المستوى 3. من الفرز التلقائي للتنبيهات إلى التحليل الذكي للسجلات — نظرة عملية على إضافة الذكاء الاصطناعي لسير عمل الدعم الحقيقي.&lt;/p>
&lt;h3 id="من-لوحة-القيادة-إلى-الوكيل-الخطوة-التالية-للمراقبة">من لوحة القيادة إلى الوكيل: الخطوة التالية للمراقبة&lt;/h3>
&lt;p>جلسة تطبيقية تربط بين عالم وكلاء الذكاء الاصطناعي والمراقبة في الإنتاج. من Semantic Kernel إلى الوكلاء المستقلين الذين يستعلمون بيانات OpenTelemetry ويتفاعلون مع Grafana عبر MCP ويساعدون في تشغيل وتحسين الأنظمة الحقيقية.&lt;/p>
&lt;p>&lt;strong>قادم&lt;/strong>: &lt;a href="https://globalazure.es">Global Azure 2026&lt;/a> — أبريل 2026، مدريد، إسبانيا&lt;/p>
&lt;hr>
&lt;h2 id="2025">2025&lt;/h2>
&lt;h3 id="إطار-عمل-الوكلاء-من-مايكروسوفت-لإنقاذ-عيد-الميلاد">إطار عمل الوكلاء من مايكروسوفت لإنقاذ عيد الميلاد&lt;/h3>
&lt;p>بناء وتنسيق وكلاء ذكاء اصطناعي متعددين باستخدام Microsoft Agent Framework للعثور على هدايا عيد الميلاد المثالية. كل وكيل متخصص — من توليد أفكار الهدايا إلى مقارنة الأسعار — يعملون معًا لتسوق موسمي أذكى.&lt;/p>
&lt;h3 id="تعزيز-دعم-المستوى-3-بالذكاء-الاصطناعي-azure-functions--semantic-kernel-في-العمل">تعزيز دعم المستوى 3 بالذكاء الاصطناعي: Azure Functions + Semantic Kernel في العمل&lt;/h3>
&lt;p>Azure Functions وAzure Monitor وSemantic Kernel يعملون معًا لجعل دعم المستوى 3 أسرع وأكثر كفاءة — من الكشف التلقائي عن التنبيهات إلى التحليل الذكي للسجلات.&lt;/p>
&lt;h3 id="ما-الجديد-في-blazor-مع-net-10">ما الجديد في Blazor مع .NET 10؟&lt;/h3>
&lt;p>استكشاف الميزات الجديدة الرئيسية في Blazor لـ .NET 10، بما في ذلك تحسينات الأداء والنماذج والحالة الدائمة والتشغيل البيني لـ JavaScript الأكثر قوة.&lt;/p>
&lt;h3 id="الذكاء-الاصطناعي-الهجين-مع-c-وsemantic-kernel-وgemini-بناء-تطبيقات-مؤسسية-أذكى">الذكاء الاصطناعي الهجين مع C# وSemantic Kernel وGemini: بناء تطبيقات مؤسسية أذكى&lt;/h3>
&lt;p>تنسيق سير عمل الذكاء الاصطناعي الهجين في C# — دمج النماذج المحلية (Phi-3) مع Gemini Pro/Ultra عبر Vertex AI، وبناء وكلاء معرفيين بمكونات إضافية لـ Semantic Kernel، وتصميم معماريات تكيفية.&lt;/p>
&lt;h3 id="net-aspire-بناء-تطبيقات-cloud-native-بلا-صداع">.NET Aspire: بناء تطبيقات Cloud-Native بلا صداع&lt;/h3>
&lt;p>جلسة تطبيقية لبناء خدمات مصغرة cloud-first تولد قابلة للتوسع ومرنة وقابلة للمراقبة. استكشاف كيف يلغي .NET Aspire 70% من النمطية السحابية.&lt;/p>
&lt;h3 id="net-aspire-cloud-native-بلا-فوضى">.NET Aspire: Cloud-Native بلا فوضى&lt;/h3>
&lt;p>أمثلة حقيقية لتصميم تطبيقات ولدت قابلة للتوسع — مع المراقبة والمرونة التلقائية مدمجة من أول commit.&lt;/p>
&lt;hr>
&lt;h2 id="2024">2024&lt;/h2>
&lt;h3 id="الذكاء-الاصطناعي-يلتقي-sql-بناء-محل-بيتزا-ذكي-مع-net-وsemantic-kernel">الذكاء الاصطناعي يلتقي SQL: بناء محل بيتزا ذكي مع .NET وSemantic Kernel&lt;/h3>
&lt;p>تفاعلات الذكاء الاصطناعي باللغة الطبيعية التي تفهم كودك وبياناتك — بناء محل بيتزا ذكي مدعوم بـ .NET وSemantic Kernel.&lt;/p>
&lt;h3 id="net-aspire-تطبيقات-cloud-native-بلا-تعقيدات">.NET Aspire: تطبيقات Cloud-Native بلا تعقيدات&lt;/h3>
&lt;p>بناء خدمات مصغرة cloud-first بلا صداع — جلسة عملية مدفوعة بالعروض التوضيحية تُظهر كيف يبسّط .NET Aspire التطوير السحابي الأصيل.&lt;/p>
&lt;h3 id="power-platform-الرحلة-من-low-code-إلى-تقنيات-pro-code">Power Platform: الرحلة من Low-code إلى تقنيات Pro-code&lt;/h3>
&lt;p>الانتقال من Power Apps المنخفض الكود إلى مكونات pro-code المتقدمة باستخدام TypeScript، كل ذلك داخل نفس المنصة.&lt;/p>
&lt;h3 id="الأتمتة-الذكية-حول-تطبيقاتك-المؤسسية-بذكاء-اصطناعي-متكامل">الأتمتة الذكية: حوّل تطبيقاتك المؤسسية بذكاء اصطناعي متكامل&lt;/h3>
&lt;p>رفع تطبيقات .NET إلى المستوى التالي — الجمع بين المنطق التقليدي والتفكير المبني على الذكاء الاصطناعي باستخدام Semantic Kernel و.NET Aspire.&lt;/p>
&lt;h3 id="من-low-code-إلى-pro-code-مناهج-لتطوير-الذكاء-الاصطناعي-في-الحلول-المؤسسية">من Low-Code إلى Pro-Code: مناهج لتطوير الذكاء الاصطناعي في الحلول المؤسسية&lt;/h3>
&lt;p>استكشاف مناهج مختلفة للحلول المؤسسية — من Power Platform مع الذكاء الاصطناعي إلى التطوير التقليدي مع Semantic Kernel.&lt;/p>
&lt;h3 id="التحكم-في-مصاريف-السفر-مع-azure">التحكم في مصاريف السفر مع Azure&lt;/h3>
&lt;p>استخدام Azure AI Vision وDocument Intelligence وOpenAI مع Semantic Kernel لأتمتة إدارة مصاريف السفر.&lt;/p>
&lt;hr>
&lt;h2 id="الشارات-والتكريم">الشارات والتكريم&lt;/h2>
&lt;ul>
&lt;li>🏆 &lt;strong>Sessionize Most Active Speaker 2025&lt;/strong>&lt;/li>
&lt;li>🏆 &lt;strong>Sessionize Most Active Speaker 2024&lt;/strong>&lt;/li>
&lt;li>🏆 &lt;strong>Sessionize Most Active Speaker 2023&lt;/strong>&lt;/li>
&lt;li>🏅 &lt;strong>مايكروسوفت MVP في تقنيات المطورين&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://sessionize.com/emimontesdeoca/">عرض جميع الجلسات على Sessionize →&lt;/a>&lt;/p></content:encoded></item><item><title>عن المؤلف</title><link>https://emimontesdeoca.github.io/ar/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://emimontesdeoca.github.io/ar/about/</guid><description>عن إيميليانو مونتيسديوكا — مايكروسوفت MVP، قائد فريق حلول السحابة، ومدافع عن المجتمع التقني.</description><content:encoded>&lt;h2 id="عن-المؤلف">عن المؤلف&lt;/h2>
&lt;p>أنا &lt;strong>إيميليانو مونتيسديوكا (Emiliano Montesdeoca)&lt;/strong>، مطور برمجيات من أصول أوروغوانية-إسبانية، &lt;strong>مايكروسوفت MVP في تقنيات المطورين&lt;/strong>، وأب فخور مقيم في &lt;strong>تينيريفي، جزر الكناري&lt;/strong>.&lt;/p>
&lt;p>أحب مواجهة التحديات التقنية المعقدة وبناء حلول سحابية قابلة للتوسع باستخدام تقنيات مايكروسوفت. تتمحور أدواتي اليومية حول &lt;strong>.NET&lt;/strong> و&lt;strong>Azure&lt;/strong> و&lt;strong>الذكاء الاصطناعي مع Semantic Kernel&lt;/strong> والمعماريات الحديثة مثل &lt;strong>.NET Aspire&lt;/strong>.&lt;/p>
&lt;h2 id="ماذا-أفعل">ماذا أفعل&lt;/h2>
&lt;p>بصفتي &lt;strong>قائد فريق حلول السحابة&lt;/strong> في &lt;a href="https://intelequia.com">Intelequia Technologies&lt;/a>، أقود تصميم وتسليم التطبيقات السحابية الأصيلة — مزجاً بين الرؤية الاستراتيجية والتنفيذ العملي. أزدهر عند تقاطع المعمارية والكود، لضمان تقديم فريقنا لحلول أنيقة وجاهزة للإنتاج.&lt;/p>
&lt;h2 id="المجتمع">المجتمع&lt;/h2>
&lt;p>أنا متحدث دولي متكرر، وقد تم الاعتراف بي كـ &lt;strong>أكثر المتحدثين نشاطاً في Sessionize&lt;/strong> في &lt;a href="https://sessionize.com/emimontesdeoca/">2023&lt;/a> و&lt;a href="https://sessionize.com/emimontesdeoca/">2024&lt;/a> و&lt;a href="https://sessionize.com/emimontesdeoca/">2025&lt;/a>. أشارك رؤى عملية من العالم الحقيقي من خلال محاضرات في مؤتمرات حول العالم وعبر مدوناتي.&lt;/p>
&lt;p>أنا أيضاً مؤسس &lt;a href="https://thedotnetblog.com">&lt;strong>The .NET Blog&lt;/strong>&lt;/a> — مورد لمجتمع .NET — وأكتب بانتظام في هذه المدونة عن الأشياء التي أتعلمها وأبنيها.&lt;/p>
&lt;p>الإرشاد وبناء المجتمع جزء أساسي من هويتي. تقديم شيء للنظام البيئي الذي شكّل مسيرتي المهنية أمر أتعامل معه بجدية.&lt;/p>
&lt;h2 id="لنتواصل">لنتواصل&lt;/h2>
&lt;p>أنا دائماً منفتح على التحدث في الفعاليات، والتعاون في المشاريع، أو الحديث عن معمارية السحابة والذكاء الاصطناعي. لا تتردد في التواصل!&lt;/p>
&lt;ul>
&lt;li>&lt;strong>البريد الإلكتروني&lt;/strong>: &lt;a href="mailto:emimontesdeoca@outlook.es">emimontesdeoca@outlook.es&lt;/a>&lt;/li>
&lt;li>&lt;strong>GitHub&lt;/strong>: &lt;a href="https://github.com/emimontesdeoca">@emimontesdeoca&lt;/a>&lt;/li>
&lt;li>&lt;strong>X / Twitter&lt;/strong>: &lt;a href="https://twitter.com/emimontesdeocaa">@emimontesdeocaa&lt;/a>&lt;/li>
&lt;li>&lt;strong>LinkedIn&lt;/strong>: &lt;a href="https://www.linkedin.com/in/emimontesdeoca/">emimontesdeoca&lt;/a>&lt;/li>
&lt;li>&lt;strong>Instagram&lt;/strong>: &lt;a href="https://www.instagram.com/emimontesdeoca/">@emimontesdeoca&lt;/a>&lt;/li>
&lt;li>&lt;strong>Sessionize&lt;/strong>: &lt;a href="https://sessionize.com/emimontesdeoca/">emimontesdeoca&lt;/a>&lt;/li>
&lt;/ul></content:encoded></item></channel></rss>