<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های بابک طارمی</title>
        <link>https://virgool.io/feed/@babaktaremi</link>
        <description>C# enthusiast. NET foundation member</description>
        <language>fa</language>
        <pubDate>2026-06-16 16:53:16</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/464523/avatar/crAviY.jpeg?height=120&amp;width=120</url>
            <title>بابک طارمی</title>
            <link>https://virgool.io/@babaktaremi</link>
        </image>

                    <item>
                <title>آپدیت جدید بلاگ و اتفاق های جذابی که قراره بیوفته!</title>
                <link>https://virgool.io/@babaktaremi/%D8%A2%D9%BE%D8%AF%DB%8C%D8%AA-%D8%AC%D8%AF%DB%8C%D8%AF-%D8%A8%D9%84%D8%A7%DA%AF-%D9%88-%D8%A7%D8%AA%D9%81%D8%A7%D9%82-%D9%87%D8%A7%DB%8C-%D8%AC%D8%B0%D8%A7%D8%A8%DB%8C-%DA%A9%D9%87-%D9%82%D8%B1%D8%A7%D8%B1%D9%87-%D8%A8%DB%8C%D9%88%D9%81%D8%AA%D9%87-denpqmszehi4</link>
                <description>با درود فراوان خدمت همه ی دوستان عزیزم. از حمایت ها و اعتمادتون بی نهایت سپاسگزارم. با من در این پست در مورد اتفاق های جذابی که قراره در آینده بیوفته همراه باشید!خب همونطور که میدونید حدود 9 ماهی هست که مطلب جدیدی در بلاگ ویرگول انتشار داده نشده. ولی خبر خوبی که براتون دارم اینه که یک کانال در زمینه C# و ASP Net Core راه انداختیم که داخل اون تا به الان نزدیک به 10 ساعت ویدیو و مطلب آموزشی قرار داده شده. همچنین برای راحتی دوستان، باتی در تلگرام ساخته شده که دوره های کمیاب سایت های معروف (مثل Udemy یا Pluralsight ) داخل اون با لینک مستقیم و تا حد امکان رایگان قرار داده میشه. همچنین دوره EF Core Advanced Topics داخل کانال در حال برگزاری هست که ویدیو های اون به صورت رایگان در اختیار همه قرار میگیره. پس همراه من باشید که قراره کلی دوره های جذاب و مفید رو پیش رو داشته باشیم. همچنین قراره طبق روال قبلی مطالب مفیدی رو داخل بلاگ منتشر کنیم.آدرس کانال:https://t.me/DotNetFunConf آدرس بات:https://t.me/dotnetfunbot</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Thu, 23 Jun 2022 15:34:32 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت چت روم با Blazor Web Assembly و SignalR قسمت دوم: ساخت کلاینت</title>
                <link>https://virgool.io/dotnetzoom/%D8%B3%D8%A7%D8%AE%D8%AA-%DA%86%D8%AA-%D8%B1%D9%88%D9%85-%D8%A8%D8%A7-blazor-web-assembly-%D9%88-signalr-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-%D8%B3%D8%A7%D8%AE%D8%AA-%DA%A9%D9%84%D8%A7%DB%8C%D9%86%D8%AA-nfwjuzeuqzbf</link>
                <description>در این مقاله قصد داریم به ادامه ساخت چت روم با ASP Net Core Web API و Blazor Web Assembly بپردازیم.ابتدا به سراغ ساخت صفحه لاگین و رجیستر میرویم. در پوشه Pages یک razor component تحت عنوان Login میسازیم. سپس برای داشتن css isolation یک فایل دیگر با نام Login.razor.css ایجاد میکنیم. برای اینکه css isolation به درستی کار کند، قطعه کد زیر را در فایل index.html به قسمت head اضافه میکنیم. https://gist.github.com/babaktaremi/614f0483aee9a5d436907841e78fc173 قسمت ChatApplication.client در واقع نام assembly پروژه میباشد. سپس فایل های استاتیک شامل Font Awsome و Bootstrap را به index.html اضافه میکنیم. در حال حاضر index.html شامل کد های زیر می باشد. https://gist.github.com/babaktaremi/c5fb91a3ddd1764e6d9fcaaa94f5781b پروژه Client را اجرا میکنیم. به صفحه لاگین میرویم و مشاهده میکنیم که استایل ها به درستی لود شده انداستفاده از Local Storage در Blazorبرای استفاده از Access Token و ارتباط با Server نیاز داریم که توکن دریافتی را در جایی ذخیره کنیم. برای اینکار بهترین کار استفاده از قابلیت Local Storage در Browser می باشد. میتوانیم از قابلیت Javascript Interop در Blazor برای کار با Local Storage استفاده کنیم. اما پکیجی با نام Blazored.LocalStorage وجود دارد که کار با Local Storage در Blazor را بسیار راحت میکند. این پکیج را در پروژه Client نصب میکنیم.همچنین در Package Manager Console نیز میتوانیم با دستور زیر این پکیج را نصب کنیمInstall-Package Blazored.LocalStorage -Version 4.1.5سپس قطعه کد زیر را برای استفاده Local Storage به کلاس Program اضافه میکنیم. https://gist.github.com/babaktaremi/3148a94f25533ea35547b60c29536526 جداسازی Code از UIبرای اینکه کد تمیزتری داشته باشیم میتوانیم کد مربوط به صفحه UI را از منطق پروژه جدا کنیم. می دانیم که هرکدام از razor فایل ها پس از کامپایل به یک partial class تبدیل میشوند. با ایجاد فایل با پسوند razor.cs و هم نام با فایل razor اصلی میتوانیم به نوعی یک code behind برای هر صفحه razor داشته باشیم. پس فایل Login.razor.cs را به شکل زیر ایجاد میکنیم. https://gist.github.com/babaktaremi/4450e4fd79e3e0c615ba52fc20e3310a حال میتوانیم منطق پروژه Client و قسمت های مربوط به بخش Login را در این کلاس بنویسیم.ایجاد سرویس مربوط به Loginابتدا در پروژه Client یک پوشه با نام Service ایجاد میکنیم. در پوشه یک پوشه دیگر با نام Login ایجاد میکنیم.اینترفیس ILoginService را به شکل زیر ایجاد میکنیم. https://gist.github.com/babaktaremi/597e36d517deaa0229548c456a97b17a حال کلاس LoginService را که این اینترفیس را پیاده سازی میکند به شکل زیر مینویسیم https://gist.github.com/babaktaremi/5fedaf5a53efd772688628a7a04d5e14 ابتدا یک instance از HttpClient را تزریق میکنیم. از Extension Method ای به نام PostAsJsonAsync که مدل را به صورت JSON در Body به URL داده شده پست میکند استفاده میکنیم. اگر Response داده شده دارای Status Code 200 نبود null بر میگردانیم و در غیر این صورت Content آن را به صورت stream خوانده و به Access Token مدل را Deserialize میکنیم.برای آنکه HttpClient در سرویس ما تزریق شود، تکه کد زیر را در کلاس Program پروژه Client اضافه میکنیم. قبل از آن نیاز داریم که پکیج Microsoft.Extensions.Http را به پروژه  Client آضافه کنیم.همچنین میتوانیم از دستور زیر در Package Manager Console استفاده کنیم.Install-Package Microsoft.Extensions.Http -Version 5.0.0استفاده از Toaster برای اعلام Notification ها به کاربرمانند Local Storage ، برای استفاده از Toaster در Blazor نیز پکیج وجود دارد که نصب و تنظیم آن مانند پکیج قبلی کار بسیار سرراست و ساده ای است.ابتدا پکیج Sotsera.Blazor.Toaster را دانلود و نصب میکنیم.با استفاده از دستور زیر در Package Manager Console نیز میتوانیم این پکیج را نصب کنیم.Install-Package Sotsera.Blazor.Toaster -Version 3.0.0سپس در کلاس Program قطعه کد زیر را برای تنظیم Toaster اضافه میکنیم. https://gist.github.com/babaktaremi/a4f2ce0323b785186ea7d35dff3c7ef5 در اینجا Postion مربوط به Toaster را در قسمت بالا وسط تعیین کرده ایم. سپس به فایل index.html قطعه کد زیر را اضافه میکنیم. https://gist.github.com/babaktaremi/68c3c0586a066d97186978eddcc256b2 سپس به فایل App.razor در قسمت بالا قطعه کد زیر را اضافه میکنیم https://gist.github.com/babaktaremi/b02e38416dc5303833f8d34cbef73536 تکمیل کد های مربوط به بخش Loginحال که همه سرویس های مربوط به بخش Login آماده است به سراغ تکمیل آن می رویم. یادتان باشد که ما بخش Code را از UI جدا کرده ایم پس تمام کد های مربوط به بخش Login را در کلاس Login.razor.cs مینویسیم.ابتدا سرویس های مورد نیاز را به این بخش تزریق میکنیم https://gist.github.com/babaktaremi/b10ab8536eb23e60952fd5498f9a63db در اینجا سرویس های مربوط به Local Storage و Toaster و Login Service را تزریق کرده ایم. صفت Inject در واقع کار تزریق را برای ما در Blazor انجام میدهد. برای Navigate کردن به سایر صفحات نیز Navigation Manager را Inject میکنیمسپس یک پراپرتی از جنس LoginViewModel را new کرده و در کلاس قرار میدهیم. برای اینکه در تگ EditForm در Blazor این مدل قابل استفاده باشد نیاز است که آن را new کنیم. https://gist.github.com/babaktaremi/ed996bd3c75f84816160a60178573f00 حال متد OnInitializedAsync را override میکنیم. این متد زمانی که صفحه رندر و Initialize می شود اجرا میشود. https://gist.github.com/babaktaremi/f656fe021745b3930a54b5ef5668bfb4 در اینجا در Local Storage چک میکنیم که آیا کلیدی با نام userToken وجود دارد یا خیر. اگر وجود داشت به معنی این است که یوزر از قبل لاگین کرده است و آن را به صفحه اصلی میفرستیم.در قسمت UI برای اینکه فرم Login به درستی کار کند تغییرات زیر را انجام میدهیم https://gist.github.com/babaktaremi/30a6f0d1b3313bbd2f6df8830e4328e4 در خط 4 با استفاده از Component ای به نام DataAnnotationsValidator قابلیت Data Annotation Validation را به فرم اضافه میکنیم.در خط 8 و 12 با استفاده از InputText Componenet مقادیر را به پراپرتی های LoginViewModel بایند میکنیم و همچنین با استفاده از ValidationMessage پیامی که هنگام خالی بودن InputText باید نمایش داده شود را نمایش میدهیم. متد HandleValidSubmit را به شکل زیر پیاده سازی میکنیم. https://gist.github.com/babaktaremi/c2f35582b6fae0e50d73409257e91a9c اگر Token برابر null بود با Toaster به کاربر نشان میدهیم که کاربر یافت نشده است و در غیر این صورت مقدار Local Storage را برابر Access Token قرار میدهیم.قبل از هرچیزی کلاس AccessToken را به شکل زیر باید تغییر دهیم https://gist.github.com/babaktaremi/819ea908c598a7b2e74b5c1d084ad32d باید یک Constructor هنگام deserialize کردن مقدار Json در نظر بگیریم.حال فرم Login را چک میکنیم. پروژه را اجرا میکنیم. ابتدا مقادیر username و password را خالی می گذاریم و مشاهده میکنیم که validation ها درست کار میکنند.حال مقدار username و password را با مقادیر اشتباه پر میکنیم و مشاهده میکنیم که Toaster هم درست کار میکند.حال با مقادیر درست username و password را پر میکنیم. مشاهده میشود که در local storage مرورگر مقدار Token ذخیره شده است.به user_id برای جداسازی چت های کاربر و سایر کاربران نیاز داریم. در اینجا برای راحتی کار user_id را داخل توکن قرار داریم ولی این کار باعث بوجود آمدن مشکلات امنیتی میشود. پیشنهاد میکنم که از GUID برای اینکار استفاده کنید و یا راه حل دیگری برای تشخیص یوزر جاری در نظر بگیرید.به همین منوال میتوانیم صفحه مربوط به رجیستر و ثبت نام کاربر را نیز بسازیم.ساخت صفحه چت کاربراندر فولدر Pages یک razor Component به نام Index می سازیم. کد اولیه این صفحه به صورت زیر خواهد بود. https://gist.github.com/babaktaremi/9663b4e21c8bfb6c1c858d92418cacf6 نوشتن کد های مربوط به Chat و SignalR Clientابتدا پکیج مربوط به SignalR Client را نصب میکنیم.از طریق Package Manager Console نیز با دستور زیر میتوانیم SignalR را نصب کنیم.Install-Package Microsoft.AspNetCore.SignalR.Client -Version 5.0.9سپس سرویس مربوط به Chat را مینویسیم. در فولدر Service یک فولدر دیگر با نام Chat ایجاد میکنیم و در آن اینترفیس IChatService را به شکل زیر مینویسیم. https://gist.github.com/babaktaremi/c71258dc8df3bb191ee0084ba54c608d سپس کلاس ChatService را به که این اینترفیس را پیاده سازی میکند به شکل زیر مینویسیم. https://gist.github.com/babaktaremi/5ad6462f9ede4e36128f5d0f6b4b7d9a در خط 14 برای احراز هویت، توکن را به صورت bearer در Header قرار داده ایم.سپس در کلاس Program.cs این سرویس را به شکل زیر رجیستر میکنیم. https://gist.github.com/babaktaremi/5049a917b85f69f7109134e3216368f2 سپس کلاس Index.razor.cs را به شکل زیر میسازیم https://gist.github.com/babaktaremi/ac29a7b8877cb3ff39b71ef0ca60dea8 ابتدا باید از IAsyncDisposable ارث بری کنیم چرا که نیاز داریم در پایان طول عمر Component مقادیر Hub و Debounce Timer را Dispose کنیم.سپس در خط 5 یک فیلد از Hub Connection تعریف میکنیم.سپس برای اینکه یک تاخیر هنگام تایپ کاربر و Notify کردن سایر کاربران ایجاد کنیم یک Debounce Timer تعریف میکنیم که پس از هر 500 میلی ثانیه یک متد را صدا میزند.در خط 13 یک فیلد از جنس string تعریف میکنیم که مقدار پیام وروردی کاربر به آن bind می شود.در خط 14 یک فیلد از جنس int داریم که ID کاربر را در آن ذخیره میکنیم. از این فیلد بعد ها برای تمایز بین پیام های کاربر و سایر کاربران استفاده خواهیم کرد.در خط 15 یک لیست از جنس UserMessageViewModel داریم که تاریخچه چت را به کاربر نشان میدهد.از خط 19 تا 22 سرویس های مورد نیاز را تزریق میکنیم.کار اصلی از override کردن متد OnInitializedAsync شروع میشود. ابتدا بررسی میکنیم که آیا کاربر در Browser و Local Storage مقادیر توکن را دارد یا خیر و اگر نداشت، وی را به صفحه Login هدایت میکنیم. سپس فیلد _userId را با استفاده از متد GetUserIdAsync  که در خط 82 تعریف شده است از Local Storage خوانده و مقدار دهی میکنیم. سپس مقدار _chatHistory را بوسیله سرویسی که قبلا نوشتیم از Web API خوانده و مقدار دهی میکنیم. سپس event مربوطه به هنگامی که debounce timer مقدار 500 میلی ثانیه را سپری میکند را صدا میزند  مقدار دهی میکنیم. متد IsTyping فانکشن مربوطه را در SignalR Server فراخوانی میکند. سپس نوبت به مقداردهی _hub میرسد. در خط 39 مقادیر مربوط به URL را مقداردهی میکنیم. در خط 40 مقدار Access Token را بوسیله متد GetAccessTokenValueAsync از Local Storage خوانده و مقدار دهی میکنیم. سپس متد هایی که Hub روی آنها قرار است invoke بشود را تعریف میکنیم. ابتدا بوسیله Toaster هنگامی که کاربری به SignalR Server متصل میشود را نشان میدهیم. سپس متدی که هنگام دریافت پیام جدید صدا زده می شود را تعریف میکنیم. هنگام دریافت پیام جدید ، آن را به Chat History اضافه میکنیم و برای rerender کردن UI متد StateHasChanged را صدا میزنیم. سپس برای Notify کردن سایر کاربران هنگامی که یک یوزر تایپ میکند ، متد UpdateUserIsTypingAsync را می سازیم .متد InvokeAsync در خط 56 یک اکشن ورودی برای رندر کردن UI دریافت میکند. در آن مقدار typing user را از Server Hub دریافت کرده و برای نمایش آن سمت UI متد StateHasChanged را صدا میزنیم. پس از گذشت 1 ثانیه مقدار typing user را خالی کرده و مجددا StateHasChanged را صدا میزنیم . در نهایت در خط 48 Hub را استارت میکنیم.در خط 89 متدی که هنگام زدن دکمه ارسال باید فراخوانی بشود را تعریف میکنیم. اگر مقدار message خالی نبود آن را به متد OnNewMessage در SignalR Server میفرستیم. سپس مقدار message را خالی میکنیم و متد StateHasChanged را صدا میزنیم.در خط 101 یک متد برای on input event  تعریف میکنیم که وقتی کاربر در حال تایپ در Text Area است صدا زده میشود. در آن یک بار Debounce Timer را متوقف میکنیم و مجددا استارت میزنیم.در نهایت در خط 114 مقدار Hub و Timer را Dispose میکنیم.نوشتن کد های مربوط به UI صفحه چتفایل Index.razor را به شکل زیر بازنویسی میکنیم. https://gist.github.com/babaktaremi/3b874bf665d0c28db72d5276dabbb2b5 ابتدا در خط 6 چک میکنیم که اگر مقدار typing user خالی نبود ، آن را در قسمت بالای صفحه نمایش دهد.سپس بین چت های موجود در Chat History پیمایش میکنیم و مسیج های کاربر و سایر کاربرها را نمایش میدهیم.در خط 46 مقدار text area را به message بایند میکنیم و event مربوط به  را به متد InitiateUserIsTyping که قبلا تعریف کرده بودیم Assign میکنیم.در خط 47 نیز event مربوط به  که برای Button تعریف شده است را به متد SendMessage که قبلا تعریف کرده ایم Assign میکنیم.تست نهاییابتدا دو مرورگر باز میکنیم و در هر دو آنها Login میکنیم. سپس مشاهده میکنیم که نشان دادن کاربر آنلاین و همچنین ارسال پیام و نشان دادن کاربر در حال تایپ به درستی کار میکند.جمع بندیدر این پروژه یک سرور از صفر با استفاده از Identity و Signal R و همچنین JWT Authentication ساختیم. همچنین به طور اجمالی با Blazor web assembly و نحوه استفاده از JWT Token و ارتباط با Signal R آشنا شدیم. شدیدا توصیه میکنم که کد های مربوط به این مقاله را از گیت هاب دریافت کرده و آن را روی سیستم خود اجرا و دیباگ کنید. در نهایت اگر سوال یا نظری داشتید، خوشحال میشوم که آن را در بخش نظرات این مقاله مطرح کنید. https://github.com/babaktaremi/Blazor-Chat-Application مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Sat, 04 Sep 2021 15:25:21 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت چت روم با Blazor Web Assembly و SignalR قسمت اول: ساخت سرور</title>
                <link>https://virgool.io/dotnetzoom/%D8%B3%D8%A7%D8%AE%D8%AA-%DA%86%D8%AA-%D8%B1%D9%88%D9%85-%D8%A8%D8%A7-blazor-web-assembly-%D9%88-signalr-%D9%82%D8%B3%D9%85%D8%AA-%D8%A7%D9%88%D9%84-%D8%B3%D8%A7%D8%AE%D8%AA-%D8%B3%D8%B1%D9%88%D8%B1-anfpjcdmdbn9</link>
                <description>در این مقاله دو قسمتی قصد داریم که بوسیله SignalR در سرور ASP Net Core و Blazor Web Assembly یک چت روم بسازیم.معماری کلی پروژهبرای ساده نگه داشتن پروژه، یک پروژه سرور که ASP Net Core Web Api هست داریم. برای کلاینت یک پروژه Blazor Web Assembly داریم و برای مشترکات بین دو پروژه که شامل مدل ها  هست یک پروژه از نوع Class Libraryداریم. معماری کلی پروژه را میتوانید در شکل زیر مشاهده کنید. دقت داشته باشید که دو پروژه Server و Client به پروژه Shared رفرنس دارند.ساخت و تکمیل سرورافزودن Identity به سرورابتدا پکیج Microsoft.Extensions.Identity.Stores را از طریق Nuget دانلود و نصب میکنیماز طریق Package Manager نیز میتوانیم با دستور زیر اقدام به نصب این پکیج بکنیم.Install-Package Microsoft.Extensions.Identity.Stores -Version 5.0.9سپس به سراغ نصب پکیج Microsoft.AspNetCore.Identity.EntityFrameworkCore می رویم از طریق Package Manager نیز میتوانیم با دستور زیر اقدام به نصب این پکیج بکنیم.Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore -Version 5.0.9ساخت مدل های Identityپس از نصب Identity به سراغ ساخت مدل های آن می رویم. ابتدا در پروژه سرور یک فولدر با نام IdentityModels ایجاد میکنیمبرای ساخت مدل User به شکل زیر عمل میکنیم https://gist.github.com/babaktaremi/d26815fdf353aa639b6c1e646d3757a5 سپس سایر مدل های Identity را به شکل زیر میسازیم https://gist.github.com/babaktaremi/0824da8ba22e90abc7929c853e8c987e  https://gist.github.com/babaktaremi/47feb0145ce818c7815d5d5df8ff1102  https://gist.github.com/babaktaremi/73b60523331c32202bacdece1c8dc5c1  https://gist.github.com/babaktaremi/eb10695641d9c99fd07c18548aa9b35b  https://gist.github.com/babaktaremi/9ee3a6d30eee6a9afa83f6228d40f780  https://gist.github.com/babaktaremi/d91fdecd626f11fc8d07f7bc2999a791 ساخت Database Contextپس از ساخت مدل های Identity به سراغ ساخت ApplicationDbContext می رویم و آن را به شکل زیر می سازیم.ابتدا یک فولدر با نام Context می سازیم و ApplicationDbContext را داخل آن قرار میدهیم. https://gist.github.com/babaktaremi/305485da35f7387d464af034b8f68a3e برای استفاده از EF Core در پروژه و ایجاد Migration ها نیازمند پکیج های زیر هستیم که از طریق Package Manager و با دستورات زیر آنها را نصب میکنیمInstall-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.9Install-Package Microsoft.EntityFrameworkCore.Design -Version 5.0.9Install-Package Microsoft.EntityFrameworkCore.Tools -Version 5.0.9سپس در متد ConfigureServices کلاس Startup این Database Context را رجیستر میکنیم. https://gist.github.com/babaktaremi/b2020dcedb9dd9b6622270721d264fd2 سپس با ایجاد Migration جداول مربوطه را میسازیم.تنظیم Identityبرای تنظیم Identity به شکل زیر عمل میکنیم.ابتدا یک فولدر با نام Extensions می سازیم و سپس یک کلاس داخل آن با نام IdentityConfigurationExtension ایجاد میکنیم. https://gist.github.com/babaktaremi/e557e618af94920a32a2cf497c78b79e در اینجا تنها یک پیاده سازی برای User Claim ها داریم که در آن میتوانیم Custom Claim ها را به توکن کاربر اضافه کنیم. پیاده سازی آن به شکل زیر می باشد https://gist.github.com/babaktaremi/0972dc5eabbaf975584678f9e9e280cb سپس در متد ConfigureServices کلاس Startup از این کلاس به شکل زیر استفاده میکنیم. https://gist.github.com/babaktaremi/0f2d47564bb428d71e9080c1f7fac465 افزودن JWT به Identityبرای افزودن JWT به Identity نیازمند پکیج Microsoft.AspNetCore.Authentication.JwtBearer هستیم این پکیج را از طریق دستور زیر نیز میتوانیم نصب کنیم.Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 5.0.9برای تنظیم JWT در فولدر Extensions یک کلاس با نام JWTConfigurationExtension به شکل زیر ایجاد میکنیم. https://gist.github.com/babaktaremi/d1daabdeaae4c3a579bc2f6f7e979eb6 سپس در متد ConfigureServices کلاس Startup تغییرات زیر را اعمال میکنیم. https://gist.github.com/babaktaremi/40b72abfc4814b3c589b0ae3f3a27438 در متد Configure کلاس Startup نیز تغییرات زیر را اعمال میکنیم. https://gist.github.com/babaktaremi/dabef9c1c2432d34484e1837a47b36eb ایجاد سرویس های مربوط به ساخت JWT Tokenبرای ایجاد JWT Token ابتدا در پروژه Shared کلاس AccessToken را به شکل زیر میسازیم. ابتدا در پروژه Shared یک پوشه با نام JWT ایجاد میکنیم و در آن کلاس AccessToken را قرار میدهیم. برای استفاده از کلاس JwtSecurityToken نیازمند پکیج System.IdentityModel.Tokens.Jwt که آن را با دستور زیر از طریق Package Manager Console نصب میکنیم.Install-Package System.IdentityModel.Tokens.Jwt -Version 6.12.2 https://gist.github.com/babaktaremi/349a70882d9404fd8ee7d0386aeeeff2 این کلاس شامل اطلاعاتی است که برای احراز هویت کاربران مورد نیاز است. ابتدا تایپ توکن را بعنوان Bearer تعریف میکنیم و سپس مقدار زمانی که توکن معتبر است را بعنوان ثانیه نشان میدهیم. در پروژه Server یک فولدر با نام Service ایجاد میکنیم و در آن یک فولدر دیگر با نام Jwt ایجاد میکنیم.سپس در آن یک اینترفیس با نام IJwtService ایجاد میکنیم که به صورت زیر میباشد. https://gist.github.com/babaktaremi/eb19489a03f57b3dd649c04f7d362577 سپس کلاس JwtService را به شکل زیر که اینترفیس IJwtService را پیاده سازی میکند مینویسیم. https://gist.github.com/babaktaremi/6a473e9e0b96e31008787c6268acca57 در این کد ما JWT Token را به صورت Encrypt شده در میاوریم. برای مطالعه بیشتر درباره نحوه Encrypt کردن JWT Token میتوانید به این مقاله از محمد جواد ابراهیمی عزیز مراجعه کنید.سپس در متد ConfigureServices این سرویس را به شکل زیر رجیستر میکنیم. https://gist.github.com/babaktaremi/7c5a1d7db7e9223b9fbdb52df366a5db ایجاد اکشن های Login و Register ابتدا مدل های Login و Register را میسازیم. در پروژه Shared یک پوشه با نام Account ایجاد میکنیم و دو کلاس LoginViewModel و RegisterViewModel را به شکل زیر میسازیم. https://gist.github.com/babaktaremi/0d92076e8de6d0687d67f9203ac0e78e  https://gist.github.com/babaktaremi/c4210bee5b68f711a334e127c7688b34 سپس به سراغ ساخت AccountController می رویم. ابتدا این کنترلر را به شکل زیر می سازیم و سپس اکشن های Login و Register را به آن اضافه میکنیم. https://gist.github.com/babaktaremi/54451345e7c880b40630c2af3890e5b3 برای ساخت اکشن Register به شکل زیر عمل میکنیم https://gist.github.com/babaktaremi/963b1de8a3e1361d132169092094faa0 برای ساخت اکشن Login نیز به شکل زیر عمل میکنیم. https://gist.github.com/babaktaremi/0e770471df62788851f21fc7430bf498 سپس این دو اکشن را در پنل Swagger تست میکنیم.ساخت جدول Chat  Historyابتدا در پروژه Server یک فولدر با نام Models ایجاد میکنیم. و در آن یک کلاس با نام ChatHistory ایجاد میکنیم. این جدول رابطه یک به چند با جدول User دارد. https://gist.github.com/babaktaremi/75e117772d096d7bf918172ca24cac82 سپس تغییر زیر را در کلاس User ایجاد میکنیم. https://gist.github.com/babaktaremi/d81b8f19d063b695b6cb88b5e3416c86 سپس تغییر زیر در کلاس ApplicationDbContext ایجاد میکنیم. https://gist.github.com/babaktaremi/7a409bdaf33a8ca827a80ad10e42d7d0 سپس Migration مربوط به ایجاد جدول ChatHistory را ایجاد میکنیم و در نهایت دیتابیس را آپدیت میکنیم.ساخت Chat Hub و Chat Controllerابتدا در پروژه Shared یک فولدر با نام Chat ایجاد میکنیم و کلاس های مربوط به Chat Hub را در آن ایجاد میکنیم.در این فولدر 3 کلاس با نام های UserMessageViewModel و UserIsTypingViewModel و UserJoinedChatViewModel و UserNewMessageViewModel قرار دارند.از UserMessageViewModel برای نشان دادن پیام ها و همچنین Notify کردن سایر کاربران هنگام ایجاد پیام جدید استفاده میکنیم.از UserIsTypingViewModel برای Notify کردن سایر کاربران هنگامی که یک کاربر در حال تایپ است استفاده میکنیماز UserJoinedChatViewModel برای Notify کردن سایر کاربران هنگامی که یک کاربر به چت روم وصل میشود استفاده میکنیم.از UserNewMessageViewModel هنگامی که کاربر یک پیام جدید به سرور میفرستد استفاده میکنیم. https://gist.github.com/babaktaremi/cf0ed0347ce9afcd73c4a93fff2cca08  https://gist.github.com/babaktaremi/0b06acd0092b5d697d9507a14282b09f  https://gist.github.com/babaktaremi/c8c0bb4e0ac072b97c3f3929c5ae86b4  https://gist.github.com/babaktaremi/cdda68f7f469a0deeae997560aff304b سپس در پروژه Server یک پوشه با نام Hub ایجاد میکنیم. در آن یک اینترفیس با نام IChatHub به شکل زیر اضافه میکنیم. https://gist.github.com/babaktaremi/d00ad7ac5001e2e827202851711fa338 سپس ChatHub را به شکل زیر ایجاد میکنیم. https://gist.github.com/babaktaremi/be32491c841dd4259bd1e59c9a6a0fb3 متد OnConnectedAsync را Override کرده ایم و در خط 8 به محض اینکه یک کاربر به هاب متصل میشود نام کاربری آن را به سایر کلاینت ها نشان میدهیم.سپس به Hub یک متد با نام OnNewMessage اضافه میکنیم که پیام های جدید را گرفته، به دیتابیس اضافه کرده و سپس سایر کلاینت ها را Notify کند. https://gist.github.com/babaktaremi/b4c2c7f411a88596fe6866a92f59599a سپس برای اینکه به سایر کاربران نشان دهیم که کاربر در حال تایپ است متد زیر به هاب اضافه میکنیم. https://gist.github.com/babaktaremi/f7f4b49ef4da631e79eed6e6897b5d4e در متد ConfigureServices کلاس Startup سیگنال آر را به شکل زیر به پروژه اضافه میکنیم. https://gist.github.com/babaktaremi/9f0a97ebb7cc1c420adf67456aace61b سپس ChatHub را به شکل زیر به URL مپ میکنیم. https://gist.github.com/babaktaremi/ccaff63cf6a8d476b19c2b4614b27648 در کلاس JwtConfigurationExtension برای اینکه ChatHub بتواند با JWTکار کند باید تغییرات زیر را اعمال کنیم. چون که در Web Socket نمیتوان مقدار AccessToken را از Header خواند، باید آن را تحت یک QueryString به سرور پاس دهیم. پس کلاس JwtConfigurationExtension به شکل زیر در میاید. https://gist.github.com/babaktaremi/6140381ba56ba3b6b46557d186450bc3  حال به سراغ ساخت ChatController میرویم. شکل ابتدایی ChatController به صورت زیر است. https://gist.github.com/babaktaremi/367f822f404ddefad89d302f65c4d901 در این کنترلر ما نیاز به یک اکشن داریم که پیام هایی که تا به حال منتشر شده است را دریافت کند https://gist.github.com/babaktaremi/01622cc87290d2cb479176484b7466a5 تنظیمات CORS برای اینکه Web Api ساخته شده سمت Client در دسترس باشد، نیاز داریم که CORS را در آن فعال کنیم. برای فعال سازی CORS به شکل زیر عمل میکنیم.ابتدا در متد ConfigureServices کلاس Startup قطعه کد زیر را اضافه میکنیم https://gist.github.com/babaktaremi/e4e0433b1dc8a7ffcbd010b0761665f9 برای اینکه بتوانیم از JWT Token در SignalR استفاده کنیم باید از متد AllowCredentials استفاده کنیم. سپس در متد Configure کلاس Startup قطعه کد زیر را اضافه میکنیم.جمع بندیدر این قسمت از مقاله یک سرور برای اپلیکیشن چت روم با استفاده از Identity ، JWT Authentication و SignalR ساختیم. در قسمت بعد به سراغ ساخت بخش کلاینت با استفاده از Blazor Web Assembly و Signal R Client می رویم و در نهایت یک اپلیکیشن چت روم به صورت Real-Time خواهیم داشتمقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Wed, 01 Sep 2021 12:22:39 +0430</pubDate>
            </item>
                    <item>
                <title>آموزش Unit Testing با استفاده از NUnit و Moq بخش دوم: Mocking</title>
                <link>https://virgool.io/dotnetzoom/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-unit-testing-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-nunit-%D9%88-moq-%D8%A8%D8%AE%D8%B4-%D8%AF%D9%88%D9%85-mocking-gwtqet1h9omn</link>
                <description>در این مقاله قصد داریم که به Mocking و کتابخانه محبوب Moq بپردازیم. https://vrgl.ir/rZMij در این مقاله از GitHub Gist استفاده شده است و لود شدن قسمت مربوط به کد ها ممکن است کمی زمانبر باشدتعریف Mock معمولا Unit Test ها روی یک Unit کار میکنند که به یک کلاس اطلاق می شود. گاهی اوقات این Unit یک سری Dependency هایی را همراه خود دارند که جزئی از تست نیستند ولی Unit مربوطه بدون این dependency ها نمی تواند کار کند.سناریویی را در نظر بگیرید که ClassA سیستم تحت تست می باشد ولی این کلاس در داخل خود از ClassB استفاده می کند. حال ما نیاز داریم که ClassB را در تست خود جایگزین کنیم و تنها ClassA را تست کنیم. در اینجا باید از Test Double ها استفاده کنیم. یکی از Test Double ها Mock می باشد. Mock در واقع یک Fake Object می باشد که میتواند مقدار تعریف شده را به عنوان Result خود برگرداند.همچنین یک Mock می تواند شامل اطلاعات اضافی مانند اینکه چه پارامتر هایی مورد استفاده قرار گرفته و چند بار یک Mock Object صدا زده شده را برگرداند.کتابخانه Moqیک کتابخانه بسیار کاربردی در زمینه Mocking می باشد که قابلیت های بسیاری از جمله شبیه سازی متد ها،Event ها، پراپرتی ها و... را در اختیارمان قرار میدهد.نصب Moqبه سراغ پروژه ای که در قسمت قبلی ایجاد کرده ایم می رویم. در Package Manager Console دستور زیر را برای نصب Moq اجرا میکنیم.Install-Package Moq -Version 4.16.1همچنین می توانیم از قسمت Manage Nuget Packages به دنبال Moq بگردیم و آن را نصب کنیم.شروع کار با Moqاینترفیس ILog زیر را در بگیرید. https://gist.github.com/babaktaremi/78fc50fa890eab51ca3a4030340de3c6 این اینترفیس یک متد ساده با نام WriteMessage دارد. حال کلاس BankAccount را در نظر بگیرید که به این اینترفیس وابستگی دارد. https://gist.github.com/babaktaremi/d48146695c884daa55521dfffbee4509 هدف ما تست یونیت BankAccount می باشد نه اینترفیس ILog. در اینجا کتابخانه Moq به کمک ما می آید. https://gist.github.com/babaktaremi/28cbe1a8d9040046f2548fc1a0a397c8 در اینجا در متد SetUp یک Mock Object از اینترفیس ILog ساخته ایم و آن را به BankAccount پاس داده ایم. بوسیله آن نیازمندی یونیت BankAccount به ILog تامین می شود و می توانیم یونیت BankAccount را بدون نیاز به ساخت اینترفیس ILog تست کنیم. در ادامه با سایر قابلیت های Moq آشنا می شویم.ایجاد Mocking Methodsاینترفیس IFoo زیر را در بگیرید. در ادامه مقاله قصد داریم که بدون نیاز به پیاده سازی این اینترفیس، آن را Mock کنیم. https://gist.github.com/babaktaremi/8ad3ff7140a15b0503703562bd6dbd71 ابتدا یک mock از اینترفیس IFoo می سازیم. https://gist.github.com/babaktaremi/b8a117e7070a130caa07e43f663f8154 کلاس mock یک متد با نام Setup در اختیارمان قرار میدهد که به وسیله آن میتوانیم رفتار mock را تغییر دهیم. رفتار متد DoSomething را به شکل زیر تعریف میکنیم https://gist.github.com/babaktaremi/7f90c024856b39df2441cd5b5e0d0497 خط 1 نشان میدهد که اگر متد هرکدام از مقدار Ping یا Foo را دریافت کرد False را به عنوان نتیجه بازگرداند و در خط 2 مقدار Pong توسط متد دریافت شد، مقدار True را بازگرداند. حال اگر متد  DoSomething مقداری غیر از Ping، Pong و یا Foo را دریافت کند، نتیجه آن مقدار Default خروجی متد می باشد که در این حالت مقدار Default برای boolean مقدار False می باشد.بررسی Mocking برای متدهای وابسته به مقدار ورودیمتد Add از اینترفیس IFoo را در نظر بگیرید. فرض کنید که میخواهیم وقتی که عدد زوج است، مقدار True را برگردانیم و یا وقتی که ورودی در رنج اعداد 1 تا 10 بود مقدار False را برگردانیم. ویا برای متد DoSomething این شرط را داشته باشیم که ورودی String باید شامل حروف باشد.کلاس It از Moq شامل چند متد است که بوسیله آن  میتوانیم برای خروجی متد ها شرط بگذاریم. https://gist.github.com/babaktaremi/f1f1580d4a17a8694556f1bbb1d092e4 بررسی Mocking برای پارامترهای Ref و Outبرای Mock کردن پارامتر out در Moq می توانیم به شکل زیر عمل کنیم https://gist.github.com/babaktaremi/3f82e1adad045defcc16200d14fb2d49 دقت کنید که در متد بالا تعریف کرده ایم که اگر ورودی TryParse مقدار Ping بود،خروجی متد مقدار True داشته باشد و out Argument را به مقدار ok تطبیق دهد.برای Ref میتوانیم مانند زیر عمل کنیم.دقت داشته باشید که در مورد Ref کتابخانه Moq به صورت Reference Equality عمل میکند و Reference دو آبجکت را بررسی میکند. https://gist.github.com/babaktaremi/0b3acd4a18cdcfad45765616ecb8df4d بررسی مقدار بازگشتی برای Mocking متدهاهنگامی که مقدار بازگشتی متد باید دارای مقدار خاصی باشد میتوانیم از یک Lambda Expression برای خروجی مقدار Returns استفاده کنیم. به طور مثال مقدار ProcessString در Mock زیر همواره کاراکتر ها را lower کرده و برمیگرداند. https://gist.github.com/babaktaremi/1c596e7998d3cf17526575a4ad4155e0 بررسی CallBack در Moqموقع Setup کردن یک متد میتوانیم تعریف کنیم که به ازای هر Call مقدار های مختلف داشته باشیم. مثلا میتوانیم تعریف کنیم که هربار متد Call شد یک Counter مقدارش زیاد شود که میتوانیم از CallBack به شکل زیر استفاده کنیم. در واقع CallBack  یک فانکشن است که هربار که متد فراخوانی میشود،مقدار تعریف شده در  CallBack اجرا می شود. https://gist.github.com/babaktaremi/b3944e368cc592a7dfd5cb9655662d13 بررسی Exception ها در Moqبرای اینکه یک Exception را در Moq شبیه سازی کنیم میتوانیم به صورت زیر عمل کنیم. https://gist.github.com/babaktaremi/b1a2af54ce727cbbd232378034284798 بررسی Property Mockingدر mock Object نمیتوان که مقداری را به یک Property تخصیص داد چرا که در اصل هیج Object ای وجود ندارد. میتوانیم به صورت زیر یک مقدار را به یک پراپرتی Assign کنیم. https://gist.github.com/babaktaremi/bb50ba21886bdc31a4613ab472d05af7 در مورد Setter های یک پراپرتی می توانیم مانند زیر عمل کنیم و هربار که Setter یک پراپرتی فراخوانی شد، عملیاتی را انجام دهیم. https://gist.github.com/babaktaremi/4cfe85e79d84e750a47fd071e72db145 بررسی Value Tracking پراپرتی هامیتوانیم به شکل زیر، تمامی پراپرتی های موجود در mock را طوری تنظیم کنیم که رفتاری مانند یک پراپرتی داشته باشند. https://gist.github.com/babaktaremi/80c636a800f2f88f8b5c33d33ef873b0 در خط 5 تمامی پراپرتی های ابجکت mock را تنظیم میکنیم که رفتاری مانند پراپرتی داشته باشند.بررسی Event Mockingفرض کنید که اینترفیس زیر را به عنوان نمونه داریم که دارای یک Event و یک Delegate می باشد. https://gist.github.com/babaktaremi/631674a96e43b84a752ec769b0191f26  https://gist.github.com/babaktaremi/c584293b5b681ba58b9e687f3728d6a0 حال یک کلاس با نام Doctor به شکل زیر ایجاد میکنیم که وابستگی به اینترفیس IAnimal دارد و به Event ها Subscribe کرده است. https://gist.github.com/babaktaremi/003860bdfed6be1a6783f4562ae997bf برای Raise کردن یک Event می توانیم مانند زیر عمل کنیم. https://gist.github.com/babaktaremi/deafd22ffda5388fbdcf2b637768c7db میتوانیم در SetUp یک متد، Raise کردن یک Event را در نظر بگیریم. https://gist.github.com/babaktaremi/36591474d085d8ef25a787734ded5918 میتوانیم یک Custom Event داشته باشیم. به طور مثال در اینترفیس IAnimal یک delegate داریم که میتوانیم از آن به شکل زیر در mock object استفاده کنیم. https://gist.github.com/babaktaremi/a2593a33ea878ff133eb6cc97b918c00 بررسی دسترسی به Protected Members در Mockفرض کنید که کلاس Person را به شکل زیر داریم که دارای اعضای Protected می باشد. https://gist.github.com/babaktaremi/8f6adf19e6344accd7c4712c942cc437 میتوانیم به شکل زیر به اعضای Protected دسترسی داشته باشیم و همچنین متدهای Protected را Invoke کنیم. https://gist.github.com/babaktaremi/f096765cd7a2681ac7bacb5e0599ea5e دقت داشته باشید که که برای Invoke کردن متد به جای استفاده از دستور It باید از دستور ItExpr استفاده کنیم.نتیجه گیریدر این مقاله با Mocking و کتابخانه Moq آشنا شدیم. به طور کلی کتابخانه Moq ابزاری فوق العاده قدرتمند برای شبیه سازی بخش هایی از یونیت که مورد نظر تست نیستند می باشد. اگر به کد های این بخش نیاز داشتید میتوانید آن را از گیت هاب به لینک زیر دریافت کنید. خوشحال میشوم که نظرات شما را راجب این مقاله بدانم و همچنین اگر دوست داشتید میتوانید من را به یک قهوه مهمان کنید. https://coffeebede.ir/buycoffee/bobby  https://github.com/babaktaremi/NUnit-Drafts مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Mon, 14 Jun 2021 13:39:59 +0430</pubDate>
            </item>
                    <item>
                <title>آموزش Unit Testing با استفاده از NUnit و Moq بخش اول: آشنایی با NUnit</title>
                <link>https://virgool.io/dotnetzoom/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-unit-testing-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-nunit-%D9%88-moq-%D8%A8%D8%AE%D8%B4-%D8%A7%D9%88%D9%84-%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-nunit-qfl0xvoueyfq</link>
                <description>در این سری آموزش قصد داریم به فریم ورک محبوب NUnit و همچنین Mocking بوسیله کتابخانه محبوب Moq بپردازیم.در این مقاله از GitHub Gist استفاده شده است و لود شدن بخش مربوط به کد ها ممکن است کمی زمانبر باشد. همچنین ممکن است نیازمند نرم افزار رفع تحریم باشید.تعریف Unit Testبه عمل نوشتن کدی که بررسی درستی نتیجه سیستم یا بخشی از آن را برعهده دارد Test گفته میشود. یک Unit به کوچک ترین بخش کد که تست پذیر باشد گفته می شود.معمولا Unit به یک کلاس اطلاق میشود که متد ها و رفتار های آن قابل تست باشد. تست واحد یا Unit Test بررسی میکند که یک عملیات نتیجه پیش بینی شده را بر میگرداند یا خیر.مفهوم Red-Green-Refactorاین مفهوم در Test Driven Development برای اولین بار بیان شد. در تکنیک Red Green Refactor رویه زیر را پیشنهاد میکندتست بنویسیدتست را اجرا کنید و ببینید که تست نتیجه اشتباه را بر میگرداند و fail میشودکد اصلی را بنویسیدتست را دوباره اجرا کنیددر صورتی که تست pass شد(نتیجه آن سبز شد) کد را refactor کنیدآشنایی با NUnitیک فریم ورک open source مخصوص Unit Testing در دات نت می باشد که کار Unit Testing را بسیار آسان می کند. به وسیله NUnit میتوانیم یک پروژه مخصوص Unit Testing داشته باشیم و تست ها را به راحتی مانیتور کنیم. در تاریخ نوشتن این مقاله طبق آمار NugetTrends.com پکیج مربوط به NUnit بیش 117 میلیون بار دانلود و نصب شده است.آمار دانلود پکیج NUnitایجاد پروژه نمونهپس از کلیک بر روی Create New Project به سراغ ساخت یک Class Library می رویم و سپس یک نام برای آن انتخاب میکنیم.سپس به سراغ نصب پکیج NUnit می رویم. میتوانیم از قسمت Package Manager Console و با دستور زیر پکیج NUnit را نصب کنیم.Install-Package NUnit -Version 3.13.2 و یا میتوانیم بر از طریق Manage Nuget Package به سراغ پکیج NUnit رفته و آن را نصب کنیمپس از نصب NUnit آیکون مربوط به Class Library تغییر میکند که نشان میدهد که این پروژه به تست تغییر کاربری داده است.ایجاد اولین تستابتدا یک کلاس ایجاد میکنیم و آن را با صفت TextFixture علامت گذاری میکنیم. سپس یک متد ایجاد کرده و آن را با صفت Test علامت گذاری میکنیم. در اینجا System Under Test و یا به صورت اختصار SUT را یک عملیات ساده در نظر میگیریم https://gist.github.com/babaktaremi/d28c019d04af7ea0fdebed0a508cabf9 از قسمت search  به دنبال Test Explorer می گردیم.محیط Test Explorer به شکل زیر باز میشود.بر روی گزینه Run کلیک میکنیم و مشاهده میکنیم که تست ها Pass می شوند.آشنایی با ReSharperیک ابزار بسیار قدرتمند که به نوشتن کد بهتر کمک بسیاری میکند و برای هر کد Suggestion های بسیار خوبی را ارائه میدهد. ReSharper در زمینه Unit Testing نیز Test Session بسیار قدرتمندی را ارائه میدهد که  تست کد را بسیار آسان میکند. برای دانلود ReSharper و نصب آن میتوانید به این لینک مراجعه کنید.پس از نصب ReSharper یک گزینه کنار هر تست به شکل زیر نمایان می شود که بوسیله آن میتوان تست را به راحتی Run کرد.آشنایی با Assertion در NUnitیک  Unit Test معمولا دو نتیجه را در بر دارد . یا تست Fail و Red میشود و یا تست Pass و Green میشود. به وسیله Assertion می توان نتیجه یک تست را طبق شرایط بدست آورد. در مثال قبل به وسیله Assertion نتیجه Unit Test را بدست آوردیم که 2+2=4 و تست pass شد.آشنایی با Assertion های سادهنتیجه Inconclusive: وقتی یک تست Inconclusive باشد نتیجه آن مشخص نیست و در نتیجه نمیتوان مشخص کرد که تست Pass شده است و یا Fail شده است. بوسیله دستور زیر میتوان نتیجه تست را Inconclusive کرد. https://gist.github.com/babaktaremi/cc7c293573fc02aed64c6aed6ff60a7b مشاهده میکنیم که نتیجه تست با رنگ زرد Inconclusive نشان داده شده است.ایجاد warning: وقتی یک warning ایجاد میکنیم نتیجه تست به صورت خودکار Inconclusive میشود. به صورت زیر میتوانیم در یک تست ایجاد warning کنیم و یک message را نمایش دهیم. https://gist.github.com/babaktaremi/6f358398e272b35574f8ba5bf1c0e642 ایجاد Equality Check: وقتی میخواهیم نتیجه دو مقدار را از نظر برابری چک کنیم میتوانیم از دستورات زیر استفاده کنیم. https://gist.github.com/babaktaremi/d884796a5fccba05c0bc3c9fb1808735  https://gist.github.com/babaktaremi/78ce0e9ca17fb287954e1b8f14aaa83a نکته قابل توجه این است که دستور Assert.That و Is میتواند دستورات مختلفی را چک کند مانند:دستور Is.False: این دستور چک میکند که نتیجه یک Assert برابر False باشد.دستور Is.Ordered : بررسی میکند که یک Collection به صورت مرتب شده باشد.دستور Is.Unique : بررسی میکند که تمام المان های یک Collection یکتا و Unique باشد.دستور Is.AssignableFrom: بررسی میکند که یک type از نوع type تعریف شده باشدمابقی قسمت های مربوط به Assert.That را میتوانید در تیکه کد زیر ببینید. https://gist.github.com/babaktaremi/df61f960eeb6d8629c8685e708da41e5 بررسی String Assertionعلاوه بر Assert معمولی، میتوانیم بوسیله String Assertion بررسی های مخصوص به String را انجام دهیم. کارهایی که میتوانیم با string Assertion انجام دهیم که پرکاربرد ترین آنها عبارتند از:دستور AreEqualIgnoringCase:  بررسی میکند که دو string باهم برابرند بدون در نظر گرفتن بزرگ و کوچک بودن کاراکتر های آنها.دستور Contains: بررسی میکند که یک string شامل یک sub string خاص هست یا خیر.دستور IsMatch : بررسی میکند که آیا یک string با یک regular expression برابری میکند یا خیر.لیست کامل دستورهای string assertion را میتوانید در قطعه کد زیر ببینید. https://gist.github.com/babaktaremi/3a6e70775f248b49d446dbcbaa2c8c4b بررسی Collection Assertionعلاوه بر string Assertion یک سری Assertion خاص برای Collection ها وجود دارد که میتوانیم بوسیله آن بررسی های مخصوص به Collection ها را انجام دهیم که پرکاربرد ترین آنها عبارتند از:دستور AreEqual :  بررسی میکند که آیا دو کالکشن دارای المان هایی با تعداد یکسان و ترتیب یکسان هستند.دستور AreEquivalent: بررسی میکند که آیا دو کالکشن المان هایی یکسان دارند یا خیر.لیست کامل دستورات Collection Assertion را میتوانید در قطعه کد زیر ببینید. https://gist.github.com/babaktaremi/2a3d5abc4d76669007d6cb3fefd16e7b بررسی صفت های پرکاربرد در NUnitدر NUnit میتوانیم متد ها و کلاس ها را بوسیله صفت ها علامت گذاری کنیم. برخی از این صفت ها عبارتند از:صفت Test : یک Test Method را بویسیله این صفت علامت گذاری میکنیم. در واقع هر متدی که نشان دهنده یک تست باشد باید این صفت را داشته باشد.صفت TestFixture :  بوسیله این صفت یک کلاس که شامل چند تست می باشد را علامت گذاری میکنیم.صفت SetUp : وقتی متدی با این صفت علامت گذاری شود قبل از هر تست اجرا میشود.صفت OneTimeSetUp : وقتی متدی با این صفت علامت گذاری شود، یکبار و فقط یکبار قبل اجرای تمامی تست ها اجرا میشود.صفت OneTimeTearDown : وقتی متدی با این صفت علامت گذاری شود ، پس از اجرای تمامی Test Case ها یکبار اجرا میشود.صفت TearDown : وقتی متدی با این صفت علامت گذاری شود، پس از اتمام هر Test Case اجرا می شود.نمونه ای از استفاده از این صفت ها را میتوانید در قطعه کد زیر ببینید. https://gist.github.com/babaktaremi/396470254e4af87276e8193be7deca04 بررسی  Data Driven Tests در NUnitبرای بررسی یک تست با data خاص دو روش وجود دارد.استفاده از صفت TestCaseدر NUnit و با استفاده از صفت TestCase میتوانیم یک Test Method را با داده های اختیاری تست کنیم. سناریو ساده زیر را در نظر بگیرید. https://gist.github.com/babaktaremi/ad7201490a25f24222bfcf41f942d57b حال این کلاس Price Calculator را میتوانیم بوسیله Test Case با مقدار های مختلف به شکل زیر تست کنیم: https://gist.github.com/babaktaremi/e56ac9527be9608f531dcd785bc26580 همچنین میتوانیم با استفاده از پراپرتی ExpectedResult میتوانیم خروجی متد را به شکل دلخواه در بیاوریم و آن را با Expected Result مقایسه کنیم. https://gist.github.com/babaktaremi/c7b5e5ce47fcab4aaea4e2c4543c29e0 استفاده از صفت TestCaseSourceوقتی میخواهیم یک تست را بر مبنای دیتای خارجی تست کنیم میتوانیم از TestCaseSource استفاده کنیم. این صفت نام یک پراپرتی Static که یک لیستی از TestCaseData بر میگرداند را میگیرد. فرض کنید که یک فایل csv به شکل زیر داریم و میخواهیم دیتای آن را برای یک Test Case وارد کنیم. https://gist.github.com/babaktaremi/7c069c7e08e4f6effdbf71d5611c8ee3 یک پراپرتی Static با نام TestCases مینویسیم که دیتا را از داخل این فایل csv بخواند و آن را برگرداند https://gist.github.com/babaktaremi/7699f364b666537fcae13ddc0c472726 حال این پراپرتی را در داخل TestCaseSource به شکل زیر استفاده میکنیم. https://gist.github.com/babaktaremi/ee88ed9d9532e888e34d1a0249d36774 نتیجه گیریدر این مقاله با NUnit این فریم ورک قدرتمند و محبوب در زمینه Unit Testing آشنا شدیم . به طور کلی نوشتن تست به داشتن کدی قابل اطمینان کمک شایانی میکند. در قسمت بعدی با  Mocking و کتابخانه Moq آشنا میشویم. خوشحال میشوم که نظرات شما را درباره این مقاله بدانم و همچنین اگر دوست داشتید میتوانید من را به یک قهوه مهمان کنید. https://coffeebede.ir/buycoffee/bobby مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Thu, 20 May 2021 11:53:40 +0430</pubDate>
            </item>
                    <item>
                <title>نکات Refactoring برای برنامه نویسان C#</title>
                <link>https://virgool.io/dotnetzoom/%D9%86%DA%A9%D8%A7%D8%AA-refactoring-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%D8%A7%D9%86-c-qhh3xibz371j</link>
                <description>در این مقاله قصد داریم به بررسی Code Smell ها و راه حل های Refactoring آنها بپردازیم.در این مقاله از GitHub Gist استفاده شده است و لود شدن بخش مربوط به کد ها ممکن است کمی زمانبر باشدوجود متد های بلند و بزرگیکی از نکاتی که هنگام Refactoring باید در نظر داشته باشید شکاندن متد های بزرگ به متد های کوچکتر می باشد. هنگام نوشتن کد معمولا متوجه وجود متد های بلند نمیشویم ولی هنگامی که میخواهیم آنها را Review و Refactor کنیم متوجه میشویم که متد شامل indentation زیاد و در نتیجه فهم آن بسیار سخت و گاهی اوقات غیر ممکن میشود. همچنین متد های بلند اصل Single Responsibility را زیر سوال میبرد. به طور مثال متد زیر را در نظر بگیرید. https://gist.github.com/babaktaremi/57dd6989a291e654ccb967470883ed91 هم اکنون متد ذکر شده هم مسئول خواندن فایل و هم نوشتن محتوایات آن تحت شرایط خاص در کنسول می باشد. میتوانیم این متد را به شکل زیر به چند متد کوچک تر بشکانیم. با این کار هم متد مربوطه خوانا تر می شود و هم قابلیت استفاده مجدد از کد افزایش می یابد. https://gist.github.com/babaktaremi/6a4ff75d0361d785dc51bb4d18ba16e3  https://gist.github.com/babaktaremi/9c32ecdd325c50728f557a473384fa67  https://gist.github.com/babaktaremi/1be060031f5411a74f68af8e9f515cb4 استفاده بیش از حد از if و Conditional Complexityگاهی اوقات یک متد بیش از حد دارای if Statement می باشد که خوانایی کد و Track کردن اینکه کدام شرط به کدام مسیر از کد منجر می شود را دشوار می سازد. که در این حالت کد دارای Indentation بسیار زیاد میشود که آن را بد شکل می سازد. به طور مثال کد زیر را در نظر بگیرید. https://gist.github.com/babaktaremi/a1528ddc3dee06886f9fbd21beeed798  همانطور که میبینید دنبال کردن اینکه کدام شرط به کدام مسیر منجر میشود کار بسیار سختی است. همیشه سعی کنید که تعداد If Statement های تو در تو را به کمترین حالت ممکن برسانید و در صورت نیاز متد را به متد های کوچکتر بشکانید و شرط ها را داخل آن چک کنید.مشکل Primitive Obsessionاین مشکل زمانی رخ میدهد که وروردی و یا خروجی متد های مختلف به جای اینکه یک data structure مناسب (مانند کلاس و...) باشد ، یک نوع primitive مانند int و یا string و... است که باعث ایجاد constraint ها و چک کردن نوع داده و در نتیجه duplicate code می شود. به طور مثال متد AddHoliday(2,3) را در نظر بگیرید. در این متد مشخص نیست که عدد اول ماه است یا عدد دوم. بهتر است که با ساخت Data structure مناسب صریحا نوع تاریخ را مشخص کنیم. پس متد AddHoliday را به شکل زیر Refactor میکنیم. https://gist.github.com/babaktaremi/a541ffa4b5d762306e0df656feeb6c75  نام گذاری ضعیفیکی دیگر از نکات مهم هنگام Refactor کردن کد، استفاده از نام های مناسب برای متغیرهاست. متغیر ها باید دارای نام هایی باشند که:به اندازه کافی درباره کاربرد داده توضیح دهنده باشنداز استاندارد ها پیروی کنند( نام متغیر های محلی بصورت camel case و نام کلاس ها و پراپرتی های پابلیک و متد ها PascalCase باشد و...)نام ها نباید خلاصه شده و مختصر باشند.باید نام گذاری متغیر و متد دقیق  و Self Descriptive باشد.برای مطالعه بیشتر درباره naming guideline ها میتوانید به این لینک مراجعه کنیدکد تکراییکی از مشکلات رایج که هنگام Refactoring باید به آن دقت داشته باشید کد تکراری است. این مشکل اغلب به خاطر Copy/Paste کد بوجود می آید. اصل Don&#x27;t Repeat Yourself و یا DRY بیان میکند که تا جای ممکن باید از کد تکراری  پرهیز کرد و اگر دو متد دارای یک بخش با Logic یکسان هستند، باید آن بخش را به عنوان یک متد مجزا Extract کرد و در هر دو متد استفاده کرد.مارتین فاولر قانون معروفی به نام Rule of Three معرفی کرده است که بیان میکند که اگر کدی 3 بار یا بیشتر تکرار شود باید استخراج و در procedure خود گنجانده شود.یکی از موارد کد تکراری Validation Logic می باشد که در آن اعتبار یک کلاس یا متغیر سنجیده می شود. به مثال زیر دقت کنید. https://gist.github.com/babaktaremi/1ae89bdb5d79d69fb6928ff488c8808e برای بررسی معتبر بودن یک متغیر، بهتر است که کد اعتبارسنجی آن را نوشته و در جاهای مختلف استفاده کنیم. متد بالا را میتوان به شکل زیر بازنویسی کرد: https://gist.github.com/babaktaremi/0702487a19f1cfd5e9225466c3093804 مشکل Hidden Temporal Couplingاین مشکل زمانی رخ میدهد که بین دو متد هیچ رابطه ای به صورت صریحا وجود ندارد ولی ترتیب اجرای متد ها مهم است. متد ها باید تا جای ممکن Side Effect Free باشند( درباره مطالعه  Side Effect میتوانید به این مقاله مراجعه کنید)به طور مثال متدهای زیر را در نظر بگیرید. https://gist.github.com/babaktaremi/93bc35182afc69d323bdc8426bc9e517 در این متد ها هیج راهی برای فهمیدن ترتیب اجرای آنها وجود ندارد و یک Developer باید با آزمون و خطا ترتیب اجرای آنها را بفهمد. میتوانیم از با استفاده از Template Method متد های بالا را در یک کلاس Abstract آورده و به ترتیب آنها را صدا بزنیم. پس کد بالا را به شکل زیر Refactor میکنیم. https://gist.github.com/babaktaremi/55646bd93c9ecb765dd1f08c65e540a1 حال متد BakeProcess صریحا ترتیب لازم برای اجرای متد های ذکر شده را مشخص میکند.یک راه حل دیگر میتواند این باشد که هر متد نتیجه متد قبلی خود را درخواست کند. پس میتوانیم مانند زیر عمل کنیم: https://gist.github.com/babaktaremi/36dbdec2b30ffce85bd1b5a2ef61dd4e مشکل ادغام Query و Command با یکدیگریکی از نکات مهم هنگام Refactoring، جداسازی Query و Modifier آز یکدیگر است. باید سعی شود که Query ها که یک مقدار را برمیگردانند بدون Side Effect باشند و از طرفی Modifier ( یا Command) قابل تشخیص و جداسازی از Query ها باشند چرا که دارای Side Effect هستند. در این رابطه اصل CQRS یا Command Query Responsibility Segregation بیان میکند که هر درخواست باید Command یا Query باشد، مدل های درخواست نیز باید یا Command و یا Query باشند. این به آن معنی است که مدل های پروژه نیز کاملا از هم جدا هستند و هر یک نماینده یک Command و یا Query می باشند. قبلا در رابطه با CQRS مقاله ای تهیه کردم که برای مطالعه میتوانید به لینک های زیر مراجعه کنید. https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%A7%D9%88%D9%84-%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-cqrs-pqcvdqzbcend  https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%AF%D9%88%D9%85-%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%DB%8C-mediator-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-%DA%A9%D8%AA%D8%A7%D8%A8%D8%AE%D8%A7%D9%86%D9%87-mediatr-aavpwkagy2pu  https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%B3%D9%88%D9%85-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%D8%B9%D9%85%D9%84%DB%8C-ccam8lktowgk مشکل متغیر موقتاین مشکل زمانی رخ میدهد که یک متعیر فقط در یک شرایط خاص مقدار دهی میشود و در باقی شرایط مقدار Null و یا پیش فرض خود را دارد. در قطعه کد پایین CalculateBonus تنها وقتی مقدار درست و غیر صفر دارد که بعد از متد CalculateEarnings صدا زده شود: https://gist.github.com/babaktaremi/aea916789c34b009844457bf096fd32d بهتر است که در چنین شرایطی مستقیما پارامتر را به متد پاس داد و یا متد محاسبه Earning را از کلاس Employee جدا کرده و یک instance معتبر از Employee را به آن پاس داد. پس میتوان یک کلاس به شکل زیر ایجاد کرد. https://gist.github.com/babaktaremi/0346cfef593b93e461779bfe630ea6ef در اینجا به جای آنکه یک متغیر که گاهی اوقات مقدار دهی میشود را به متد های مختلف پاس بدهیم، یک instance از Employee که همیشه مقدار دهی شده است را به متد های مختلف پاس میدهیم. که در این حالت مشکل Null و یا مقدار پیش فرض بودن یک متغیر را نخواهیم داشت.مشکل وابستگی پنهاناین مشکل زمانی رخ میدهد که یک کلاس یا متد وابستگی هایی داشته باشد که از طریق Constuructor و یا Parameter خود این وابستگی ها را درخواست نکرده باشد و به نوعی این وابستگی ها را پنهان کرده باشد.به کلاس زیر دقت کنید.کلاس Employee در متد Calculate وابستگی مستقیم به کلاس Calculator دارد که این وابستگی به هیچ وجه از خارج کلاس مشخص نیست. https://gist.github.com/babaktaremi/4f5af0f612f43d4a2d3c9cdd324298f8 بهتر است که کلاس وابستگی خود به کلاس Calculator را صریحا نشان دهد و یک instance از کلاس Calculator را به جای ایجاد کردن، در Construcutor خود درخواست کند. پس کلاس Employee را به شکل زیر Refactor میکنیم. https://gist.github.com/babaktaremi/0ff42c17b93092ff1c2ef601d6b59f3d کلاس های Anemicیکی دیگر از مشکلاتی که هنگام Refactoring به آن برمیخوریم، کلاس های دارای تنها فیلد و پراپرتی و بدون رفتار هستند که معمولا برای انتقال Data از آنها استفاده میشود. این گونه کلاس ها معمولا وابسته به سایر کلاس ها برای اعتبارسنجی و سایر عملیات هستند. به مثال زیر دقت کنید. https://gist.github.com/babaktaremi/67745053a492f69981332cee77848682 این کلاس هیچگونه رفتاری ندارد و وابسته کلاس دیگر برای انجام عملیات می باشد. به طور مثال یک کلاس دیگر میتواند از این کلاس به شکل زیر استفاده کند: https://gist.github.com/babaktaremi/cacd748929c944112c4052ba9b692bb5 میتوانیم متد CalculateInterst را مستقیما درون کلاس Account پیاده سازی کنیم و این ویژگی را وابسته به کلاس Account کنیم. پس کلاس Account را به صورت زیر Refactor میکنیم. https://gist.github.com/babaktaremi/cf5d21ac47d7d6febafec2dac8f244b9 نتیجه گیریدر این مقاله به بررسی 10 نکته برای Refactor کد های C# پرداختیم. به طور کلی رعایت اصول SOLID و استفاده از Design Pattern ها میتواند به داشتن یک کد تمیز کمک کند. خوشحال میشوم که نظرات شما در رابطه با مقاله را مطالعه کنم. همچنین اگر دوست داشتید میتوانید من را به یک قهوه مهمان کنید. https://coffeebede.ir/buycoffee/bobby مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom  </description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Sun, 02 May 2021 12:03:16 +0430</pubDate>
            </item>
                    <item>
                <title>اشتباهات رایج در استفاده از Async/Await + راه حل</title>
                <link>https://virgool.io/dotnetzoom/%D8%A7%D8%B4%D8%AA%D8%A8%D8%A7%D9%87%D8%A7%D8%AA-%D8%B1%D8%A7%DB%8C%D8%AC-%D8%AF%D8%B1-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-asyncawait-%D8%B1%D8%A7%D9%87-%D8%AD%D9%84-ca8xabgfwrqw</link>
                <description>در این مقاله قصد داریم به 7 اشتباه رایج در استفاده از Async/Await بپردازیم و راه حل مناسب را برای آن ارائه دهیم.در این مقاله از Github Gist استفاده شده است و لود شدن بخش مربوط به کدها ممکن است کمی زمانبر باشد. همچنین ممکن است که نیازمند نرم افزار رفع تحریم باشید.1-یک بار async. همواره asyncیکی از اشتباهات رایج هنگام استفاده از TPL این است که یک Task که async میباشد در یک متد sync صدا زده شود. همیشه به یاد داشته باشید که اگر یکجا از متد async استفاده کرده اید همواره آن را در متد های async استفاده کنید و برای متد های sync، معادل async آن را بنویسید.در مثال زیر یک کلاس SomeTask داریم که یک متد async دارد. https://gist.github.com/babaktaremi/ce5e78352a4969983337dd04fe991098 حال در کلاس Main آن را به صورت زیر در متد Main استفاده میکنیم که اشتباه است.چون یک متد async را داخل یک متد sync صدا زده ایم. https://gist.github.com/babaktaremi/61ce26657a1447948f5b4cdc6db2e61c متد Main را به شکل زیر به صورت زیر بازنویسی میکنیم و معادل async آن را مینویسیم. https://gist.github.com/babaktaremi/ebb452cbb21d66570a6e5955b745ec2c 2-استفاده از async voidیکی دیگر از اشتباهات بسیار خطرناک در مورد استفاده از async/await استفاده از async void به جای async Task می باشد. متدی که async void باشد دو مشکل جدی را به همراه خود دارد:1- در سایر متد ها نمی توان آن را await کرد چرا که خروجی آن از جنس Task نیست و در نتیجه ممکن است که جوابی که مد نظر ماست از این متد دریافت نشود.2- در صورت وقوع exception نمیتوان آن را catch کرد چون که خروجی از جنس void می باشد و هیچ مقداری ندارد. و در نتیجه کل برنامه crash میکند.پس بهتر است که جنس خروجی متد های async را از نوع Task قرار دهیم.نکته:تنها یک جا و فقط یک جا مجاز به استفاده از async void هستیم و آن هم موقع هندل کردن Event ها می باشد.در کلاس SomeTask زیر متد DoSomeWork به صورت async void نوشته شده است. https://gist.github.com/babaktaremi/fd627e4c914849f4c07be0b35abb830c اگر دقت کنید در متد Main کلاس Program وقتی که متد DoSomeWork را میخواهیم await کنیم به خطا بر میخوریم. https://gist.github.com/babaktaremi/f4efd37a29b3b1f2492ebb2ec47dc376 برای بر طرف کردن خطا Signature متد را به جای void به Task بر میگردانیم. https://gist.github.com/babaktaremi/fb05176a5987de40179cc3bcadcc6d59 3-استفاده از Task.Run به جای Task.FromResultهمیشه بهتر است وقتی که متد شما async است ولی بدنه آن هیچ عملیات async ای ندارد و نمیخواهید که عملیاتی را به صورت async انجام دهید از Task.FromResult به جای Task.Run استفاده کنید . چرا که استفاده از Task.Run هیچ مزیتی به همراه خود ندارد و تنها یک Thread اضافی از Thread Pool را مصرف میکند(مگر آنکه واقعا نیاز داشته باشید که عملیات در یک Thread دیگر انجام شود که مانعی ندارد).مجددا در متد DoSomeWork اینبار یک مقدار int را بر میگرداند. در روش اول این کار را با استفاده از Task.Run انجام میدهیم که اشتباه است. https://gist.github.com/babaktaremi/daf20daf26c16efc1660c13e3e6e5b3e در روش دوم همانطور که گفته شد به جای استفاده از Task.Run از Task.FromResult و یا Task.CompletedTask برای نوع غیر جنریک Task استفاده میکنیم. https://gist.github.com/babaktaremi/f3648807ff1c9c6154e24e507d020d61 4-استفاده از .Result و .Waitیکی دیگر از اشتباهات رایج، استفاده از Result و Wait می باشد. با اینکار عملیات async به صورت sync اجرا میشود و همچنین یک Thread دیگر برای اجرای عملیات استفاده میشود. یعنی عملا دو Thread برای اجرا به صورت sync مورد استفاده قرار میگیرد. این کار ممکن است که موجب ایجاد بن بست (Deadlock) شود. در این مورد نیز باید قانون &quot;یک بار async همواره async&quot; را رعایت کنیم و متد های async را همواره به صورت async اجرا کنیم. در نظر داشته باشید که در اکثر مواقع میتوان معادل async تمام متد های sync را نوشت.در متد DoSomeWork زیر خروجی آن به صورت Task می باشد. https://gist.github.com/babaktaremi/05dea35b7cdc9eca744c5487ba57bd9d در متد Main زیر برای بدست آوردن نتیجه Task از Result آن استفاده کرده ایم که کار اشتباهی است. https://gist.github.com/babaktaremi/0cdcfcada14da0fd60fb61741a138959 روش درست آن است که متد main را به صورت async در بیاوریم و برای بدست آوردن نتیجه آن را await کنیم. پس متد main را به صورت زیر بازسازی میکنیم. https://gist.github.com/babaktaremi/c212957c92c58fe3cb7e9079598074de 5- عدم استفاده از ()GetAwaiter().GetResultزمانی که نیاز است حتما یک متد async را داخل یک متد sync صدا بزنید، بهتر است که به جای استفاده از .Wait و .Result از ()GetAwaiter().GetResult استفاده کنید. با این کار در صورت وقوع exception به جای برگرداندن Aggregate Exception که مجموعه ای از Exception ها است ، خود Exception را بر میگرداند.6-استفاده بیش از حد از async awaitیکی از اشتباهات رایج استفاده بیش از حد از async await می باشد. در اینجا میتوانیم از تکنیک Task Eliding استفاده کنیم. یعنی به جای اینکه یک Task را await کنیم، خود Task را مستقیما بعنوان خروجی متد برگردانیم و در متد های بالاتر از آن هنگامی که میخواهیم از آن استفاده کنیم، آن را await کنیم. در متد GetGoogleContent زیر یک Task از نوع stringداریم که در آن نتیجه کار await شده است و محتوای html صفحه اصلی Google برگردانده شده است. https://gist.github.com/babaktaremi/47bcc7ff54fd6ddbd36c319a7630c822  به جای اینکار، بهتر است که مستقیما خود Task را برگردانیم و در متد Main آن را await کنیم. https://gist.github.com/babaktaremi/0b10b5a3f8cb2f147fd64f2e3001d70b سپس از این متد در متد main به شکل زیر استفاده میکنیم. https://gist.github.com/babaktaremi/6e83827ef1e429f0fa82f798976e4f26  7-سینک کردن یک متد async در Constructor یک کلاسهمانطور که میدانید سازنده یک کلاس نمیتواند async باشد. اگر نیاز داشته باشیم که یک async call در constructor یک کلاس داشته باشیم، باید از .Wait و یا .Result استفاده کنیم که قبلا به عنوان یک اشتباه رایج به آن اشاره کرده ایم.یک راه حل مناسب این است که از الگوی Factory Method استفاده کنیم. به جای اینکه از Constructor برای ساخت کلاس استفاده کنیم، از یک static method برای ایجاد آن استفاده کنیم.در مثال زیر فرض کنید که کلاس SomeTask برای ایجاد نیاز به یک async call داشته باشد. پس این کلاس به شکل زیر خواهد بود. https://gist.github.com/babaktaremi/253343ee36c546d91414e8efa6d780cc و سپس در متد Main به شکل زیر از کلاس SomeTask استفاده خواهیم کرد. https://gist.github.com/babaktaremi/f68d10354619bc10865ea6990939cbd8 برای حذف .Wait مراحل زیر را انجام میدهیم.1-ابتدا Constructor کلاس را به شکل private تبدیل میکنیم.2- سپس یک متد استاتیک به نام Create که حاوی امضای async می باشد را ایجاد کرده و کلاس را داخل آن New میکنیم.پس کد کلاس SomeTask را به شکل زیر تغییر میدهیم. https://gist.github.com/babaktaremi/446f7f4d3788e47e0c6fc2dbe2c3632d سپس برای استفاده از کلاس SomeTask در متد main، کد کلاس main را به شکل زیر تغییر میدهیم.  https://gist.github.com/babaktaremi/31fb32edecbb327f7b8f2a8583a6ec70 نتیجه گیریدر این مقاله به بررسی 7 اشتباه رایج هنگام استفاده از async/await پرداختیم.به طور کلی توصیه میشود که هر جا توانستید از async/await استفاده کنید و سعی کنید که Blocking Call نداشته باشید و اگر نیاز به نتیجه یک async call داشتید، آن را await کنید. همچنین همواره در async call ها Task و یا نوع جنریک آن Task&lt;&gt; را استفاده کنید.مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Wed, 31 Mar 2021 22:35:37 +0430</pubDate>
            </item>
                    <item>
                <title>آموزش Caching با استفاده از Redis در ASP Net Core</title>
                <link>https://virgool.io/dotnetzoom/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-caching-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-redis-%D8%AF%D8%B1-asp-net-core-l77s3235bd6t</link>
                <description>در این مقاله قصد داریم به بررسی دیتابیس Redis و Cache کردن داده در آن به وسیله ASP Net Core بپردازیمدر این مقاله از Github Gist استفاده شده است و لود شدن بخش مربوط به کد ها ممکن است کمی زمانبر باشد. همچنین ممکن است که نیازمند نرم افزار رفع تحریم باشیدآشنایی با Redisردیس یک دیتابیس NoSQL می باشد که از قانون ACID به صورت کامل پیروی نمی کند. یعنی داده در آن Durable نیست و ممکن است که در صورت قطعی برق یا System Crash داده موجود در آن از بین برود(هرچند که با اعمال تنظیماتی میتوان بوسیله آن هرچند ثانیه یکبار داده را روی دیسک ذخیره کرد). Redis داده را به صورت Key/Value ذخیره میکند و به علت سرعت بسیار خوب گزینه مناسبی برای Cache کردن داده و استفاده از آن به صورت متوالی می باشد.راه اندازی Redis در محیط ویندوزبهترین گزینه برای راه اندازی Redis در محیط ویندوز استفاده از داکر می باشد. در cmd دستور زیر را برای نصب ایمیج Redis اجرا میکنیم. دقت داشته باشید که برای استفاده از Docker نیازمند نرم افزار رفع تحریم هستید.docker pull redisپس از مدتی، Image مربوط به Redis دانلود و نصب می شود.ساخت Container برای Redisپس از دانلود و نصب Image مربوط به Redis ، برای استفاده از آن باید اقدام به ایجاد Container بکنیم. نکته مهم هنگام ایجاد Container ، ایجاد پسوورد برای آن است که یک نکته امنیتی مهم محسوب میشود.دستور زیر را در محیط cmd اجرا میکنیم.docker run -p 9191:6379 -d --name RedisCache redis --requirepass &amp;quot123456&amp;quotدر اینجا یک Container از Redis با پورت 9191 و نام RedisCache می سازیم و پسوورد 123456 را برای آن تنظیم میکنیم. سپس دستور زیر را در محیط cmd اجرا میکنیم تا کانتینر ساخته شده شروع به کار کندdocker start RedisCacheبرای تست Redis میتوان با دستور زیر به cli در Redis دسترسی پیدا کرد.docker exec -it RedisCache redis-cliدقت کنید که RedisCache نام کانتینر ساخته شده می باشد.سپس با استفاده از دستور زیر در Redis پسووردی که برای آن تنظیم کرده ایم را وارد میکنیم auth 123456در اینجا ما پسوورد 123456 را برای Redis ست کردیم.با استفاده از دستور زیر میتوانیم از صحت کارکرد Redis مطمئن شویمpingاگر در جواب PONG دریافت کردیم یعنی همه چیز به درستی تنظیم شده است.هم اکنون همه چیز برای استفاده از Redis در ASP Net Core آماده است.استفاده از Redis در ASP Net Coreپس از ایجاد پروژه نمونه ASP Net Core ، اقدام به نصب پکیج StackExchange.Redis میکنیم.برای نصب این پکیج، از طریق  Package Manager Console دستور زیر را اجرا میکنیم.Install-Package Microsoft.Extensions.Caching.StackExchangeRedis -Version 5.0.1برای استفاده از Redis در ASP Net Core نیاز است که تنظیمات زیر را در Startup و در قسمت ConfigureServices اعمال کنیم. https://gist.github.com/babaktaremi/40373d3200e8574e2334843ba4448599 در قسمت Configuration کانکشن Redis و همچنین پسووردی که برای آن تنظیم کرده ایم را قرار میدهیم. قسمت InstanceName بیان میکند که با چه کلید واژه ای قرار است که Value های مربوط به Redis ساخته شوند و در واقع نام پیشوند برای Key های داده هایی که قرار است در Redis ذخیره شوند را تنظیم میکند که آن را خالی میگذاریم و کلید ها را خودمان می سازیم.ساخت مدل پروژهدر مسیر پروژه یک پوشه با نام Model ایجاد میکنیم و در آن یک کلاس با نام Book ایجاد میکنیم.کلاس Book را به شکل زیر می سازیم. https://gist.github.com/babaktaremi/7de13b748bc5e7433141678d92db31bb سپس به سراغ ساخت BookController می رویم و یک API Controller به شکل زیر می سازیم. https://gist.github.com/babaktaremi/7f10d05343abe4122c7616a53ba7b35d ذخیره سازی داخل Redis Cacheبرای دسترسی به Redis نیاز به یک Instance از IDistrubtedCache داریم که آن را در کنترلر Inject میکنیم. https://gist.github.com/babaktaremi/87cbb56d16d5296c62d6264dba137472 برای ذخیره سازی یک نمونه Book در Redis یک اکشن با نام CreateBook می سازیم. دقت داشته باشید که Value ذخیره شده در Redis باید به صورت Byte Array باشد. https://gist.github.com/babaktaremi/dd96736c49b9db35f8301cf0e695aadc در قسمت بالا و در Entry Options برای Expire شدن Cache آن را به صورت Sliding تنظیم کردیم که با هربار دسترسی به Cache مدت زمان Expiration آن تمدید شود و یک روز به Expiration Duration آن اضافه شود.سپس این اکشن را به وسیله Swagger تست میکنیم.گرفتن تمامی Book ها از Redisبرای گرفتن تمامی Instance های ساخته شده و ذخیره شده در Redis نیاز است که ابتدا تمامی کلید های ذخیره شده در Redis را لیست کنیم. برای این کار نیاز به یک Instance از Connection Multiplexer داریم. یکی از Best Practice ها هنگام استفاده از Redis آن است که این Instance در سیستم به صورت Singleton رجیستر شود. پس آن را به صورت زیر در سیستم رجیستر میکنیم. https://gist.github.com/babaktaremi/49a132d6fc6fa6bd28be97e449b9204f حال Constructor کنترلر را به صورت زیر آپدیت میکنیم https://gist.github.com/babaktaremi/551b727b49cebb7efdeb47ac09ad9230 سپس با استفاده از پترن زیر تمام کلید های موجود در Redis که با Book_ شروع میشوند را لیست میکنیم و مقادیر آن را deserialize کرده و در لیست میریزیم. https://gist.github.com/babaktaremi/bbb52c8367948270a67d570bfc051490 سپس این اکشن را بوسیله Swagger تست میکنیم.گرفتن یک Book از Redisدر قسمت قبل یک Instance از Book را با کلید ISBN آن در Redis ذخیره کردیم. حال به وسیله همین کلید همان Instance را بازیابی میکنیم. https://gist.github.com/babaktaremi/6305e629f81a11f020f463fd860ef44a سپس بوسیله Swagger این اکشن را تست میکنیم.حذف یک Book از Redisبرای حذف یک Instance از Book از متد RemoveAsync به صورت زیر عمل میکنیم و به آن کلیدی که برای هر Book تعریف کرده ایم را پاس میدهیم. https://gist.github.com/babaktaremi/d280e014c3ba90bd1932e365f797c68d سپس این اکشن را به وسیله Swagger تست میکنیمسپس مجدد تمام Book ها را واکشی میکنیم. مشاهده میشود که Book مورد نظر با موفقیت پاک شده استنتیجه گیریدر این مقاله به طور اجمالی با caching در Redis آشنا شدیم. به طور کلی Distributed Caching می تواند در مقیاس بالا به افزایش پرفرمنس کمک کند و یکی از ابزار های بسیار کاربردی آن Redis می باشد. اگر به کد های این مقاله نیاز داشتید میتوانید آن را از لینک زیر دریافت کنید. همچنین خوشحال میشوم که نظرات خود را در رابطه با این مقاله مطرح کنید. https://github.com/babaktaremi/Sample-Caching-Redis مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Thu, 18 Mar 2021 11:15:39 +0330</pubDate>
            </item>
                    <item>
                <title>بررسی عملی CQRS- بخش سوم: پروژه عملی</title>
                <link>https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%B3%D9%88%D9%85-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%D8%B9%D9%85%D9%84%DB%8C-ccam8lktowgk</link>
                <description>در این بخش به بررسی و پیاده سازی یک پروژه عملی ساده با CQRS و SQL Server و Mongo DB می پردازیمدر این پروژه قرار است با دو دیتابیس کار کنیم. ابتدا توسط یک Command دیتا را با EF Core در دیتابیس SQL Server ذخیره میکنیم و سپس یک Event ایجاد میکنیم. این Event توسط یک Background Service دریافت شده و دیتا را داخل Mongo DB ذخیره میکند. سپس هرجا که نیاز به خواندن و واکشی داده داشتیم، از داده موجود در Mongo DB استفاده میکنیم.در این مقاله از Github Gist استفاده شده است و لود شدن بخش مربوط به کد ها ممکن است کمی زمانبر باشد. https://vrgl.ir/01pUA  https://vrgl.ir/0a7QA آشنایی با MongoDBیک دیتابیس NoSQL می باشد که داده را به جای اینکه به صورت جدولی و با ردیف های مختلف ثبت کند، آنها را در کالکشن هایی به صورت document ذخیره میکند. در Mongo DB نیازی به تعریف جدول و روابط و ردیف های آن نیست. به همین علت می توان از آن برای Read Model استفاده کرد، چرا که هم پرفورمنس بهتری موقع کوئری گرفتن داده دارد و هم نیازی به تعریف روابط و عمل Join میان جداول مختلف نیست و میتوانیم داده را به صورت DeNormalized در آن ذخیره کنیم.نصب و راه اندازی MongoDB در ویندوزنصب Mongo روی ویندوز کار سر راست و ساده ای است. ابتدا به این لینک مراجعه کرده و نسخه Community آن را دانلود میکنیم. پس از دانلود و نصب آن Mongo Compass هم همراه با آن نصب میشود. Mongo Compass یک GUI برای مدیریت Mongo DB و داکیومنت ها و کالکشن های آن می باشد.برای آشنایی بیشتر با Mongo Compass می توانید به این لینک مراجعه کنید.پس از نصب، داشبورد Mongo Compass به طور خودکار بالا می آید. روی دکمه Connect کلیک کرده و هم اکنون میتوانیم از Mongo Compass استفاده کنیم.داشبورد کلی Mongo Compassبر روی دکمه Create Database کلیک کرده و یک دیتابیس درست میکنیم. نام هر دو Database Name و Collection Name را برای پروژه روی moviesdatabase قرار میدهیم و سپس بر روی دکمه Click Database کلیک میکنیم.آشنایی با معماری کلی پروژهپروژه دارای یک معماری استاندارد سه لایه (Data Access Layer-Core-Presentation) می باشد. در لایه Core جایی است که الگوی Mediator را به وسیله کتاب خانه MediatR پیاده سازی میکنیم و در لایه Data Access لایه برقراری با دیتابیس ها ( Mongo Db و SQL Server) می باشد. لایه Web جایی است که در آن API های پروژه قرار دارند و با استفاده از الگوی Dependecy Injection می توانیم از Handler های موجود در لایه Core استفاده کنیم.معماری استاندارد 3-tierنصب پکیج های مورد نیازبرای کار با Entity Framework Core نیاز به پکیج های متداول زیر داریم. این پکیج ها را در لایه Data Access نصب میکنیمInstall-Package Microsoft.EntityFrameworkCore -Version 5.0.3
Install-Package Microsoft.EntityFrameworkCore.Abstractions -Version 5.0.3
Install-Package Microsoft.EntityFrameworkCore.Design -Version 5.0.3
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.3
Install-Package Microsoft.EntityFrameworkCore.Tools -Version 5.0.3آشنایی با Mongo Driver: یک کتابخانه مانند Entity Framework می باشد که از آن میتوانیم برای ارتباط با Mongo DB استفاده کنیم و به نوعی یک Abstraction روی دستورات مورد نیاز برای کوئری و ثبت داده روی Mongo DB به همراه دارد.( در این مقاله به طور عمیق به Mongo Driver نمی پردازیم و در حد ثبت داده و اجرای یک کوئری ساده به آن بسنده میکنیم. برای آشنایی بیشتر با Mongo Driver میتوانید به مستندات آن مراجعه کنید.)پکبج Mongo Driver را از طریق دستور زیر نصب میکنیمInstall-Package MongoDB.Driver -Version 2.11.6در لایه Core نیاز به دو پکیج زیر برای استفاده MediatR داریمInstall-Package MediatR -Version 9.0.0
Install-Package MediatR.Extensions.Microsoft.DependencyInjection -Version 9.0.0ساخت مدل های دیتابیسهمانطور که میدانید  در CQRS مدل های Read و Write از یکدیگر جدا هستند. به همین منظور برای یک موجودیت نیاز به ساخت دو مدل هستیم. برای پروژه دمو یک موجودیت به نام Movie در نظر گرفته شده است که برای مدل Read و Write آن به شکل زیر عمل میکنیم. یکی از مزایای جداسازی مدل Read و Write آن است که می توانیم در مدل Read دیتا را به صورت Denormalized در بیاوریم و آن را ذخیره کنیم که برای واکشی اطلاعات دیگر نیازی به Join نباشد.در مدل Read با صفت BsonId ابتدا Id دیتا را تعیین و مدل ذخیره سازی آن را تعیین میکنیم. سپس با صفت BsonElement تعیین میکنیم که فیلد به چه صورت در دیتابیس ذخیره شود. https://gist.github.com/babaktaremi/b46559f8694a802c486fddd5adc1e4e2 برای Write Model ها دو موجودیت Movie و Director را در نظر میگیریم که هر Director میتواند چند Movie داشته باشد. https://gist.github.com/babaktaremi/e73b96652028bcc75f604aa461a48097  https://gist.github.com/babaktaremi/c54d053afd34dd8d85d1353477a2a791 سپس به سراغ ساخت ApplicationDbContext می رویم و آن را به صورت زیر می سازیم.  https://gist.github.com/babaktaremi/839ab549214f396a9cdf5abd6f72f769 در کلاس Startup باید ApplicationDbContext ای که ساختیم را رجیستر کنیم https://gist.github.com/babaktaremi/9df59b7914604bf67ebdbcdac6b53edc پس از این کار، به Package Manger Console مراجعه کرده و اولین Migration را برای ساخت جداول اعمال میکنیم.ساخت ریپوزیتوری های مربوط به Write Modelپس از ساخت دیتابیس به سراغ ساخت Repository های مربوط به آن میرویم. در لایه Data Access  یک پوشه با نام WriteRepositories می سازیم و سپس در آن دو کلاس با نام های DirectorRepository و WriteMovieRepository می سازیم.در کلاس WriteMovieRepository سه متد با نام های AddMovieAsync ، GetMovieByIdAsync و DeleteMovie به صورت زیر خواهیم داشت https://gist.github.com/babaktaremi/8271295364b77b06d38acefdacfc6f28 برای DirectorRepository دو متد با نام های GetDirectorAsync و AddDirectorAsync به صورت زیر خواهیم داشت. https://gist.github.com/babaktaremi/cf72f126e8981cba22d7aabfd6c8f16d سپس این دو Repository را در Startup رجیستر میکنیم https://gist.github.com/babaktaremi/5caa4f7ced744373fd89b94c7fe9a080 ساخت ریپوزیتوری های مربوط به Read Modelدر لایه Data Access یک پوشه با نام ReadRepositories می سازیم و سپس در آن یک پوشه دیگر با نام Common می سازیم. در پوشه Common برای راحتی کار با Mongo Driver اقدام به ساخت یک BaseReadRepository می کنیم که در آن از متد های موجود در Mongo Driver استفاده میکنیمکلاس BaseReadRepository را به صورت زیر می سازیم. https://gist.github.com/babaktaremi/ae5d3722934d822a4f3cd1fcbd2a715b یکی از Best Practice ها در مورد Mongo Driver آن است که باید به صورت Singleton در سیستم رجیستر شود چرا که ساخت هرباره یک Instance از Mongo Client بسیار هزینه بر است. پس Mongo Database را به شکل زیر به صورت Singleton رجیستر میکنیم. دقت کنید که در اینجا Connection string و دیتابیس مربوطه در Mongo را هم به Mongo Client پاس میدهیم. یکی از مزیت های Mongo DB آن است که برای ساخت Collection ها نیازی به Migration نیست و به صورت خودکار ساخته می شوند. https://gist.github.com/babaktaremi/ec4b1e17e3cb8653832c14853dd6d145 سپس ReadMovieRespository را به شکل زیر می سازیم و آن را رجیستر میکنیم. https://gist.github.com/babaktaremi/c36ca2fe1c7d4d3d92b79eb5697d46e9  https://gist.github.com/babaktaremi/52f79f31e93f248508479466b18b4023 آشنایی با Background Service در ASP Net Coreدر ASP Net Core قابلیتی به نام Background Task وجود دارد که به کمک آن میتوان از یک Thread جداگانه در Background برای اجرای کارهای زمانبر و یا به صورت Interval بهره برد. هنگام استفاده از Background Task ها باید نکاتی را رعایت کنیم از جمله:برای مطالعه بیشتر درباره Background Service ها و همچنین Hosted Service ها میتوانید به این لینک مراجعه کنید.برای استفاده از Background Service ها نیازمند پکیج زیر هستیم که آن را در لایه Core نصب میکنیمInstall-Package Microsoft.Extensions.Hosting.Abstractions -Version 5.0.0مختصر توضیحی درباره الگوی Pub/Subدر الگوی Pub/Sub دو موجودیت داریم. Publisher و Subscriber که در آن Publisher یک Event را انتشار میدهد و Subscriber های آن Event بسته به شرایط یک سری عملیات خاص را مدیریت و اجرا میکنند. برای اینکه یک Subscriber از Event با خبر باشد، حتما باید عضوی از آن Event باشد که Publisher آن را Invoke میکنداین الگو برای ایجاد Loose Coupling و همچنین در Distributed System ها بسیار کاربرد دارد.آشنایی با channel ها در ASP Net Coreخیلی جاها نیاز هست که یک سری داده در یک صف نگهداری بشوند که بعدا بتوانیم توسط سرویسی آنها را پردازش کنیمدر مقیاس کوچک، به جای استفاده از Message Broker هایی مانند RabbitMq میتوانیم از Channel ها استفاده کنیم. دقت داشته باشید که چنل در مقایسه با Message Broker هایی مثل RabbitMq محدودیت هایی دارد مثلا از چنل ها فقط در یک Application میتوان استفاده کرد و قابلیت به اشتراک گذاشتن محتوای یک Channel میان چند Application وجود نداردچنل در واقع صفی هست که thread safe هست و میتواند یک یا چند prodcuer و consumer داشته باشد برای آشنایی بیشتر با Channel ها میتوانید به این لینک مراجعه کنیدساخت یک چنل جنریکبرای استفاده از Channel ها  در پروژه به سراغ ساخت یک Channel جنریک میرویم که بتوانیم از آن در چند جا استفاده کنیم. ابتدا در پروژه یک پوشه به نام Common ایجاد میکنیم و سپس در آن یک پوشه دیگر به نام BaseChannel ایجاد میکنیم. در این پوشه کلاس ChannelQueue را به شکل زیر ایجاد میکنیم.  https://gist.github.com/babaktaremi/d71d43d342d4b97330246c8b3652d0dd سپس این چنل جنریک را به صورت Singleton در سیستم رجیستر میکنیم https://gist.github.com/babaktaremi/2b6b3be51a69190e923f62e6fbc70f38 مسئله Eventual Consistencyزمانی که عملیات Write در دیتابیس اصلی اتفاق می افتد، این تغییر با یک تاخیر در دیتابیس های Read اعمال می شود.در این حالت چون تغییرات به صورت Eventually ( نه در لحظه، بلکه با تاخیر و در نهایت) Sync میشوند، ممکن است داده ای که در لحظه از دیتابیس Read واکشی می شود، به روز و آخرین نسخه نباشد.این تکنیک در سیستم های توزیع شده باعث می شود که در عوض یکپارچگی و ثبات داده ها (Consistency) به پرفورمنس بهتر، Scalability و  High Availability دست پیدا کنیمساخت اولین Commandابتدا به سرغ ساخت AddMovieCommand می رویم. این Command قرار است توسط Handler مربوطه، داده را هم در دیتابیس SQL Server و هم در Mongo DB ذخیره کند.در لایه Core یک پوشه به نام MovieApplication می سازیم و در آن یک پوشه دیگر به نام Commands می سازیم و در آن یک پوشه دیگر به نام AddMovie می سازیم. https://gist.github.com/babaktaremi/fb934879479d9552389c3e6eabaa3c8c  https://gist.github.com/babaktaremi/712a08964962bd2f4f5a602ac3621aec سپس به سراغ ساخت Command Handler مربوط به AddMovieCommand می رویم. https://gist.github.com/babaktaremi/570340969736a70326ee39ae71c13ba8 در خط 6 یک نمونه از چنل را Inject کرده ایم که قرار است Message این چنل توسط یک Background Service مدیریت شود و Movie توسط آن به دیتابیس Mongo اضافه شود. مدل Message قرار داده شده در چنل به شکل زیر است. ابتدا در پوشه MovieApplication یک پوشه با نام BackgroundWorker ایجاد میکنیم و سپس در آن یک پوشه دیگر با نام Common ایجاد میکنیم و در آن یک پوشه دیگر با نام Events ایجاد میکنیمدر این پوشه یک کلاس با نام MovieAdded به شکل زیر ایجاد میکنیم https://gist.github.com/babaktaremi/424c3df4ed471e4eff5a42dc107400c4 برای ساخت Background Worker مربوطه یک پوشه با نام AddReadMovie ایجاد میکنیم و در آن یک کلاس با نام AddReadModelWorker می سازیم. https://gist.github.com/babaktaremi/d0ccecce383cf8274866a5885ff0e199 چون Background Service ها به صورت Singleton در سیستم رجیستر میشوند، به کمک IServiceProvider یک Scope می سازیم و در آن سرویس های مورد نیاز را Inject میکنیم. در خط 19 و 20 به ترتیب WriteMovieRepository و ReadMovieRepository را از Scope ساخته شده دریافت میکنیم. سپس Movie را از دیتابیس دریافت کرده و در دیتابیس Mongo قرار میدهیم.در نهایت این Background Service را در Startup رجیستر میکنیم. https://gist.github.com/babaktaremi/af1680e21b254bff14532120e6731e22 به همین ترتیب میتوان سرویس های مربوط به Delete و Update را نیز نوشت.ساخت کوئری و دریافت Movie از Mongo DBدر پوشه MovieApplication یک پوشه دیگر با نام Queries می سازیم و در آن یک پوشه با نام GetMovieById می سازیم. در این پوشه دو کلاس با نام های GetMovieByNameQuery و GetMovieByNameQueryHandler را قرار میدهیم. https://gist.github.com/babaktaremi/50b4894e0033511829835f35062ed772 اگر یادتان باشد قبلتر در این مقاله در مورد ReadMovieRepository صحبت کردیم و در آن متدی با نام GetByNameAsync داشتیم که یک Movie را از دیتابیس Mongo بر اساس نام فیلتر و واکشی میکند. از این متد در Handler مربوط به این کوئری استفاده خواهیم کرد. https://gist.github.com/babaktaremi/981ecfa03014e4a17d8f3a6a16e13b2b ساخت کنترلر Movieابتدا یک Instance از IMediator را در کنترلر Inject میکنیم. سپس اکشن AddMovie را به شکل زیر می سازیم. https://gist.github.com/babaktaremi/7be3b102908fe5104d8133cc98d5e3a6 سپس بوسیله Swagger این اکشن را تست میکنیم.به SQL Server Management Studio مراجعه میکنیم. ملاحظه میشود که یک Instance از Movie و یک Instance از Director به دیتابیس اضافه شده است.حال به قسمت مهم ماجرا میرسیم. به سراغ Mongo DB Compass می رویم. در ابتدا یک دیتابیس با نام moviesdatabase ساختیم. به سراغ آن میرویم. مشاهده می شود که یک Collection با نام Movie ساخته شده است که در آن اطلاعات Movie به صورت Json قرار دارد.ساخت اکشن GetMovieهمانند اکشن AddMovie این بار با استفاده از MediatR به سراغ واکشی اطلاعات یک Movie می رویم.  https://gist.github.com/babaktaremi/0969747336d50c553ad008e569160a63 برای تست این اکشن به سراغ Swagger می رویم. ملاحظه میشود که اطلاعات از دیتابیس Mongo DB خوانده میشود چرا که یک ObjectId که Mongo به Document اختصاص میدهد نیز واکشی شده است.نتیجه گیریدر این مقاله یک پروژه بسیار ساده را با استفاده از MediatR و دیتابیس های MongoDB و SQL Server با پترن CQRS پیاده سازی کردیم. توصیه میکنم که این پروژه را از گیت هاب دریافت کرده و با همین پترن، اکشن Update یک Movie را پیاده سازی کنید. از طریق لینک زیر میتوانید پروژه را از گیت هاب دریافت کنید و اگر سوال و یا پیشنهادی دارید، خوشحال میشوم که آن را در قسمت نظرات مطرح کنید. https://github.com/babaktaremi/Sample-CQRS-Project مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Fri, 19 Feb 2021 18:01:11 +0330</pubDate>
            </item>
                    <item>
                <title>بررسی عملی CQRS- بخش دوم: بررسی الگوی Mediator با استفاده از کتابخانه MediatR</title>
                <link>https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%AF%D9%88%D9%85-%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%DB%8C-mediator-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-%DA%A9%D8%AA%D8%A7%D8%A8%D8%AE%D8%A7%D9%86%D9%87-mediatr-aavpwkagy2pu</link>
                <description>در این بخش به یکی از ملزومات پیاده سازی CQRS یعنی الگوی Mediator و کتابخانه MediatR می پردازیم. https://vrgl.ir/01pUA در قسمت قبلی به طور اجمالی با CQRS آشنا شدیم و اشاره کردیم که الگوی Mediator به ارائه یک سولوشن تمیز در ارتباط با CQRS کمک شایانی میکند و باعث ایجاد Loose Coupling می شود. در این قسمت به بررسی کتابخانه معروف و محبوب MediatR از جیمی بوگارد می پردازیم.در این مقاله از Gist استفاده شده است و لود شدن بخش مربوط به کد ها ممکن است کمی زمانبر باشدالگوی Mediatorدر الگوی Mediator دیگر کلاس ها و سرویس ها با یکدیگر مستقیم در ارتباط نیستند بلکه درخواست خود را از طریق یک واسط می فرستند و از طریق همان واسط، پاسخ مربوطه را دریافت میکنند. این کار باعث می شود که وابستگی شدید بین دو سرویس به حداقل برسد و Loose Coupling داشته باشیم که به توسعه و نگهداری یک پروژه کمک شایانی میکند.کتابخانه MediatRکتابخانه MediatR یک پیاده سازی از الگوی Mediator در دات نت می باشد که از Request و Command پشتیبانی میکند. در MediatR هر درخواست در یک صف در حافظه قرار میگیرد و سپس به Handler مربوط به آن درخواست وصل می شود. MediatR هیچ وابستگی به سایر کتابخانه ها ندارد و از Generic ها در سی شارپ برای ساخت Response و همچنین تعیین Handler ها استفاده میکند. یکی دیگر از ویژگی های بسیار خوب MediatR، پشتیبانی از الگوی Pub/Sub می باشد که به وسیله آن میتوان یک notification ایجاد کرد و در جاهای مختلف پروژه این notification را صدا زد.الگوی Pub/Sub با استفاده از Notification ها در MediatRبررسی Request و Request Handler در MediatRدر MediatR برای رسیدگی به Request ها از Handler ها استفاده میکنیم. هر درخواست از اینترفیس IRequest (و یا مدل Generic آن ) ارث بری میکند. و Handler مربوط به آن از اینترفیس IRequestHandler&lt;TRequest,TResponse ارث بری میکند. که TRequest همان درخواست می باشد که از IRequest ارث بری کرده است و به این ترتیب Request به Handler خود مپ میشود. در نمونه کد زیر یک Request و Request Handler مربوط به آن را میتوانید ببینید. https://gist.github.com/babaktaremi/61bae27d61130fb1255fb7c17c2dd048  https://gist.github.com/babaktaremi/8cf76b9b457cae9f332517f4de1a525a در مدل جنریک Handler، خروجی درخواست معلوم میشود که از جنس boolean می باشد. اینترفیس IRequestHandler یک متد به اسم Handle دارد که باید پیاده سازی شود.سپس در کنترلر مربوطه، یک instance از IMediator را تزریق کرده و کلاس Request را به شکل زیر به آن پاس میدهیم. https://gist.github.com/babaktaremi/23883059e79e13c8d14fef42f0c3620c استفاده از MediatR در پروژه عملیپس از ایجاد یک پروژه نمونه، پکیج مربوط به MediatR را از طریق Package Manager Console با دستور زیر نصب میکنیم:Install-Package MediatRو یا از طریق Manage Nugget Packages پکیج مربوط به MediatR را نصب میکنیم:سپس نیاز است برای استفاده از MediatR در Dependency Injection پکیج MediatR.Extensions.Microsoft.DepenedencyInjection را نیز نصب کنیم. این پکیج اینترفیس Imediator و همچنین Pipeline ها و Handler و Request ها را به صورت Transient به سیستم تزریق میکند.برای نصب میتوانیم از طریق Package Manager Console دستور زیر را اجرا نماییم:Install-Package MediatR.Extensions.Microsoft.DependencyInjection و یا از طریق Manage Nugget Manager برای نصب اقدام نماییمسپس در Startup.cs در قسمت Configure Services سرویس مربوط به MediatR را تزریق میکنیم. https://gist.github.com/babaktaremi/d1bdee85964361c5a656ecee734280b1 به AddMediaR باید Assembly مربوط به قسمتی که Handler ها در آن قرار دارد را پاس بدهیم. در این پروژه چون Class Library جدا نداریم همان Assembly مربوط به Startup را پاس دادیم. تمامی تنظیمات مربوط به MediatR انجام شده است. ایجاد اولین Request و Handlerبرروی سولوشن راست کلیک کرده یک فولدر به نام Data ایجاد میکنیم. در این پوشه، پوشه دیگری به نام Model ایجاد میکنیم که در این پوشه قرار است مدل ها و انتیتی های پروژه قرار بگیرند.در پوشه Model یک کلاس به نام Book ایجاد میکنیمسپس در پوشه Model یک کلاس به نام Book ایجاد میکنیم که شامل Property های زیر می باشد. https://gist.github.com/babaktaremi/0c2e6b9e6086392ee0edfb3d80d24fa0 این کلاس قرار است نقش Book Entity را بازی کند.در پوشه Data، یک پوشه دیگر تحت عنوان   Repositories می سازیم.سپس در پوشه Repository یک پوشه دیگر تحت عنوان BookRepository می سازیم که قرار  است در این پوشه ریپوزیتوری مربوط به کلاس Book را بسازیم. در این پروژه از In Memory Database استفاده میکنیم و لیستی از Book را درون حافظه ذخیره میکنیم و از آن نیز Book را باز خوانی میکنیم.یک اینترفیس با نام IBookRepository می سازیم. https://gist.github.com/babaktaremi/6aca444a32be029d3fbecfc1e63d27e1 سپس کلاسی با نام BookMockRepository می سازیم و این اینترفیس را در آن به شکل زیر پیاده سازی میکنیم. https://gist.github.com/babaktaremi/3b7e83db74e035aca81f779d527ba332 سپس این اینترفیس را به صورت Scoped رجیستر میکنیم.حال همه چیز برای ایجاد اولین Handler آماده است. در پروژه یک پوشه دیگر به نام Application ایجاد میکنیم. لایه Application وظیفه سازماندهی به ریکوئست ها را بر عهده دارد ( Request Orchestration). برای مطالعه بیشتر درباره لایه اپلیکیشن و همچنین لایه بندی پروژه میتوانید به این لینک مراجعه کنید.در پوشه Application یک پوشه به نام BookApplication ایجاد میکنیم. در این پوشه یک پوشه دیگر به نام GetBookById ایجاد میکنیم.در این پوشه دو کلاس قرار میگیرد:GetBookByIdRequestGetBookByRequestHandlerکلاس GetBookByIdRequest از اینترفیس IRequest ارث بری میکند.این اینترفیس جنریک یک پارامتر ورودی دارد که نوع Response یک Request را مشخص میکند. که در اینجا Response درخواست GetBookByIdRequest یک Book می باشد. https://gist.github.com/babaktaremi/2fecbc1dea3c97a07cd5b206973d6595 در قدم بعدی برای این Request یک Handler مینویسیم که وظیفه ایجاد Response  برای GetBookByIdRequest را برعهده دارد. هر Handler از اینترفیس IRequestHandler ارث بری میکند. این اینترفیس دو پارامتر را به عنوان وروردی جنریک میگیرد، اولین پارامتر Request و دومین پارامتر  Response مربوطه می باشد. این اینترفیس یک متد به نام Handle از جنس Task دارد که باید پیاده سازی شود.در این Handler یک نمونه از IBookRepository را Inject میکنیم. در متد Handle با استفاده از BookRepository یک Instance از Book را به عنوان Response بر میگردانیم. https://gist.github.com/babaktaremi/28f5cd4f1424097a0f0e908aa235dc15 سپس یک کنترلر برای استفاده از این Handler می سازیم و نام آن را BookController قرار میدهیم. برای استفاده از Handler های MediatR ، اینترفیس IMeditor را در کنترلر تزریق میکنیم.برای دریافت Book از طریق Id، ریکوئست GetBookByIdRequest را به متد Send در اینترفیس IMediator پاس میدهیم.حال این متد یک Book را به ما بر میگرداند چرا که در تایپ IRequest یک Book را به عنوان Response تعریف کرده ایم. https://gist.github.com/babaktaremi/41ab0e9c5f2ff30820d1033416f62eb6 به وسیله Swagger این API را چک میکنیم. و جواب یک Instance از Book می باشد. آشنایی با Behavior های MediatRبا استفاده از Behavior ها در MediatR میتوان پایپ لاین های مخصوص ساخت که در حین، قبل و یا بعد اجرای یک Handler اجرا شوند.میخواهیم با استفاده از Behavior ، درخواست هایی که به سمت هرکدام از Handler ها فرستاده میشوند را چک کنیم و در صورت وجود خطا،آن را لاگ کنیم.در پوشه Application یک پوشه دیگر به نام Common می سازیم. در این پوشه یک کلاس به نا LoggingBehavior ایجاد میکنیم. این کلاس را به صورت جنریک در می آوریم. برای ساخت پایپ لاین در MediatR باید کلاس، اینترفیس IPiplelineBehavior ارث بری کند. این اینترفیس یک متد به نام Handle و از جنس Task دارد که باید پیاده سازی شود. https://gist.github.com/babaktaremi/7733bf688e8aadda7181064199b495a0 در قطعه کد بالا، یک اینستنس از ILogger را به پایپ لاین تزریق کردیم که در صورت وقوع خطا از آن برای لاگ کردن خطا استفاه میکنیم. برای اینکه در ادامه Handler مربوطه صدا زده شود،نیاز است که next صدا زده شود. این عمل در اصل باعث تشکیل Pipleline میشود و باعث میشود که LoggingBehavior در مسیر یک درخواست برای رسیدن به یک Handler قرار بگیرد.پس از ساخت Pipeline آن را در Startup رجیستر میکنیم. https://gist.github.com/babaktaremi/774775084b7187adfaf9b8d6ef4de393 برای تست پایپ لاین، در GetBookByIdRequestHandler یک خطای تستی پرتاب میکنیم.و مجدد Api ای که ساختیم را در Swagger صدا میزنیم. مشاهده میکنیم و که Pipeline ساخته شده حین درخواست، خطا را Catch و آن را لاگ کرده است و API در واقع خطا را دریافت نکرده استدر MediatR دو Behavior به صورت پیش فرض وجود دارد مه به صورت پیش فرض در MediatR رجیستر شدند و میتوان آنها را پیاده سازی و استفاده کرد.IRequestPreProccessorاین پایپ لاین قبل از اجرای هر Handler اجرا میشود. این اینترفیس یک متد به نام Process دارد که نوع درخواست را در را میتوان به وسیله آن در اختیار داشت و با توجه به آن قبل از هر درخواست یک سری عملیات را انجام داد. https://gist.github.com/babaktaremi/a8d52d5e8f0ed8f713bc4caaca37ad13 IRequestPostProcessorاین پایپ لاین بعد از اجرای هر Handler اجرا میشود. این اینترفیس یک متد به نام Process دارد که به وسیله آن میتوان نوع درخواست و همچنین نوع Response را در اختیار داشت و با توجه به آنها یک سری عملیات انجام داد. https://gist.github.com/babaktaremi/13e98c01eb53b578b0a7a99989327879 پیاده سازی الگوی Unit of Work توسط Post Processor در MediatRهمانطور که میدانید، Post Processor ها بعد از اجرای هر Handler اجرا میشوند و توسط آنها میتوان به نوع درخواست و همچنین نوع Response دسترسی داشت. در این قسمت توسط یک Post Processor میخواهیم الگوی Unit of Work را پیاده سازی کنیم. برای مطالعه درباره Unit of Work میتوانید به این لینک مراجعه کنیددر پوشه Common یک اینترفیس خالی با نام ICommitable ایجاد میکنیم. بوسیله این اینترفیس میخواهیم تمامی درخواست هایی که قرار است در دیتابیس تغییرات داشته باشند را علامتگذاری کنیم. https://gist.github.com/babaktaremi/fc3c09494801509af5834846ac2489b9 در پوشه BookApplication، یک پوشه جدید به نام AddBook ایجاد میکنیم. در این پوشه یک کلاس به نام AddBookRequest به شکل زیر ایجاد میکنیم. این کلاس از اینترفیس ICommitable ارث بری میکند و به نوعی آن را با اینترفیس ICommitable علامت گذاری میکنیم. https://gist.github.com/babaktaremi/c084475ce6485efa18738d015f98ffac هندلر مربوط به این Request را به شکل زیر میسازیم.دقت کنید که برای اضافه کردن مدل جدید، از Mock Repository که قبلتر ساختیم استفاده میکنیم. https://gist.github.com/babaktaremi/396b01cb184703d7f55121720977bf4b برای پیاده سازی الگوی Unit of Work به سراغ پیاده سازی Post Processor آن میرویم. در پوشه Common یک کلاس به نام CommitPostProcessor می سازیم. در اینجا برای نمونه در کنسول لاگ ثبت میکنیم و به نوعی ثبت داده در دیتابیس را شبیه سازی میکنیم. https://gist.github.com/babaktaremi/f561528ebc17253afc303b85c9816332 همانطور که میدانید Post Processor ها در انتهای هر Request به MediatR اجرا میشوند، پس در اینجا در انتهای هر Request چک میکنیم که آیا Request با ICommitable علامت گذاری شده است یا خیر و اگر علامت گذاری شده بود تغییرات را در دیتابیس اعمال میکنیم. به طور مثال اگر از EF Core استفاده میکنید میتوانید در اینجا متد Save Changes روی DbContext را صدا بزنید.(برای مشاهده منبع اصلی این قسمت میتوانید به این لینک در گیت هاب مراجعه کنید.)در نهایت API Action مربوطه را ساخته و با استفاده از اینترفیس IMediator این درخواست را صدا میزنیم. https://gist.github.com/babaktaremi/b73f23b195c1b1e68b7fbff3936ebf77 بررسی الگوی Pub/Subدر الگوی  Pub/Sub دو موجودیت داریم. Publisher و Subscriber که در آن Publisher یک Event را انتشار میدهد و Subscriber های آن Event بسته به شرایط یک سری عملیات خاص را مدیریت و اجرا میکنند. برای اینکه یک Subscriber از Event با خبر باشد، حتما باید عضوی از آن Event باشد که Publisher آن را Invoke میکند این الگو برای ایجاد Loose Coupling و همچنین در Distributed System ها بسیار کاربرد دارد.برای مطالعه بیشتر در مورد پیاده سازی الگوی Pub/Sub در سی شارپ با استفاده از Event Handler ها میتوانید به این لینک مراجعه کنید.پیاده سازی الگوی Pub/Sub در MediatRپیاده سازی الگوی Pub/Sub در MediatR بسیار ساده است. در MediatR مفهومی به اسم Notification وجود دارد. یک کلاس از اینترفیس INotification ارث بری میکند. این کلاس نمایانگر یک Event در MediatR میباشد که بقیه Handler ها و یا کلاس ها میتوانند این کلاس را Publish کنند. سپس Handler مربوط به این Event ( که از INotificationHandler ارث بری کرده است) اقدامات مربوط به این Event را انجام میدهد.از Notification ها میتوان برای انجام کار هایی که بین چند Handler مشترک هستند استفاده کرد.ایجاد یک Notificationفرض کنید میخواهیم به ازای ایجاد هر Book یک Email به ادمین وب سایت ارسال کنیم. ابتدا در پوشه Common یک پوشه دیگر به نام Notifications ایجاد میکنیم.سپس در این پوشه یک پوشه دیگر به نام EmailNotification ایجاد میکنیم.در این پوشه دو کلاس به نام های EmailNotification و EmailNotificationHandler ایجاد میکنیم.کلاس EmailNotification را به شکل زیر ایجاد میکنیم. https://gist.github.com/babaktaremi/2241362c1c4c101473ad7f99ac648990 دقت کنید که این کلاس از INotification ارث بری میکند و شامل دو پراپرتی Email Address و              Email Content می باشد که باید مقدار دهی شوند.سپس کلاس EmailNotificationHandler را به شکل زیر پیاده سازی میکنیم. برای شبیه سازی، به جای ارسال ایمیل، یک لاگ در کنسول ثبت میکنیم. https://gist.github.com/babaktaremi/a98a84a029602da376117fd064fa2bc0 به BookController بر میگردیم. در اکشن CreateBook برای استفاده از این Notification از متد Publish اینترفیس IMediator استفاده میکنیم. https://gist.github.com/babaktaremi/dd1d39c1d4232f71fb158155b53262a7 در خط 8 کد بالا، Notification مربوط به ارسال Email را پابلیش کرده ایم. در کنسول خواهیم داشت:مشاهده میشود که Event مربوطه به ارسال Notification توسط EmailNotifcation Handler اجرا شده است.نتیجه گیریدر این قسمت به بررسی MediatR پرداختیم و با ویژگی های آن آشنا شدیم. پیاده سازی الگوی Mediator با استفاده از کتابخانه MeidatR کاری بسیار راحت و بی دردسر است که به ارائه سولوشن تمیز و ایجاد Loose Coupling (که از ملزومات پیاده سازی CQRS می باشد) کمک بسیازی میکند. همچنین در MediatR میتوان Pipeline های اختصاصی ایجاد کرد و با استفاده از Post Processor ها الگوی Unit of Work را پیاده سازی کرد. در قسمت بعد به پیاده سازی پروژه به روش CQRS با استفاده از MediatR می پردازیم.اگر به سورس کد این قسمت نیاز داشتید میتوانید آن را لینک زیر در گیت هاب دریافت نمایید. و همچنین اگر سوال یا مطلبی بود، خوشحال میشوم که آن را در بخش نظرات مطرح فرمایید. https://github.com/babaktaremi/MediatRExplore مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom </description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Tue, 26 Jan 2021 09:50:29 +0330</pubDate>
            </item>
                    <item>
                <title>بررسی عملی CQRS- بخش اول: مقدمه ای بر CQRS</title>
                <link>https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%A7%D9%88%D9%84-%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-cqrs-pqcvdqzbcend</link>
                <description>در زمینه پروژه های  Enterprise و همچنین بحث داغ Event Sourcing ، حتما درباره CQRS شنیده اید. در این مقاله چند قسمتی، قرار است به صورت عمیق به بررسی CQRS بپردازیم و تکنیک های لازم برای پیاده سازی یک پروژه به صورت CQRS را بررسی کنیم.در این مقاله از Gist استفاده شده است و لود شدن کد ها ممکن است کمی زمانبر باشدتعریف Command و Queryدر تعریف، Command یک دستور است که تقاضای ایجاد و یا تغییر یک Entity را دارد و Query، یک درخواست برای گزارش گیری و یا دریافت اطلاعات یک Entity می باشد. به زبان ساده، هر درخواستی که تغییری در دیتای موجود در سیستم دهد Command و هر درخواستی که تغییری در دیتا ندهد و فقط آن را نمایش دهد Query نام دارد. https://gist.github.com/babaktaremi/62c83bfc7b2028625c4026d3b9e70c6d  https://gist.github.com/babaktaremi/b9e916a7c0343f1278218c08d6455b22 تعریف CQSدر لغت، CQS مخفف Command Query Seperationمی باشد. این به آن معنی است که در هر درخواست، یا باید Command صورت بگیرد و یا Query. در پروژه های روزانه، حتما به این مسئله برخورد کرده اید که متد ها و یا اکشن هایی داشتید که یا دیتا را تغییر می دادند و یا برای نمایش دیتا به دیتابیس کوئری زده می شده است. این همان مبحث CQS در ابعاد به نسبت کوچکتر می باشد.تعریف CQRSدر لغت، CQRS به مخفف Command Query Responsibility Segregation می باشد. در CQRS علاوه بر اینکه هر درخواست باید Command یا Query باشد، مدل های درخواست نیز باید یا Command و یا Query   باشند. این به آن معنی است که مدل های پروژه نیز کاملا از هم جدا هستند و هر یک نماینده یک Command و یا Query می باشند. به عنوان مثال در قطعه کد زیر دو interface داریم که هر کدام یا  Query هستند و یا Command.اینترفیس IMakeOrderCommand برای Command و IGetOrderByIdQueryHandler برای Query استفاده می شودحال این دو اینترفیس را پیاده سازی می کنیم.برای استفاده این دو اینترفیس را رجیستر و در OrderController تزریق می کنیم:همانطور که دیدید. در این حالت تعداد سرویس هایی که به هر کنترلر Inject میشوند بسیار زیاد خواهد شد. برای کاهش وابستگی و ایجاد Loose Coupling بهتر است که از الگوی Mediator و کتابخانه MediatR استفاده شود.به عنوان مثال در کد زیر با استفاده از MediatR یک Command و یک Query ساخته ایم که مدل مربوط به هرکدام از آنها با یکدیگر فرق می کند. و در نهایت تنها کافی است که اینترفیس IMediator به سیستم تزریق شود. https://gist.github.com/babaktaremi/f25978fcd4b4600c9a597901b3b1b7b2  https://gist.github.com/babaktaremi/0ab02cf876f50b05b208ec284c1407a3 در قسمت بعدی درباره MediatR به طور کامل صحبت خواهیم کرد.انواع CQRSبه صورت کلی، می توان CQRS را در سه سطح دسته بندی کرد:در سطح کد: در این حالت تنها به جداسازی Command و Queryدر سطح کد بسنده میکنیم. در این حالت تنها سولوشن تمیز تری خواهیم داشت ولی از نظر پرفورمنس تفاوتی را شاهد نخواهیم بود.در سطح دیتابیس :  در این حالت به بهینه سازی دیتابیس (به خصوص برای Query ها) می پردازیم و در صورت نیاز دیتابیس Command و Query را از هم جدا میکنیم در سطح کد و دیتابیس:  در این حالت چون Command و Query در سطح کد جداسازی شده، یکی کردن بهینه سازی های سطح دیتابیس با کد آسان تر خواهد شد.مقایسه CQS و CQRSبه صورت کلی ، CQS و CQRS بسیار بهم شبیهند ولی در عمل تفاوت هایی دارند. در CQS به جداسازی Command و Query در حد متد و اکشن بسنده می کند ولی در CQRS تمام مدل های Command و Query باید از یکدیگر جدا باشند. به همین علت CQRS سولوشن بسیار تمیزتری را ارائه میدهد که در آن جداسازی دیتابیس ها از یک دیگر بسیار آسانتر خواهد بود. همچنین در CQRS استفاده از الگوی Mediator بسیار حائز اهمیت است و بدون آن پیاده سازی CQRS یا بسیار سخت خواهد شد و یا در نهایت با سرویس ها و کنترلر هایی مواجه خواهیم شد که وابستگی بسیار زیادی به اجزای بسیار زیاد دارند که تغییر و توسعه آن را بسیار سخت میکند.الگوی Mediatorدر الگوی Mediator دیگر کلاس ها و سرویس ها با یکدیگر مستقیم در ارتباط نیستند بلکه درخواست خود را از طریق یک واسط می فرستند و از طریق همان واسط، پاسخ مربوطه را دریافت میکنند. این کار باعث می شود که وابستگی شدید بین دو سرویس به حداقل برسد و Loose Coupling داشته باشیم که به توسعه و نگهداری یک پروژه کمک شایانی میکند.در CQRS استفاده از الگوی Mediator مزیت های فراوانی را به همراه دارد. هر Command و Query در سیستم با استفاده از Mediator به Handler مربوط به خود وصل میشوند و جواب درخواست نیز از طریق همین Handler تامین میشود. الگوی Mediator باعث Loose Coupling می شودچه موقع از CQRS استفاده کنیم؟هنگامی که در یک سناریو نیاز است که دیتا مربوطه را از چند جدول لود کنیم، عمل Join بین چند جدول ممکن است زمانبر باشد و روی Performance تاثیر منفی بگذارد، پس بهتر است که دیتای مورد نیاز را در یک جدول جدا و به صورت Denormalized در بیاوریم که کوئری زدن و لود کردن آن ساده تر باشد. در اینجا استفاده از CQRS کار را بسیار راحت می کند و همچنین باعث می شود که Complexity هنگام لود کردن دیتا جدا شده و به حداقل برسد. هنگامی که خواندن و نوشتن دیتا روی دیتابیس از مسیر های جدا صورت میگیرد ( مثلا با Store Procedure دیتا خوانده شود، و یا از روی یک Cache مانند Redis خوانده شود) استفاده از CQRS باعث Separation of Concerns خواهد شد و میتوان سرویس های مربوط به هرکدام را جداگانه توسعه داد.هنگامی که Read Model پروژه ساده است و پیچیدگی خاصی ندارد، و یا از دیتابیس های جداگانه برای Query و Command استفاده نمی کنیم و یا میسر خواندن و نوشتن روی دیتابیس جدا نیست ( به طور مثال از یک جدول هم عملیات خواندن و هم نوشتن انجام میشود) استفاده از CQRS مزیت خاصی را به همراه ندارد و پروژه بدون استفاده از آن نیز کار خود را انجام خواهد داد.هنگامی که مدل خواندن و نوشتن در دیتابیس جداست، CQRS میتواند پیچیدگی Command و Query را کاهش دهدمزایای استفاده از CQRSباعث می شود که اپلیکیشن مقیاس پذیر باشد و توسعه آن راحت باشد. در آینده اگر لازم باشد که تمهیداتی برای Query گرفتن از دیتابیس صورت بگیرد، می توان به راحتی این تغییرات را در سطح کد انجام داد.در اکثر مواقع، عمل خواندن داده بیش از سایر عملیات انجام میشود. CQRS باعث می شود که برای هر عمل Read بتوانیم تمهیدات لازم را انجام دهیم. مثلا ممکن است که در یک سناریو نیاز باشد که داده از  یک SP در دیتابیس خوانده شود و در سناریو دیگر از روی Cache در Redis. پس مهم است که هر عمل Read به طور مجزا رسیدگی شود که در CQRS این کار به سادگی امکان پذیر استباعث بهبود پرفرمنس میشود. حتی اگر دیتابیس های Read و Write یکی باشند ، با استفاده از CQRS می توان که روی هر کدام به صورت جداگانه Optimization انجام داد. مثلا هنگام استفاده از EF Core می توان برای Read Model داده از Second Level Cache برای cache کردن کوئری ها استفاده کرد. و یا میتوانیم از Dapper برای کوئری زدن به دیتابیس استفاده کنیم.باعث ساده شدن کار با اپلیکیشن می شود. عملیات Command و Query هرکدام نیاز های خاص خود را دارند و اگر بخواهیم که از یک مدل برای هر دو استفاده کنیم، کار را سخت کرده ایم و به مدلی رسیدیم که هیچ کدام از این دو را نمیتواند به خوبی پوشش دهد. CQRS باعث می شود که برای هر کدام از Command ها و Query ها یک مدل جدا داشته باشیم و هرکدام را جدا از دیگری توسعه دهیم. پس CQRS باعث ایجاد Single Responsibility در سطح معماری پروژه می شود که هر مدل جدا از دیگر مدل ها فقط یک وظیفه دارد.نتیجه گیریدر قسمت اول، به طور خلاصه به CQRS پرداختیم. آن را با CQS مقایسه کردیم و مزایای استفاده از CQRS اشاره کردیم. در قسمت بعدی به بررسی یکی از ابزار های بسیار محبوب و لازم برای پیاده سازی CQRS، یعنی MediatR می پردازیم و تکنیک های استفاده از این ابزار را بررسی میکنیم.مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Sun, 10 Jan 2021 11:33:06 +0330</pubDate>
            </item>
                    <item>
                <title>بررسی و پیاده سازی Logging حرفه ای در Asp Net Core با استفاده از Serilog و ElasticSearch و Kibana</title>
                <link>https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D9%88-%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-centerilzed-logging-system-%D8%AF%D8%B1-asp-net-core-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-serilog-%D9%88-elasticsearch-%D9%88-kibana-jbevdsvooran</link>
                <description>در این مقاله به یکی از مهم ترین و در عین حال پرچالش ترین مسئله در معماری میکروسرویس، مانیتور و مدیریت میکروسرویس ها و ثبت رویدادها می پردازیمتوجه کنید که در این مقاله از Gist استفاده شده است و ممکن است لود شدن قسمت مربوط به کد ها کمی زمانبر باشددر دنیای Microservice یکی از چالش هایی که اغلب تیم ها با آن رو به می شوند مسئله Logging  هست و اینکه چگونه باید با Log ها در هر میکروسرویس برخورد کنیم. دو روش کلی برای لاگ کردن رویداد ها در میکروسرویس ها وجود دارد1- برای هر میکروسرویسی یک سیستم Logging جدا طراحی کنیم، این روش چند مشکل به همراه دارد: در هر میکروسرویس ممکن است که مجبور به نوشتن کد های تکراری شویمردیابی و Trace کردن لاگ ها به راحتی ممکن نیست و در صورت بروز خطا پیدا کردن Source خطا کار سختی خواهد بود لاگ ها یک فرمت واحد و یکسانی نخواهند داشت و اطلاعاتی که توسط هر میکروسرویس ثبت میشوند یکسان نخواهد بود2- یک سیستم مرکزی برای Log کردن رویداد ها وجود داشته باشد. در اینصورت مشکلات روش قبلی را نخواهیم داشت ولی طراحی چنین سیستمی چالش های خاص خودش را به همراه دارد. خوشبختانه ابزار هایی برای Centralized Logging وجود دارند که کار ما را بعنوان برنامه نویس  بسیار آسان میکنند که مجبور به اختراع چرخ از ابتدا نباشیم. در ادامه با یکی از ابزار های بسیار خوب در این زمینه آشنا میشویم.آشنایی با Serilog. محبوب، آسان و جامع!فکر میکنم که اکثر برنامه نویسان دات نت در مورد Serilog شنیده باشند. Serilog یک کتابخانه مخصوص logging در دات نت هست که برای structured logging طراحی و بهینه سازی شده است. در زمان نوشتن این مقاله، پکیج Serilog بیش از 172 میلیون دانلود داشته است!استفاده از Serilog بسیار سرراست و آسان است بدون کوچک ترین مشکلی میتوانیم آن را با لاگر پیش فرض در Asp Net Core ادغام کنیم. آمار دانلود و نصب پکیج Serilog بر اساس سایت Nuget Trendsیکی از ویژگی های خوب Serilog که موجب محبوبیت آن شده است، وجود Provider های مختلف( یا به اصطلاح  سازندگان آن Sink ) هست. به کمک Sink ها میتوانیم Serilog را برای استفاده در Provider های مختلف تنظیم کنیم و به نوعی بتوانیم از امکانات Serilog در هر محیطی استفاده کنیم.به طور مثال، یکی از Sink های بسیار محبوب و کاربردی Console می باشد که به کمک آن میتوانیم لاگ هایی را که در کنسول ثبت میشوند سازماندهی و مرتب کنیم.لاگ کردن رویداد ها در کنسول با استفاده از Serilog Console Sinkآشنایی با مفهوم Structured Loggingدر Structured Logging از یک فرمت واحد  برای ثبت لاگ ها استفاده میکنیم. با این کار خوانایی لاگ ها افزایش پیدا میکند، اطلاعات بیشتری را توسط لاگ ها می توانیم به دست بیاوریم و همچنین ثبت لاگ ها در محیط های مختلف کار آسان تری خواهد بود.در Serilog به طور پیشفرض فرمتدهی بسیار خوبی را برای لاگ ها در نظر می گیرد. به طور مثال در Console ما میتوانیم زمان وقوع لاگ، سطح لاگ، اطلاعات ثبت شده در لاگ و همچنین زمان صرف شده برای اجرای یک Request را مشاهده کنیم. برای آشنایی بیشتر با فرمت دهی لاگ ها در Serilog میتوانید به این مقاله در گیت هاب Serilog مراجعه کنید.نمونه ای از فرمت دهی لاگ ها در Serilogغنی سازی (Log Enrichment) در Serilogیکی از ویژگی های بسیار خوب و بسیار کاربردی در Serilog پیوست کردن اطلاعات اضافی به هر لاگ هنگام ثبت  آن است. به طور مثال می توانیم که Thread Id ای که هر Thread برای اجزای یک Request دارد را به همراه لاگ های مربوط به آن Request ثبت کنیم. یا محیط اجرای برنامه را به همراه لاگ آن ثبت کنیم. با استفاده از این قابلیت علاوه بر اینکه فرمت مناسب هر لاگ حفظ میشود، با مراجعه به لاگ ها میتوانیم اطلاعات مفیدی را برای هرکدام از آنها مشاهده کنیم.برای غنی سازی لاگ ها پکیج های بسیار زیادی وجود دارند که برای مشاهده و آشنایی با آنها میتوانید به این لینک مراجعه کنید. نمونه ای از غنی سازی اطلاعات لاگ با Process Enrichment و نمایش آن در داشبورد Kibanaنقش Serilog در Destributed Loggingهمانطور که قبلا گفته شد، در Serilog بوسیله Sink های مختلف میتوان لاگ ها را در هر محیطی ثبت کرد. یکی از ابزار های بسیار خوب برای مدیریت لاگ ها ElasticSearch هست که بوسیله Sink مخصوص میتوان آن را برای ثبت لاگ ها به وسیله Serilog تنظیم کرد که در ادامه آن را بررسی می کنیم.آشنایی مختصر با ELK Stackکلمه ELK نشان دهنده سه پروژه متن باز و محبوب ElasticSearch ، Logstash و Kibana می باشد که هر کدام را به اختصار توضیح میدهیمElasticSearch:یک Search Engine بسیار پیشرفته با قابلیت آنالیز و تبادل اطلاعات در قالب REST می باشد.Kibana:رابط کاربری و داشبورد ElasticSearch می باشد که کار با آن را بسیار آسان می کند. به وسیله Kibana میتوان به راحتی میتوان نمودار ها و چارت های گرافیکی مربط با اطلاعات ElasticSearch را مشاهده و تولید کرد و یا با تعیین فیلتر های مختلف از اطلاعات ElasticSearch گزارش گرفت.تصویری از محیط داشبورد KibanaLogstash:یک پایپ لاین برای پردازش داده ای که ممکن است توسط Provider های مختلف فراهم شود. در واقع Logstash دیتا را از Provider های مختلف دریافت کرده و با تغییر شکل آن، آن را در Store های مختلف ذخیره می کند.نصب و راه اندازی Kibana و ElasticSearch در محیط ویندوزابتدا باید از نصب Java SDK متناسب با ورژن ElasticSearch روی سیستم خود اطمینان حاصل کنید و همچنین از دسترس بودن پورت 9200 (پورتی که ElasticSearch به صورت پیشفرض از آن استفاده می کند) بر روی سیستم خود مطمئن شوید. سپس این فایل را دانلود کنید. پس از اتمام دانلود فایل elasticsearch.bat در پوشه bin را به صورت administrator اجرا نمایید. اگر مشکلی وجود نداشته باشد ElasticSearch به طور خودکار نصب خواهد شد.ممکن است پس از نصب نیاز باشد که سیستم خود را ریستارت نمایید.سپس به آدرس http://localhost:9200 روی سیستم خود مراجعه کنید، اگر نصب موفقیت آمیز بوده باشد یک فایل JSON حاوی اطلاعات ElasticSearch در مرورگر نمایش داده خواهد شد.برای اطلاعات بیشتر و سایر تنظیمات نصب ElasticSearch میتوانید به این لینک مراجعه کنید. اطلاعات ElasticSearch پس از نصبنصب Kibana نیز روال مشابه ای دارد. ابتدا از دسترس بودن پورت 5601 (پورتی که Kibana به صورت پیش فرض از آن استفاده میکند) اطمینان حاصل کنید. سپس این فایل را دانلود کنید. پس از اتمام دانلود به پوشه bin رفته و فایل kibana.bat را به صورت administrator اجرا نمایید. اگر مشکلی وجود نداشته باشد، نصب Kibana به صورت خودکار انجام خواهد شد. پس از نصب ممکن است که نیاز باشد سیستم خود را ریستارت نمایید. سپس آدرس http://localhost:5601 را در مرورگر خود وارد نمایید اگر نصب موفقیت آمیز بوده باشد، داشبورد Kibana در مرورگر نمایش داده خواهد شد.برای اطلاعات بیشتر درباره نصب و همچنین تنظیمات نصب Kibana در ویندوز میتوانید به این لینک مراجعه کنید.تصویر از صفحه اصلی داشبورد Kibanaنصب و راه اندازی ElasticSearch و Kibana در Dockerطبق تجربه شخصی، نصب و راه اندازی ElasticSearch و Kibana در محیط ویندوز کار بی دردسری نیست. ممکن است که این دو روی ویندوز نصب نشوند و یا با تنظیمات اشتباه، نتوان آنها را اجرا کرد. ساده ترین و در عین حال بی دردسر ترین راه برای راه اندازی ElasticSearch و Kibana استفاده از Docker می باشد. ما در این مقاله به جزییات داکر نمی پردازیم. برای اطلاعات بیشتر میتوانید به سایت رسمی داکر مراجعه کنید. همچنین جادی عزیز در چند ویدیو داکر را به شکل گویا و روان توضیح داده است که میتوانید آنها را در این لینک مشاهده کنید.معرفی Docker Composeبا استفاده از Docker Compose میتوانیم چند کانتینر و ترتیب ایجاد آنها، تعریف تنظیمات برای نحوه ایجاد هر کدام از کانتینر ها را در یک فایل YAML داشته باشیم. سپس با یک خط دستور docker-compose تمامی سرویس های مربوط به اپلیکیشن تنظیم و شروع به کار خواهند کرد. Docker Compose ابزاری فوق العاده برای توسعه، تست و مرحله بندی ساخت محیط توسعه می باشد. برای اطلاعات بیشتر می توانید به این لینک مراجعه کنید.استفاده از Docker Compose برا تنظیم Kibana و  ElasticSearch برای تنظیم Kibana و ElasticSearch می توانیم از کد docker compose زیر استفاده کنیم  https://gist.github.com/babaktaremi/4708726d005dc94fadb0080bbce3cc19 توجه داشته باشید که برای دانلود ایمیج های داکر نیاز به نرم افزارهای رفع تحریم هستید چرا که سایت داکر IP های مربوط به ایران را مسدود می کند. پس از تعریف ورژن و سرویس ها در خط 4 داکر Image مربوط به ElasticSearch را دانلود میکند. -در خط 5 یک نام برای کانتینری که قرار است از روی این ایمیج ساخته شود انتخاب میکنیم.-از خط 6 تا 11 مربوط به تنظیمات و کانفیگ نصب ElasticSearch می باشد. node name و cluster name در واقع نام node مربوطه به ElasticSearch را تنظیم می کند که در صورتی که نیاز به Cluster کردن ElasticSearch با چند node باشد بتوانیم از نام آن استفاده کنیم. bootstrap memory lock مربوط به تنظیمات JVM و مدیریت GC در آن می باشد. برای بهبود عملکرد ElasticSearch توصیه می شود که مقدار آن را true قرار دهید. discovery type تعیین می کند که آیا برای ElasticSearch یک node وجود دارد یا بیشتر. در صورتی که این مقدار روی single-node قرار داده شود ElasticSearch به دنبال سایر node ها نمی گردد که در صورتی که فقط یک node برای ElasticSearch وجود داشته باشد موجب افزایش سرعت آن میشود. در خط 19 پورت پیش فرض ElasticSearch به یک پورت از سیستم مپ می شود.-از خط 22 تنظیمات مربوط به Kibana شروع می شود که شباهت زیادی به تنظیمات ElasticSearch دارد. ابتدا ایمیج مربوط به Kibana دانلود می شود. سپس در خط 28 و 29 آدرس مربوط به ElasticSearch برای استفاده Kibana تنظیم می شود.برای اطلاعات بیشتر در مورد فایل docker compose مربوط به ElasticSearch و Kibana می توانید به این لینک مراجعه کنید.در نهایت در مسیر فایل docker compose ایجاد شده دستور زیر را در command prompt اجرا میکنیمdocker-compose up -dبسته به سرعت اینترنت دانلود و نصب ایمیج های مربوط به Kibana و ElasticSearch ممکن است مقداری طول بکشد. پس از اتمام دانلود و ایجاد کانتینر های مربوطه، ElasticSearch  و Kibana در دسترس خواهند بود.اجرای کانتینر های مربوط به ElasticSearch و Kibanaاجرای ElasticSearch در Dockerایجاد پروژه ASP Net Coreبرای استفاده بهتر از داشبورد Kibana و همچنین استفاده از ElasticSearch در Serilog یک پروژه نمونه ایجاد میکنیم.پس از ایجاد پروژه، از طریق manage nuget package در ویژوال استودیو، پکیج Serilog.AspNetCore را نصب میکنیم.Package Manager:
Install-Package Serilog.AspNetCore -Version 3.4.0توصیه میکنم که پکیج Serilog.Exceptions را هم در کنار Serilog نصب کنید. این پکیج در واقع یک Extension روی Serilog به همراه دارد که با Enrich کردن Serilog باعث ایجاد فرمت مناسب هنگام لاگ کردن  Exception ها خواهد شد که میتوانیم از آن هنگام جست و جو میان لاگ ها در Kibana استفاده کنیم.Package Manager:
Install-Package Serilog.Exceptions -Version 6.0.0همونطور که قبلا اشاره کردم یکی از نقاط قوت Serilog وجود Sink های مختلف هست. برای کانفیگ کردن Serilog با ElasticSearch تنها کافیست که پکیج Serilog.Sinks.Elasticsearch را روی پروژه نصب کنیم.Package Manager:
Install-Package Serilog.Sinks.ElasticSearch -Version 8.4.1سپس url مربوط به ElasticSearch که در داکر کانفیگ کرده ایم را در appsettings.json قرار میدهیم.برای جلوگیری از نوشتن کد تکراری، در solution پروژه یک Shared Project ایجاد میکنیم( البته میتوانیم از Class Library هم استفاده کنیم. Shared Project ها گزینه مناسبی برای Helper Method ها، Extension Method ها و کد های مشترک بین چند پروژه هستند. در این لینک میتوانید درباره تفاوت Shared Project و Class Library ها بیشتر بخوانید) روی Solution راست کلیک کرده و در تب Add گزینه New Project را انتخاب میکنیم.سپس گزینه Shared Project را انتخاب کرده و یک اسم برای آن انتخاب میکنیم. من اسم Common را انتخاب میکنم.تنظیم Serilogدر پروژه Common یک کلاس جدید با نام LoggingConfiguration ایجاد میکنیم. تمام تنظیمات مربوط به Serilog را در این کلاس انجام خواهیم داد.ابتدا یک Action  با پارامتر های HostBuilderContext و LoggerConfiguration ایجاد میکنیم و تنظیمات مربوط به Serilog را در آن اتخاذ میکنیم. https://gist.github.com/babaktaremi/1cfbf5a468bc424a0e6039da0c7d5f60 -در خط 7 و 8 با استفاده از HostBuilderContext به یک نمونه از Hosting Environment دسترسی پیدا میکنیم. سپس Logger را با اطلاعاتی همچون نام اپلیکیشن و Environment Name غنی میکنیم. وجود این اطلاعات در آینده برای Trace کردن لاگ ها کمک کننده خواهند بود. همچنین در اینجا میتوانیم از پکیج Serilog.Exception برای فرمت دهی مناسب به Exception Message ها نیز استفاده کنیم. همچنین با استفاده از پکیج Serilog.Enrichers.Process میتوانیم لاگ ها را با آیدی و نام Process غنی کنیم.-در خط 18 تنظیمات مربوط به ElasticSearch  شروع می شود. با استفاده از Configuration در HostBuilderConext مقدارر url تنظیم شده برای ElasticSearch را بدست می آوریم. تنظیمات مهم مربوط به ElasticSearch بصورت زیر خواهد بود:-در خط 24 AutoRegisterTemplate تعیین میکند که در ElasticSearch یک Index برای لاگ های Serilog ایجاد شود.-در خط 25 AutoRegisterTemplateVersion ورژن مربوط به Template برای استفاده در ElasticSearch را تعیین میکند.-در خط 26 IndexFormat  مهم ترین بخش در تنظیمات میباشد. تعیین میکند که Index برای ثبت لاگ ها در ElasticSearch چگونه باشد. بعدا از این Index برای جست و جو و فیلتر کردن لاگ ها در Kibana استفاده میکنیم. دقت داشته باشید که Index Format باید تماما بصورت Lower Case نوشته شود.-در خط 27 MinimumLogEventLevel تعیین میکند که کمترین سطح لاگ برای ثبت در ElasticSearch چقدر باشد.در مرحله بعد در فایل Program.cs اکشن ConfigureLogger به همراه Serilog را بصورت زیر تنظیم میکنیم.برای تنظیم Kibana ابتدا احتیاج به ثبت چند لاگ داریم پس برنامه را اجرا میکنیم. سپس به داشبورد Kibana  بر میگردیم.تنظیم داشبورد Kibana برای مشاهده و مدیریت لاگ هادر صفحه Home گزینه Connect to your ElasticSearch index را انتخاب میکنیم.سپس در صفحه جدید گزینه Create index Pattern را انتخاب میکنیم.سپس در قسمت Index pattern name مقدار indexFormat ای که در Serilog تنظیم کرده ایم را وارد میکنیم( که در نمونه کد نام آن را mywebapilog قرار داده ایم). اگر Index ای در ElasticSearch ثبت شده باشد در این قسمت نشان داده خواهد شدسپس برای Time field گزینه @timestamp را انتخاب کرده و Create index pattern را انتخاب میکنیم.برای مشاهده لاگ های ثبت شده، از منوی کنار گزینه Discover را انتخاب میکنیمدر صفحه جدید مجددا از منوی کنار index pattern مورد نظرمان را انتخاب میکنیم.قبلا در کد و تنظیمات Serilog نام index رو mywebapilog قرار داده ایم.با استفاده از تنظیمات نوار بالایی میتوانیم لاگ های ثبت شده را فیلتر کنیم. مثلا میتوانیم لاگ های ثبت شده طی یک سال گذشته را فیلتر و مشاهده کنیم.در نهایت لاگ های اپلیکیشن ما به تفیکیک زمانی قابل مشاهده هستند.حل مشکل فیلد های بدون Index Pattern در Kibanaممکن است کنار یک سری از فیلد ها علامت warning وجود داشته باشداین اخطار بخاطر این است که ElasticSearch یک سری فیلد جدید پیدا کرده که در index pattern وجود نداشته است. برای حل این مشکل به index pattern برمیگردیم و یکبار آن را Refresh میکنیم.تاثیر Exception Formatting در مشاهده و ثبت خطاهابرای اینکه با Exception Formatting بیشتر آشنا شویم یک خطای تستی در پروژه ASP Net Core ایجاد میکنیم و سپس Index Pattern را مجددا Refresh  میکنیم.ملاحظه میکنیم که در فرمت Json با یک خطای Structured رو به رو هستیم که به خوبی source خطا و همچنین Request Id ای که باعث خطا شده رو نشون میده و به طبع تمام اپلیکیشن های دیگر در سیستم که از این تنظیمات استفاده کنند هم همین فرمت خطا را خواهند داشت.ردیابی لاگ ها با استفاده از Correlation IDهمواره ردیابی منبع خطا و یافتن اینکه کدام یک از میکرو سرویس ها مسبب این خطا بوده اند کاری دشوار است. برای حل این مسئله می توان به ازای هر ریکوئست، میکروسرویس هدف یک شناسه تصادفی و از جنس GUID تولید کند که وقتی درخواست از یک میکرو سرویس به میکروسرویس دیگر می رسد این شناسه را به همراه خود داشته باشد. به این صورت ردیابی لاگ ها بین میکرو سرویس ها تا رسیدن به منبع خطا یا ایجاد کننده لاگ آسان تر خواهد شد.خوشبختانه لاگر پیش فرض Asp Net Core به صورت پیش فرض دنبال Trace ID لاگ میگردد و اگر وجود داشته باشد آن را ذخیره میکند، همچنین HttpClient پیش فرض در Asp Net Core این Trace ID را به صورت خودکار تولید میکند.نتیجه گیریدر این مقاله به یکی از چالش های دنیای میکروسرویس ها، ثبت خطا و رویداد ها پرداختیم و ابزار مناسب برای آن را معرفی کردیم. ElasticSearch یک Search Engine فوق العاده هوشمند و با پرفرمنس بالاست که به بدون هیچ تردیدی میتوان از آن در پروژه های Enterprise  استفاده کرد. همچنین با استفاده از داشبورد Kibanba میتوان از تمامی امکانات ElasticSearch بدون کوچک ترین دغدغه استفاده کرد و مدیریت جامعی بر روی لاگ های سیستم های مختلف داشت.اگر به کدهای این مقاله نیاز داشتید میتوانید آن را از  گیت هاب دریافت نمایید و اگر سوال یا موردی هست، خوشحال میشوم که آن را در بخش نظرات مطرح کنید. https://github.com/babaktaremi/Logging مقالات بیشتر در دات نت زومhttps://t.me/DotNetZoom</description>
                <category>بابک طارمی</category>
                <author>بابک طارمی</author>
                <pubDate>Thu, 24 Dec 2020 19:03:36 +0330</pubDate>
            </item>
            </channel>
</rss>