مطالب
دریافت خروجی Curl از HttpClient در دات‌نت (NET.)
در این مقاله قصد دارم راجع‌به یک Extension در دات نت صحبت کنم که خیلی وقت‌ها می‌تواند بسیار مفید و نجات بخش و همینطور در زمان کارتان تاثیر زیادی بگذارد. خیلی وقت‌ها پیش آمده که داریم با یک سرویس بیرونی ارتباط برقرار میکنیم، اما هنگام فراخوانی کردن، با خطا مواجه می‌شویم و ما متوجه دلیل خطای رخ داده در آن لحظه نمی‌شویم. برای خود من بار‌ها پیش آمده که Property‌های اطلاعات ورودی برای وب سرویس را بصورت Pascal Case داده باشم، ولی سرویس بیرونی فقط بصورت Camel Case برای آن قابل قبول بوده‌است و من بعد از ساعت‌ها بررسی متوجه این موضوع می‌شدم و یا ممکن بود یک Property با مقدار نادرست ارسال می‌کردم و یا ممکن بود یک Property را اصلا ارسال نمی‌کردم و یا حتی اینکه یک Header را درست نمی‌فرستادم و کلی از این موضوعات که با آن‌ها برخورد کردیم و با صرف زمان، مشکل را حل کردیم. این Extension کار ما را برای حل این مسائل خیلی راحت می‌کند.

حالا چطور و چگونه ازش استفاده کنیم؟!

این Extension کارش این است، وقتی HttpClient ما مقدار دهی شده و آماده‌ی برای ارسال درخواست به سرویس بیرونی است، می‌توانیم قبل ارسال، آن را فراخوانی کنیم و یک خروجی Curl از درخواستی را که داریم می‌فرستیم، ببینیم. سپس خروجی Curl را در ترمینال صدا بزنیم و نتیجه را ببینیم. همینطور می‌توانیم به Postman خود Import کنیم و با داکیومنتی که داده شده، بررسی کنیم و مشکل را دقیق‌تر بررسی کنیم.

نحوه Import کردن Curl در Postman
 open the Postman -> click on the Import button -> 
select the Raw text tab -> paste the curl script here -> 
then press the Continue button -> at the end press the button import.
جای دیگری که نقش این Extension می‌تواند تاثیر گذار باشد، زمانی است که ما از نحوه‌ی فراخوانی سرویس‌های بیرونی خود که در سیستم نوشته شده، هیچ داکیومنت یا Postman Collection ای نداریم. ما با این Extension با خروجی Curl که در اختیارمان می‌گذارد، می‌توانیم Collection خود را ایجاد کنیم و در اختیار هم تیمی‌های خود قرار دهیم. می‌بینید که چقدر کارها را ساده و راحت می‌کند!

استفاده از این Extension بسیار ساده و سریع است و شما با نوشتن یک خط می‌توانید آن را فراخوانی کنید: 
آدرس Nuget Package


این Extension سه(۳) راه برای نمایش Curl دارد:

۱- چاپ در Console
httpClient.GenerateCurlInConsole(httpRequestMessage, null);
پارامتر دوم، کانفیگ هست که شما می‌توانید بنا به نیاز، آنها را تغییر دهید (پیش فرض آن null است). مثال و توضیحات کانفیگ به شرح زیر است:
httpClient.GenerateCurlInConsole(
   httpRequestMessage,
   configs =>
   {
     configs.TurnOn = true;
     configs.NeedAddDefaultHeaders = true;
     configs.EnableCodeBeautification = false;
   });
- مقدارTurnOn پیش فرض فعال است؛ درصورت غیرفعال کردن جنریتور، غیر فعال می‌شود و عمل ایجاد اسکریپت را انجام نمی‌دهد.
- با مقدارNeedAddDefaultHeaders می‌توانید مشخص کنید در صورت داشتن هدر‌های پیش فرض، در خروجی Curl اضافه شود یا خیر. پیش فرض آن فعال هست.
- مقدارEnableCodeBeautification اگر فعال باشد اسکریپت‌های چاپ شده در Console را به ازای هر HttpMethod، با رنگ متفاوتی نشان می‌دهد؛ برای خوانایی بهتر اسکریپت. بصورت پیش فرض غیر فعال است.

۲- ذخیره در فایل

httpClient.GenerateCurlInFile(httpRequestMessage, null);
پارامتر دوم کانفیگ هست که شما می‌توانید بنا به نیاز، آنها را تغییر دهید (پیش فرض آن null است).
مثال و توضیحات کانفیگ به شرح زیر است:
httpClient.GenerateCurlInFile(
   httpRequestMessage,
   configs =>
   {
    configs.Filename = "your filename";
    configs.Path = "your path";
    configs.TurnOn = true;
    configs.NeedAddDefaultHeaders = true;
   });
- مقدارFilename را اگر وارد کنید، می‌توانید نام فایلی را که ایجاد می‌شود، مشخص کنید. در صورت مقدار ندادن، پیش فرض تاریخ روز جاری را اعمال میکند. مثال: 20220910.curl
- مقدارPath را می‌توانید در صورت داشتن مسیری خاص، مشخص کنید. در غیر این صورت بصورت پیش فرض اطلاعات را در مسیر ProjectDirectory\bin\Debug\netX ذخیره می‌کند.
- مقدارTurnOn پیش فرض آن فعال است. درصورت غیرفعال کردن جنریتور غیر فعال می‌شود و عمل ایجاد اسکریپت را انجام نمی‌دهد.
- با مقدار NeedAddDefaultHeaders می‌توانید مشخص کنید در صورت داشتن هدر‌های پیش فرض، در خروجی Curl اضافه شود یا خیر. پیش فرض آن فعال هست.

۳- ذخیره در متغیر

httpClient.GenerateCurlInString(httpRequestMessage);

لینک آدرس GitHub پروژه جهت دیدن سورس پروژه و دیدن مثال‌های بیشتر و همینطور برای دیدن قابلیت‌های بیشتر این extension.

خوشحال می‌شوم اگه نظری دارید راجع به پروژه و یا مشکلی دیدید در سورس کد به من اطلاع بدهید و خیلی خوشحال می‌شوم اگر در تکمیل و پیاده سازی این پروژه مشارکت کنید و اگر این پروژه براتون جذاب و یا مفید بود استار بدهید.

مطالب
intern pool جدول نگهداری رشته‌ها در دات‌نت
کد زیر را در نظر بگیرید :
object text1 = "test";
object text2 = "test";

object num1 = 1;
object num2 = 1;

Console.WriteLine("text1 == text2 : " + (text1 == text2));
Console.WriteLine("num1 == num2 : " + (num1 == num2));

به نظر شما چه چیزی در خروجی نمایش داده میشود؟

هر چهار متغییر text1  و text2 و num1 و num2 از نوع object هستند. با اینکه مقدار text1 و text2 یکی و مقدار num1 و num2 هم یکی است، نتیجه text1==text2 برابر true است اما num1==num2 برابر false.

خطی که text2 تعریف شده است را تغییر میدهیم :
object text2 = "test".ToLower();

اینبار با این که باز مقدار text1 و text2 یکی و هر دو "test" است، اما نتیجه text1==text2 برابر false است. انتظار ما هم همین است. دو object ایجاد شده است و یکی نیستند. تنها در صورتی باید نتیجه == آنها true باشد که هر دو به یک object اشاره کنند.

اما چرا در کد اولی اینگونه نبود؟

دلیل این کار برمیگردد به رفتار دات‌نت نسبت به رشتههایی که به صورت صریح در برنامه تعریف میشوند. CLR یک جدول برای ذخیره رشتهها به نام intern pool برای برنامه میسازد. هر رشتهای تعریف میشود، اگر در intern pool رشتهای با همان مقدار وجود نداشته باشد، یک رشته جدید ایجاد و به جدول اضافه میشود، و اگر موجود باشد متغییر جدید فقط به آن اشاره میکند. در واقع اگر 100 جای برنامه حتی در کلاسهای مختلف، رشتههایی با مقادیر یکسان وجود داشته باشند، برای همه آنها یک نمونه وجود دارد.

بنابراین text1 و text2 در کد اولی واقعا یکی هستند و یک نمونه برای آنها ایجاد شده است.

البته چند نکته در اینجا هست :
اگر text1 و text2 به صورت string تعریف شوند، نتیجه text1==text2 در هر دو حالت فوق برابر true است. چون عملگر == در کلاس string یکبار دیگر overload شده است:
public sealed class String : ...
{
    ...
    public static bool operator ==(string a, string b)
    {
      return string.Equals(a, b);
    }
   ...
}

این که کدام یک از overload‌ها اجرا شوند (کلاس پایه، کلاس اصلی، ...) به نوع دو متغییر اطراف == بستگی دارد. مثلا در کد زیر :
string text1 = "test";
string text2 = "test".ToLower();

Console.WriteLine("text1 == text2 (string) : " + (text1 == text2));
Console.WriteLine("text1 == text2 (object) : " + ((object)text1 == (object)text2));

اولین نتیجه true و دومی false است. چون در اولی عملگر == تعریف شده در کلاس string مورد استفاده قرار می‏‏گیرد اما در دومی عملگر == تعریف شده در کلاس object.

اگر دقت نشود این رفتار مشکلزا میشود. مثلا حالتی را در نظر بگیرید که text1 ورودی کاربر است و text2 از بانک اطلاعاتی خوانده شده است و با اینکه مقادیر یکسان دارند نتیجه == آنها false است. اگر تعریف عملگرها در کلاس object به صورت virtual بود و در کلاس‌های دیگر override می‌شد، این تغییر نوع‌ها تاثیری نداشت. اما عملگرها به صورت static تعریف می‌شوند و امکان override شدن ندارند. به همین خاطر کلاس object متدی به اسم Equals در اختیار گذاشته که کلاس‌ها آنرا override می‌کنند و معمولا از این متد برای سنجش برابری دو کلاس استفاده می‌شود :
object text1 = "test";
object text2 = "test".ToLower();

Console.WriteLine("text1 Equals text2 : " + text1.Equals(text2));
Console.WriteLine("text1 Equals text2 : " + object.Equals(text1, text2));

البته یادآور می‌شوم که فقط رشته‌هایی که به صورت صریح در برنامه تعریف شده‌اند، در intern pool قرار می‌گیرند و این فهرست شامل رشته‌هایی که از فایل یا بانک اطلاعاتی خوانده می‌شوند یا در برنامه تولید می‌شوند، نیست. این کار منطقی است وگرنه حافظه زیادی مصرف خواهد شد.

با استفاده از متد string.Intern می‌توان یک رشته را که در intern pool وجود ندارد، به فهرست آن افزود. اگر رشته در intern pool وجود داشته باشد، reference آنرا بر می‌گرداند در غیر اینصورت یک reference به رشته جدید به intern pool اضافه می‌کند و آنرا برمی‌گرداند.

یک مورد استفاده آن هنگام lock روی رشته‌هاست. برای مثال در کد زیر DeviceId یک رشته است که از بانک اطلاعاتی خوانده می‌شود و باعث می‌شود که چند job همزمان به یک دستگاه وصل نشوند :
lock (job.DeviceId)
{
    job.Execute();
}

اگر یک job با DeviceId برابر COM1 در حال اجرا باشد، این lock جلوی اجرای همزمان job دیگری با همین DeviceId را نمی‌گیرد. زیرا هر چند مقدار DeviceId دو job یکی است ولی به یک نمونه اشاره نمی‌کنند.

می‌توان lock را اینگونه اصلاح کرد :
lock (string.Intern(job.DeviceId))
{
    job.Execute();
}

اشتراک‌ها
استفاده از کتابخانه Puppeteer-Sharp برای خودکارسازی مرورگر در دات‌نت
Puppeteer-Sharp کتابخانه ای برای محیط دات‌نت است که به توسعه‌دهندگان این امکان را می‌دهد تا از Puppeteer، ابزار خودکارسازی مرورگر برای Node.js، در پروژه‌های خود استفاده کنند. این کتابخانه به‌ویژه برای رندر کردن صفحات وب، استخراج داده‌ها، و انجام تست‌های خودکار کاربرد دارد. با ویژگی‌هایی مانند پشتیبانی از JavaScript، امکان گرفتن اسکرین‌شات و تبدیل به PDF، و قابلیت مدیریت جلسات و کوکی‌ها، Puppeteer-Sharp ابزاری قدرتمند برای خودکارسازی و تست وب به شمار می‌رود.
استفاده از کتابخانه Puppeteer-Sharp برای خودکارسازی مرورگر در دات‌نت
اشتراک‌ها
اضافه شدن قابلیتی به ویندوز 11 به نام «Dev Drive» جهت بالابردن سرعت کار با پروژه‌های بزرگ برنامه نویسی

Dev Drive definitely speeds up the build time and that leads into faster software development. I got approx 25% improvement into build times simply by copying my source code folders into dev drive. 

اضافه شدن قابلیتی به ویندوز 11 به نام «Dev Drive» جهت بالابردن سرعت کار با پروژه‌های بزرگ برنامه نویسی
اشتراک‌ها
دوره توسعه Web API با دات نت 8 در 2 ساعت

Web API Development in .NET 8 in 2 Hours | ASP.NET CORE | RESTFUL API

00:00:00 Introduction
00:03:06 What is Web API & Why create Web API
00:10:21 How Web API Works in Theory
00:14:49 How Web API Works  (Demo with Minimal APIs)
00:27:32 What is a Web API Framework
00:33:27 ASP.NET Core Middleware Pipeline
00:37:34 Web API Controller
00:42:25 Routing in Web API
00:51:17 Model Binding
01:01:06 Model Validation with DataAnnatation
01:08:07 Model Validation with ValidationAttribute
01:15:10 Web API Return Types
01:21:30 In Mememory Repository
01:25:01 Model Validation with Action Filter
01:35:30 Read Endpoint
01:36:55 Create Endpoint
01:46:45 Validating Create Endpoint with ActionFilter
01:51:23 Update Endpoint
01:59:48 Exception Hanlding with Exception Filter
02:05:48 Delete Endpoint
 

دوره توسعه Web API با دات نت 8 در 2 ساعت
مطالب
واژه‌های کلیدی جدید and، or و not در C# 9.0
یکی از ویژگی‌های زبان VB، شباهت بیش از اندازه‌ی آن به زبان انگلیسی است. برای مثال در این زبان با استفاده از not و and:
If Not a And b Then
  ...
Else
  ...
EndIf
می‌توان if‌های خواناتری را نسبت به #C ایجاد کرد:
if(!(a) && b)
{
...
}
else
{
}
در ادامه خواهیم دید که چگونه C# 9.0، این آرزوی دیرین را برآورده می‌کند! البته مایکروسافت در جای دیگری هم عنوان کرده‌است که زبان VB را دیگر پیگیری نمی‌کند و تغییر خاصی را در آن شاهد نخواهید بود. شاید به همین دلیل و جذب برنامه نویس‌های VB به #C، یک چنین تغییراتی رخ داده‌اند!


معرفی واژه‌ی کلیدی جدید not در C# 9.0

در ابتدا اینترفیس نمونه‌ای را به همراه دو کلاس مشتق شده‌ی از آن درنظر بگیرید:
public interface ICommand
{
}

public class Command1 : ICommand
{
}

public class Command2 : ICommand
{
}
اکنون اگر وهله‌ای از Command1 را ایجاد کرده و بخواهیم بررسی کنیم که آیا از نوع کلاس Command2 هست یا خیر، با استفاده از pattern matching و واژه‌ی کلیدی if می‌توان به صورت زیر عمل کرد:
ICommand command = new Command1();
if (!(command is Command2))
{

}
در C# 9.0، برای خواناتر کردن یک چنین بررسی‌هایی، می‌توان از pattern matching بهبود یافته‌ی آن و واژه‌ی کلیدی جدید not نیز استفاده کرد:
if (command is not Command2)
{

}


معرفی واژه‌های کلیدی جدید and و or در C# 9.0

واژه‌های کلیدی جدید and و or نیز درک و نوشتن عبارات pattern matching را بسیار ساده می‌کنند. برای نمونه قطعه کد متداول زیر را درنظر بگیرید:
if ((command is ICommand) && !(command is Command2))
{

}
اکنون در C# 9.0 با استفاده از واژه‌های کلیدی جدید and، or و not، می‌توان قطعه کد فوق را بسیار ساده کرد:
if (command is ICommand and not Command2)
{

}
نه تنها این قطعه کد ساده‌تر شده‌است، بلکه خوانایی آن افزایش یافته‌است و مانند یک سطر نوشته شده‌ی به زبان انگلیسی به نظر می‌رسد. همچنین در این حالت نیازی هم به تکرار command، در هر بار مقایسه نیست.
و یا حتی در اینجا در صورت نیاز می‌توان از واژه‌ی کلیدی جدید or نیز استفاده کرد:
if (command is Command1 or Command2)
{

}


امکان اعمال واژه‌های کلیدی جدید and، or و not به سایر نوع‌ها نیز وجود دارند

تا اینجا مثال‌هایی را که بررسی کردیم، در مورد بررسی نوع اشیاء بود. اما می‌توان این واژه‌های کلیدی جدید در C# 9.0 را به هر نوع ممکنی نیز اعمال کرد. برای نمونه، مثال ساده‌ی زیر را که در مورد بررسی اعداد است، درنظر بگیرید:
var number = new Random().Next(1, 10);
if (number > 2 && number < 8)
{

}
اکنون در C# 9.0 و با استفاده از امکانات جدید pattern matching آن می‌توان شرط متداول فوق را به صورت زیر ساده کرد:
if (number is > 2 and < 8)
{

}
در اینجا تنها یکبار نیاز به ذکر number است و از واژه‌های کلیدی is و and استفاده شده‌است.

یک مثال دیگر: متد زیر را در نظربگیرید که با استفاده از && و || متداول #C نوشته شده‌است:
public static bool IsLetterOrSeparator(char c) =>
   (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '.' || c == ',';
روش ارائه‌ی C# 9.0 ای آن به صورت زیر است:
public static bool IsLetterOrSeparator(char c) =>
   c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';


امکان اعمال واژه‌های کلیدی جدید and، or و not به switchها نیز وجود دارد

برای نمونه قطعه کد if/else دار متداول زیر را درنظر بگیرید که قابلیت تبدیل به یک سوئیچ را نیز دارد:
 var number = new Random().Next(1, 10);
 if (number <= 0)
 {
     Console.WriteLine("Less than or equal to 0");
 }
 else if (number > 0 && number <= 10)
 {
     Console.WriteLine("More than 0 but less than or equal to 10");
 }
 else
 {
     Console.WriteLine("More than 10");
 }
اگر بخواهیم همین قطعه کد را به کمک واژه‌های کلیدی جدید C# 9.0 و pattern matching بهبود یافته‌ی آن تبدیل به یک سوئیچ کنیم، به قطعه کد زیر خواهیم رسید:
// C#9.0
 switch (number)
 {
     case <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case > 0 and <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }
تا پیش از C# 7.0، سوئیچ‌های #C امکان بررسی باز‌ه‌ای از مقادیر را نداشتند. از آن زمان با معرفی pattern matching، چنین محدودیتی برطرف شد و اکنون می‌توان syntax قدیمی آن‌را توسط C# 9.0، بسیار خلاصه‌تر کرد. در ذیل، معادل قطعه کد فوق را بر اساس امکانات C# 7.0 مشاهده می‌کنید که خوانایی کمتری را داشته و حجم کد نویسی بیشتری را دارد:
// C#7.0
 switch (number)
 {
     case int value when value <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case int value when value > 0 && value <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }

و یا حتی می‌توان سوئیچ C# 9.0 را توسط switch expression بهبود یافته‌ی C# 8.0 نیز به شکل زیر بازنویسی کرد:
 var message = number switch
 {
     <= 0 => "Less than or equal to 0",
     > 0 and <= 10 => "More than 0 but less than or equal to 10",
     _ => "More than 10"
 };


انواع pattern matching‌های اضافه شده‌ی به C# 9.0

در این مطلب سعی شد مفاهیم pattern matching اضافه شده‌ی به C# 9.0، ذیل عنوان واژه‌های کلیدی جدید آن بحث شوند؛ اما هر کدام دارای نام‌های خاصی هم هستند:
الف) relational patterns: امکان استفاده‌ی از <, >, <= and >= را در الگوها میسر می‌کنند. مانند نمونه‌های سوئیچی که نوشته شد.
ب) logical patterns: امکان استفاده‌ی از واژه‌های کلیدی and، or و not را در الگوها ممکن می‌کنند.
ج) not pattern: امکان استفاده‌ی از واژه‌ی کلیدی not را در عبارات if میسر می‌کند.
د) Simple type pattern: در مثال‌های زیر، پس از انطباق با یک الگو، کاری با متغیر یا شیء مرتبط نداریم. در نگارش‌های قبلی برای صرفنظر کردن از آن، ذکر _ ضروری بود؛ اما در C#9.0 می‌توان آن‌را نیز ذکر نکرد:
private static int GetDiscount(Product p) => p switch
        {
            Food => 0, // Food _ => 0 before C# 9
            Book b => 75, // Book b _ => 75 before C# 9
            _ => 25
        };