نظرات مطالب
C# 6 - Null-conditional operators
یک نکته‌ی تکمیلی: بهبود Null Coalescing Assignment در C# 8.0
ساده سازی انتساب مقداری به یک متغیر، اگر نال باشد:
- روش انجام اینکار تا پیش از C# 8.0
if (variable == null)
{
   variable = expression; // C# 1..7
}
- در C# 8.0
variable ??= expression; // C# 8
مطالب
طراحی شیء گرا: OO Design Heuristics - قسمت اول
هدف از طراحی چیست؟

ما طراحی می‌کنیم تا علاوه بر نیاز‌های عملیاتی، به نیاز‌های غیر عملیاتی (Non Functional Requirements) نیز فکر کنیم؛ در حالیکه در زمان برنامه نویسی صرفا به Functionality فکر می‌کنیم.

کتاب Object Oriented Design Heuristics اولین کتاب در زمینه طراحی و توسعه شیء گرا می‌باشد. خواندن آن برای برنامه نویسان در هر رده ای که هستند، مفید خواهد بود و میتوانند از این Heuristicها (قواعد شهودی) به عنوان ابزاری برای تبدیل شدن به یک توسعه دهنده برتر، استفاده کنند.

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

پیشنهاد می‌کنم حداقل برای اینکه ادبیات فنی خود را سامان ببخشید و با ادبیات یکسانی باهم صحبت کنیم، این کتاب را مطالعه کنید.

Introduction to Classes and Objects

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

این مفاهیم را با یک مثال واقعی، بهتر می‌توان شرح داد. یک اتاق پر از جمعیت را درنظر بگیرید؛ اگر شما می‌پرسیدید «چه تعداد از حاضرین در این اتاق می‌توانند یک ساعت زنگدار(alarm clock ) را با در دست داشتن تمام قطعات آن، بسازند؟» در بهترین حالت یک یا دو نفر تمایل داشتند دست خود را بالا ببرند. اگر در همین اتاق می‌پرسیدید، «چه تعداد از حاضرین در این اتاق می‌توانند یک ساعت زنگدار را برای ساعت 9 صبح تنظیم کنند؟» بدون شک بیشتر جمعیت تمایل داشتند دست خود را بالا ببرند.

آیا نامعقول نیست که این تعداد جمعیت زیاد، ادعا دارند که میتوانند از ساعت زنگدار استفاده کنند، درحالیکه حتی نمی‌توانند یک ساعت زنگدار بسازند؟ پاسخ بی درنگ برای این سوال «البته که نه! سوال شما نامعقول است» می‌باشد.

در دنیای واقعی خیلی چیزها هستند که ما میتوانیم از آنها استفاده کنیم، بدون آنکه دانشی درباره پیاده سازی آنها داشته باشیم؛ مانند: یخچال‌ها، اتومبیل‌ها، دستگاه‌های فتوکپی، کامپیوترها و غیره. چون آنها برای استفاده شدن از طریق واسط عمومی خودشان، تعریف و طراحی شده‌اند. لذا حتی بدون داشتن دانشی از پیاده سازی آنها، استفاده از آنها آسان می‌باشد. این واسط عمومی وابسته به دستگاه مورد نظر است. اما جزئیات پیاده سازی دستگاه را از دید کاربرانش پنهان میکند. این استراتژی طراحی، چیزی است که به سازنده اجازه می‌دهد بدون آنکه کاربران رنجیده شوند، با آزادی عمل، 60 مؤلفه کوچک استفاده شده در ساخت ساعت زنگدار را تعویض کند.

مثال دیگری از واسط عمومی در مقابل جزئیات پیاده سازی، میتواند در حوزه اتومبیل‌ها دیده شود. زمانیکه تولید کنندگان اتومبیل از سیستم‌های احتراق مکانیکی به سمت سیستم‌های احتراق الکترونیکی کوچ کردند، تعداد خیلی کمی از کاربران اتومبیل‌ها نگران این موضوع بودند. اما چرا؟ چون واسط عمومی آن‌ها مانند سابق ماند و تنها پیاده سازی تغییر کرد. فرض کنید که شما به قصد خرید اتومبیل به یک فروشنده اتومبیل مراجعه می‌کنید و فروشنده یک سوئیچ را به شما داده و از شما می‌خواهد برای تست آن را برانید. بعد از تلاشی که برای استارت زدن داشتید، فروشنده اعلام می‌کند که در این مدل برای استارت زدن باید ابتدا کاپوت را بالا زده و دکمه قرمز را فشار دهید. در این حالت، بدلیل اینکه واسط عمومی اتومبیل دستخوش تغییر بوده است، باعث ناراحتی شما خواهد شد.

این فلسفه، دقیقا یکی از ایده‌های پایه‌ای در پارادایم شیءگرا می‌باشد. تمام جزئیات پیاده سازی در سیستم شما باید در پشت یک واسط عمومی مستحکم و سازگار، از کاربران آنها پنهان باشد. نیاز کاربران، دانستن درباره واسط عمومی می‌باشد؛ اما هرگز مجاز به دیدن جزئیات پیاده سازی آنها نیستند. با این روش، پیاده ساز میتواند به هرشکلی که مناسب است، پیاده سازی را تغییر دهد؛ درحالیکه واسط عمومی مانند سابق می‌باشد. به عنوان مسافری که مکرر سفر میکنم، به شما اطمینان میدهم که استفاده از ساعت‌های زنگدار با وجود عدم اطلاع از پیاده سازی آنها، فواید عظیمی دارند. در هتل‌های زیادی که از دسته بندی‌های گسترده‌ای از ساعت‌ها مانند الکتریکی، قابل کوک (windup)، باتری خور، در هر دو مدل دیجیتال و آنالوگ استفاده میکنند، اقامت کرده‌ام. یکبار هم اتفاق نیفتاده‌است در حالیکه در هواپیما نشسته باشم، نگران این باشم که قادر نخواهم بود از ساعت زنگی اتاقم در هتل استفاده کنم.

بیشتر خوانندگان این کتاب، با وجود اینکه در نزدیکی آنها شاید ساعت زنگداری هم نباشد، ولی منظور بنده را با عبارت «ساعت زنگدار» متوجه شدند. به چه دلیل؟ شما در زندگی خودتان ساعت‌های زنگدار زیادی را می‌بینید و متوجه می‌شوید که همه آنها از یکسری خصوصیات مشترک مانند زمان، یک زمان هشدار و طراحی‌ای که مشخص میکند هشدار روشن یا خاموش است، بهره می‌برند. همچنین متوجه می‌شوید که همه ساعت‌های زنگداری که دیده‌اید امکان تنظیم کردن زمان، تنظیم زمان هشدار و روشن و خاموش کردن هشدار را به شما می‌دهند. در نتیجه، شما الان مفهومی را به نام «ساعت زنگدار» دارید که مفهومی را از داده و رفتار، در یک بسته بندی مرتب برای همه ساعت‌های زنگدار، تسخیر می‌کند. این مفهوم به عنوان یک Class (کلاس) شناخته می‌شود. یک ساعت زنگدار فیزیکی که شما در دست خود آن را نگه داشته‌اید، یک Object (وهله، Instance) ای از کلاس ساعت زنگدار می‌باشد. رابطه بین مفهوم کلاس و وهله، Instantiation Relationship (وهله سازی) نام دارد. به یک object، ساعت زنگدار وهله سازی شده (Instantiated) از کلاس ساعت زنگدار گفته می‌شود؛ در حالیکه از کلاس ساعت زنگدار به عنوان تعمیم (Generalization) از همه object‌های کلاس ساعت زنگدار که شما با آنها روبرو شده‌اید، یاد می‌شود. 

شکل 2.1 An Alarm Class and Its Objects 

شکل 2.1 An Alarm Class and Its Objects

 اگر من به شما می‌گفتم که ساعت زنگدارم از روی پاتختی (میز کوچک کنار تخت که دارای کشو می‌باشد) من پرید، من را گاز گرفت، سپس گربه‌ی همسایه را دنبال کرد، قطعا مرا دیوانه به حساب می‌آوردید. اگر به شما می‌گفتم که سگ من کارهای مشابه‌ای را انجام می‌دهد، کاملا منطقی می‌بود. چون نام یک کلاس تنها به مجموعه‌ای از خواص اشاره نمی‌کند، بلکه رفتارهای موجودیت (entity) را نیز مشخص می‌کند. این رابطه دوسویه بین داده و رفتار، اساس پارادایم شیء گرا می‌باشد.

یک object همیشه دارای 4 جنبه مهم زیر خواهد بود:
  • هویت خود (ممکن است آدرس آن در حافظه باشد) - its own identity
  • خواص کلاس خود (معمولا استاتیک) و مقادیر این خواص (معمولا پویا) - attributes of its class 
  • رفتار کلاس خود (از دید پیاده ساز) -  behavior of its class
  • واسط منتشر شده کلاس خود (از دید استفاده کننده) - published interface of its class

یک کلاس را  می توان با record definition (ساختار داده پایه، struct) و لیستی از عملیاتی که مجاز به کار بر روی این record definition هستند، پیاده سازی کرد. در زبان‌های رویه‌ای (Procedural) یافتن وابستگی داده‌ها در یک تابع معین، آسان می‌باشد. این کار را می‌توان به سادگی با بررسی کردن جزئیات پیاده سازی تابع و مشاهده نوع داده پارامترهای آن، مقادیر بازگشتی و متغییرهای محلی‌ای که تعریف شده‌اند، انجام داد. اگر قصد شما پیدا کردن وابستگی‌های تابعی بر روی یک داده می‌باشد، باید همه کد را بررسی کرده و به دنبال توابعی باشید که به داده شما وابسته هستند. در مدل شیء گرا، هر دو نوع وابستگی (داده به رفتار و رفتار به داده) به راحتی در دسترس می‌باشند. وهله‌ها، متغیرهایی از یک نوع داده کلاس هستند. جزئیات داخلی آنها باید فقط برای لیست توابع مرتبط با کلاس‌هایشان آشکار باشد. این محدودیت دسترسی به جزئیات داخلی وهله‌ها، Information Hiding نامیده می‌شود. اختیاری بودن این بحث در خیلی از زبان‌های شیء گرا ما را به سمت اولین قاعده شهودی هدایت می‌کند.

قاعده شهودی 2.1 
همه داده‌ها باید در داخل کلاس خود پنهان شده باشند. (All data should be hidden within its class)

با نقض این قاعده، امکان نگهداری را هم از دست می‌دهید. اجبار به پنهان کردن اطلاعات در مراحل طراحی و پیاده سازی، بخش عظیمی از فواید پارادایم شیء گرا می‌باشد. اگر داده به صورت عمومی تعریف شده باشد، تشخیص اینکه کدام بخش از عملیات (functionality) سیستم به آن داده وابسته است، سخت و مشکل خواهد بود. در واقع، نگاشت تغییرات داده به عملیات سیستم، همانند طراحی و پیاده سازی در دنیای action-oriented می‌باشد. ما مجبور می‌شویم برای تشخیص اینکه کدام عملیات به داده مورد نظر ما وابسته است، تمام عملیات سیستم را بررسی کنیم، تا به این ترتیب متوجه شویم.

برخی اوقات، یک توسعه دهنده استدلال می‌کند «نیاز دارم این بخش از داده را عمومی تعریف کنم زیرا ....» در این وضعیت، توسعه دهنده باید از خود سوال کند «کاری که تلاش دارم با این داده انجام دهم چیست و چرا کلاس این عملیات را خودش برای من انجام نمی‌دهد؟» در همه موارد  این کلاس است که به سادگی عملیات ضروری را فراموش کرده‌است. کمی بر روی شکل 2.2 فکر کنید. توسعه دهنده به صورت تصادفی فکر کرده است که عضو byte_offset را برای مجاز ساختن دسترسی تصادفی I/O، به صورت عمومی تعریف کند. اما چیزی که واقعا برای انجام این کار به آن نیاز داشت، تعریف یک operation بود (در زبان سی، توابع fseek و ftell برای ممکن کردن دسترسی تصادفی I/O، موجود هستند).

مراقب توسعه دهنده‌هایی که جسورانه می‌گویند: «ما می‌توانیم این بخش از داده را تغییر دهیم، زیرا هیچوقت تغییر نخواهد کرد!» باشید. طبق قانون برنامه نویسی مورفی، اولین بخشی که نیاز به تغییر خواهد داشت همین بخش از داده است.

شکل 2.2 Accidental Public Data   

 Accidental Public Data

به عنوان مثال دیگر برای روشن‌تر شدن بحث، کلاس Point را که پیاده سازی آن به روش مختصات دکارتی می‌باشد، در نظر بگیرید. یک طراح بی‌تجربه ممکن است دلیل تراشی کند که ما می‌توانیم داده‌های  X و Y را به صورت عمومی تعریف کنیم؛ چرا که هیچ موقع تغییر نخواهند کرد. فرض کنید نیاز جدیدی مبنی بر اینکه پیاده سازی Point به ناچار باید از دکارتی به قطبی تغییر کند، به دست شما برسد. به این صورت استفاده کنندگان از این کلاس Point نیز باید تغییر کنند. حال اگر داده‌ها پنهان بودند و عمومی نبودند، در نتیجه فقط لازم بود پیاده ساز‌های این کلاس، کد خود را تغییر دهند.

شکل 2.3  The danger of public data 

 خطر داده‌های عمومی

مسیرراه‌ها
ASP.NET MVC
              مطالب
              ثبت لینک‌های غیرتکراری
              ثبت لینک‌های مختلف در یک سیستم (مثلا قسمت به اشتراک گذاری لینک‌ها) در ابتدای کار شاید ساده به نظر برسد؛ خوب، هر صفحه‌ای که یک آدرس منحصربفرد بیشتر ندارد. ما هش این لینک را محاسبه می‌کنیم و بعد روی این هش، یک کلید منحصربفرد را تعریف خواهیم کرد تا دیگر رکوردی تکراری ثبت نشود. همچنین چون این هش نیز طول کوتاهی دارد، جستجوی آن بسیار سریع خواهد بود. واقعیت این است که خیر! این روش ناکارآمدترین حالت پردازش لینک‌های مختلف است.
              برای مثال لینک‌های http://www.site.com و http://www.site.com/index.htm دو هش متفاوت را تولید می‌کنند اما در عمل یکی هستند. نمونه‌ی دیگر، لینک‌های http://www.site.com/index.htm و http://www.site.com/index.htm#section1 هستند که فقط اصطلاحا در یک fragment با هم تفاوت دارند و از این دست لینک‌هایی که باید در حین ثبت یکی درنظر گرفته شوند، زیاد هستند و اگر علاقمند به مرور آن‌ها هستید، می‌توانید به صفحه‌ی URL Normalization در ویکی‌پدیا مراجعه کنید.
              اگر نکات این صفحه را تبدیل به یک کلاس کمکی کنیم، به کلاس ذیل خواهیم رسید:
              using System;
              using System.Web;
              
              namespace OPMLCleaner
              {
                  public static class UrlNormalization
                  {
                      public static bool AreTheSameUrls(this string url1, string url2)
                      {
                          url1 = url1.NormalizeUrl();
                          url2 = url2.NormalizeUrl();
                          return url1.Equals(url2);
                      }
              
                      public static bool AreTheSameUrls(this Uri uri1, Uri uri2)
                      {
                          var url1 = uri1.NormalizeUrl();
                          var url2 = uri2.NormalizeUrl();
                          return url1.Equals(url2);
                      }
              
                      public static string[] DefaultDirectoryIndexes = new[]
                          {
                              "default.asp",
                              "default.aspx",
                              "index.htm",
                              "index.html",
                              "index.php"
                          };
              
                      public static string NormalizeUrl(this Uri uri)
                      {
                          var url = urlToLower(uri);
                          url = limitProtocols(url);
                          url = removeDefaultDirectoryIndexes(url);
                          url = removeTheFragment(url);
                          url = removeDuplicateSlashes(url);
                          url = addWww(url);
                          url = removeFeedburnerPart(url);
                          return removeTrailingSlashAndEmptyQuery(url);
                      }
              
                      public static string NormalizeUrl(this string url)
                      {
                          return NormalizeUrl(new Uri(url));
                      }
              
                      private static string removeFeedburnerPart(string url)
                      {
                          var idx = url.IndexOf("utm_source=", StringComparison.Ordinal);
                          return idx == -1 ? url : url.Substring(0, idx - 1);
                      }
              
                      private static string addWww(string url)
                      {
                          if (new Uri(url).Host.Split('.').Length == 2 && !url.Contains("://www."))
                          {
                              return url.Replace("://", "://www.");
                          }
                          return url;
                      }
              
                      private static string removeDuplicateSlashes(string url)
                      {
                          var path = new Uri(url).AbsolutePath;
                          return path.Contains("//") ? url.Replace(path, path.Replace("//", "/")) : url;
                      }
              
                      private static string limitProtocols(string url)
                      {
                          return new Uri(url).Scheme == "https" ? url.Replace("https://", "http://") : url;
                      }
              
                      private static string removeTheFragment(string url)
                      {
                          var fragment = new Uri(url).Fragment;
                          return string.IsNullOrWhiteSpace(fragment) ? url : url.Replace(fragment, string.Empty);
                      }
              
                      private static string urlToLower(Uri uri)
                      {
                          return HttpUtility.UrlDecode(uri.AbsoluteUri.ToLowerInvariant());
                      }
              
                      private static string removeTrailingSlashAndEmptyQuery(string url)
                      {
                          return url
                                  .TrimEnd(new[] { '?' })
                                  .TrimEnd(new[] { '/' });
                      }
              
                      private static string removeDefaultDirectoryIndexes(string url)
                      {
                          foreach (var index in DefaultDirectoryIndexes)
                          {
                              if (url.EndsWith(index))
                              {
                                  url = url.TrimEnd(index.ToCharArray());
                                  break;
                              }
                          }
                          return url;
                      }
                  }
              }
              از این روش برای تمیز کردن و حذف فیدهای تکراری در فایل‌های OPML تهیه شده نیز می‌شود استفاده کرد. عموما فیدخوان‌های نه‌چندان با سابقه، نکات یاد شده در این مطلب را رعایت نمی‌کنند و به سادگی می‌شود در این سیستم‌ها، فیدهای تکراری زیادی را ثبت کرد.
              برای مثال اگر یک فایل OPML چنین ساختار XML ایی را داشته باشد:
              <?xml version="1.0" encoding="utf-8"?>
              <opml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0">
                <body>
                  <outline text="آی تی ایرانی">
                  <outline type="rss" 
                                         text="‫فید کلی آخرین نظرات، مطالب، اشتراک‌ها و پروژه‌های .NET Tips" 
                                         title="‫فید کلی آخرین نظرات، مطالب، اشتراک‌ها و پروژه‌های .NET Tips" 
                                         xmlUrl="https://www.dntips.ir/Feed/LatestChanges"
                                         htmlUrl="https://www.dntips.ir/" />
                  </outline> 
                </body>
              </opml>
              هر outline آن‌را به کلاس زیر می‌توان نگاشت کرد:
              using System.Xml.Serialization;
              
              namespace OPMLCleaner
              {
                  [XmlType(TypeName="outline")]
                  public class Opml
                  {
                      [XmlAttribute(AttributeName="text")]
                      public string Text { get; set; }
              
                      [XmlAttribute(AttributeName = "title")]
                      public string Title { get; set; }
              
                      [XmlAttribute(AttributeName = "type")]
                      public string Type { get; set; }
              
                      [XmlAttribute(AttributeName = "xmlUrl")]
                      public string XmlUrl { get; set; }
              
                      [XmlAttribute(AttributeName = "htmlUrl")]
                      public string HtmlUrl { get; set; }
                  }
              }
              برای اینکار فقط کافی است از LINQ to XML به نحو ذیل استفاده کنیم:
              var document = XDocument.Load("it-92-03-01.opml");
              var results = (from node in document.Descendants("outline")
                                         where node.Attribute("htmlUrl") != null && node.Parent.Attribute("text") != null 
                                          && node.Parent.Attribute("text").Value == "آی تی ایرانی"
                                         select new Opml
                                         {
                                             HtmlUrl = (string)node.Attribute("htmlUrl"),
                                             Text = (string)node.Attribute("text"),
                                             Title = (string)node.Attribute("title"),
                                             Type = (string)node.Attribute("type"),
                                             XmlUrl = (string)node.Attribute("xmlUrl")
                                         }).ToList();
              در این حالت لیست کلیه فیدهای یک گروه را چه تکراری و غیرتکراری، دریافت خواهیم کرد. برای حذف موارد تکراری نیاز است از متد Distinct استفاده شود. به همین جهت باید کلاس ذیل را نیز تدارک دید:
              using System.Collections.Generic;
              
              namespace OPMLCleaner
              {
                  public class OpmlCompare : EqualityComparer<Opml>
                  {
                      public override bool Equals(Opml x, Opml y)
                      {
                          return UrlNormalization.AreTheSameUrls(x.HtmlUrl, y.HtmlUrl);                
                      }
              
                      public override int GetHashCode(Opml obj)
                      {
                          return obj.HtmlUrl.GetHashCode();
                      }
                  }
              }
              اکنون با کمک کلاس OpmlCompare فوق که از کلاس UrlNormalization برای تشخیص لینک‌های تکراری استفاده می‌کند، می‌توان به لیست بهتر و متعادل‌تری رسید:
               var distinctResults = results.Distinct(new OpmlCompare()).ToList();
               
              نظرات مطالب
              تنظیمات CORS در ASP.NET Core
              با توجه به پیاده‌سازی  CORS-RFC1918  در مرورگر کروم، با وجود پذیرش درخواست‌های CORS، از درخواست‌هایی که از public-network به منابعی از private-network ارسال شوند جلوگیری می‌شود.
              به طور مثال از درخواست‌هایی که از طرف یک سایت اینترنتی به سرویس‌های در حال اجرا بر روی کلاینت (مانند سرویس‌های وبی دستگاه‌های پوز) ارسال می‌شود، جلوگیری می‌شود.
              با غیر فعال کردن chrome://flags/#block-insecure-private-network-requests  این رفتار کروم را می‌توان تغییر داد.
              نظرات مطالب
              آماده سازی زیرساخت تهیه Integration Tests برای ServiceLayer
              نکته تکمیلی
              در پروژه خود از الگوی Container Per Request استفاده می‌کنید؟ برای نزدیکتر کردن شرایط تست به شرایط محیط عملیاتی می‌توان به شکل زیر عمل کرد:
              کلاسی برای ایجاد و تخریب Nested Container 
                  public static class TestDependencyScope
                  {
                      private static IContainer _currentNestedContainer;
              
                      public static void Begin()
                      {
                          if (_currentNestedContainer != null)
                              throw new Exception("Cannot begin test dependency scope. Another dependency scope is still in effect.");
              
                          _currentNestedContainer = IoC.Container.GetNestedContainer();
                      }
              
                      public static IContainer CurrentNestedContainer
                      {
                          get
                          {
                              if (_currentNestedContainer == null)
                                  throw new Exception($"Cannot access the {nameof(CurrentNestedContainer)}. There is no dependency scope in effect.");
              
                              return _currentNestedContainer;
                          }
                      }
              
                      public static void End()
                      {
                          if (_currentNestedContainer == null)
                              throw new Exception("Cannot end test dependency scope. There is no dependency scope in effect.");
              
                          _currentNestedContainer.Dispose();
                          _currentNestedContainer = null;
                      }
                  }

              سپس به مانند  AutoRollbackAttrbiute مذکور در مطلب جاری، ContainerPerTestCaseAttribute را برای مدیریت این قضیه در نظر می‌گیریم:
               public class ContainerPerTestCaseAttribute : Attribute, ITestAction
                  {   
                      public void BeforeTest(ITest test)
                      {
                          TestDependencyScope.Begin();
                      }
              
                      public void AfterTest(ITest test)
                      {
                          TestDependencyScope.End();
                      }
              
                      public ActionTargets Targets => ActionTargets.Test;
                  }
              و حال نحوه استفاده از آن:
                  [PopulateHttpContext]
                  [ContainerPerTestCase]
                  [Transactional]
                  [TestFixture]
                  public class IntegratedTestBase
                  {
                      [SetUp]
                      public void EachTestSetUp()
                      {
                          BeforeEachTest();
                      }
                      [TearDown]
                      public void EachTestTearDown()
                      {
                          AfterEachTest();
                      }
                      protected virtual void BeforeEachTest()
                      {
                      }
                      protected virtual void AfterEachTest()
                      {
                      }
              
                      protected void UsingUnitOfWork(Action<IUnitOfWork> action)
                      {
                          IoC.Container.Using((IUnitOfWork uow)=>
                          {
                              uow.DisableAllFilters();
                              action(uow);
                          });
                      }
              
                      protected T UsingUnitOfWork<T>(Func<IUnitOfWork, T> func)
                      {
                          var uow = IoC.Resolve<IUnitOfWork>();
              
                          uow.DisableAllFilters();
              
                          using (uow)
                          {
                              var result = func(uow);
              
                              uow.SaveChanges();
              
                              return result;
                          }
                      }
                      protected async Task<T> UsingUnitOfWorkAsync<T>(Func<IUnitOfWork, Task<T>> func)
                      {
                          var uow = IoC.Resolve<IUnitOfWork>();
              
                          uow.DisableAllFilters();
              
                          using (uow)
                          {
                              var result = await func(uow).ConfigureAwait(false);
              
                              await uow.SaveChangesAsync().ConfigureAwait(false);
              
                              return result;
                          }
              
                      }
                  }
              و برای دسترسی به Nested Container جاری می‌توان به شکل زیر عمل کرد:
              namespace ProjectName.ServiceLayer.IntegrationTests
              {
                  public static class Testing
                  {
                      private static IContainer Container => TestDependencyScope.CurrentNestedContainer;
              
                      public static T Resolve<T>()
                      {
                          return Container.GetInstance<T>();
                      }
              
                      public static object Resolve(Type type)
                      {
                          return Container.GetInstance(type);
                      }
              
                      public static void Inject<T>(T instance) where T : class
                      {
                          Container.Inject(instance);
                      }
                  }
              }
              
              //in test classes
              using static ProjectName.ServiceLayer.IntegrationTests.Testing;
              namespace ProjectName.ServiceLayer.IntegrationTests
              {
                  public class RoleServiceTests : IntegratedTestBase
                  {
                      private IRoleService _service;
                      protected override void BeforeEachTest()
                      {
                          _service = Resolve<IRoleService>();
                      }
                   }
              }

              نظرات مطالب
              Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
              نحوه نمایش مقدار Display attribute پراپرتی‌ها در تگ‌های Label با استفاده از کامپوننت‌های جنریک

              در ASP.NET Core اگه بخوایم Display یک پراپرتی رو نمایش بدیم به این صورت عمل میکنیم:
              @model ProjectName.ViewModels.Identity.RegisterViewModel
              <label asp-for="PhoneNumber"></label>
              در حال حاضر چنین قابلیتی به صورت توکار در Blazor وجود ندارد، برای اینکه این قابلیت رو با استفاده از کامپوننت‌ها پیاده سازی کنیم میتوان از یک کامپوننت جنریک استفاده کرد (اطلاعات بیشتر در مورد کامپوننت‌های جنریک):
              @using System.Reflection
              @using System.Linq.Expressions
              @using System.ComponentModel.DataAnnotations
              @typeparam T
              @if (ChildContent is null)
              {
                  <label>@Label</label>
              }
              else
              {
                  <label>
                      @Label
                      @ChildContent
                  </label>
              }
              @code {
              
                  [Parameter, EditorRequired]
                  public Expression<Func<T>> DisplayNameFor { get; set; } = default!;
              
                  [Parameter]
                  public RenderFragment? ChildContent { get; set; }
              
                  private string Label => GetDisplayName();
              
                  private string GetDisplayName()
                  {
                      var expression = (MemberExpression)DisplayNameFor.Body;
                      var value = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
                      return value?.Name ?? expression.Member.Name;
                  }
              }

              نحوه استفاده:
              private LoginDto Login { get; } = new();
              <CustomDisplayName DisplayNameFor="@(() => Login.UserName)" />
              همچنین میتوان ChildContent رو که یک RenderFragment است مقداری دهی کرد که در کنار Display، مقدار ChildContent رو هم نمایش بده.
              <CustomDisplayName DisplayNameFor="@(() => Login.UserName)">
                  <span class="text-danger">(*)</span>
              </CustomDisplayName>

              نحوه استفاده در پروژه‌های چند زبانه
              کامپوننت فوق برای استفاده در پروژه چند زبانه باید به صورت زیر تغییر پیدا کنه:
              @using System.Reflection
              @using System.Linq.Expressions;
              @using System.ComponentModel.DataAnnotations;
              @typeparam T
              @if (ChildContent == null)
              {
                  <label>@Label</label>
              }
              else
              {
                  <label>
                      @Label
                      @ChildContent
                  </label>
              }
              @code {
              
                  [Parameter]
                  public Expression<Func<T>> DisplayNameFor { get; set; } = default!;
              
                  [Parameter]
                  public RenderFragment? ChildContent { get; set; }
              
                  [Inject]
                  public IStringLocalizerFactory LocalizerFactory { get; set; } = default!;
              
                  private string Label => GetDisplayName();
              
                  private string GetDisplayName()
                  {
                      var expression = (MemberExpression)DisplayNameFor.Body;
                      var displayAttribute = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
                      if (displayAttribute is {ResourceType: not null })
                      {
                          // Try to dynamically create an instance of the specified resource type
                          var resourceType = displayAttribute.ResourceType;
                          var localizer = LocalizerFactory.Create(resourceType);
              
                          return localizer[displayAttribute.Name ?? expression.Member.Name];
                      }
                      return displayAttribute?.Name ?? expression.Member.Name;
                  }
              }
              برای اتریبیوت Display هم باید مقدار ResourceType مقدار دهی شود:
              public class LoginDto
              {
                  [Display(Name = nameof(LoginDtoResource.UserName), ResourceType = typeof(LoginDtoResource))]
                  [Required]
                  [MaxLength(100)]
                  public string UserName { get; set; } = default!;
              
                  //...
              }

              LoginDtoResource یک فایل Resource است که باید با باز کردنش در ویژوال استودیو، Access modifier اونو به public تغییر بدید.
              نحوه افزودن کلاس به Label ( ساده سازی تعاریف ویژگی‌های المان‌ها )
              افزودن یک دیکشنری به کامپوننت:
              [Parameter(CaptureUnmatchedValues = true)]
              public Dictionary<string, object> InputAttributes { get; set; } = new();
              استفاده از InputAttributes در تگ Label:
              @if (ChildContent is null)
              {
                  <label @attributes="InputAttributes">
                      @Label
                  </label>
              }
              else
              {
                  <label @attributes="InputAttributes">
                      @Label
                      @ChildContent
                  </label>
              }
              الان میتونیم هر تعداد اتریبیوتی رو به کامپوننت پاس بدیم:
              <CustomDisplayName DisplayNameFor="@(() => Login.UserName)" class="form-label" id="test" for="UserName" />
              اشتراک‌ها
              روش محافظت از سایت، در صورت آلوده شدن تعدادی از اسکریپت‌های آن که از یک سایت ثالث تامین شده‌‌اند

              SRI Integrity Attribute allows the browser to determine if the file has been modified, which allows it to reject the file.

              <script src="//www.site.com/plus/scripts/ba.js"
                   integrity="sha256-Abhisa/nS9WMne/YX+dqiFINl+JiE15MCWvASJvVtIk="
                   crossorigin="anonymous"></script>

              روش محافظت از سایت، در صورت آلوده شدن تعدادی از اسکریپت‌های آن که از یک سایت ثالث تامین شده‌‌اند
              نظرات مطالب
              ارسال ایمیل در ASP.NET Core
              نکته تکمیلی:
              در صورت استفاده از mailkit در محیط net core. میتوان کلاس SMTPClient را به شکل زیر مورد استفاده قرار داد.
               public class DiskSmtpClient : SmtpClient
                  {
                      public DiskSmtpClient(IOptionsSnapshot<MailKitOptions> mailOptionsSnapshot)
                      {
                          if (mailOptionsSnapshot.Value.SpecifiedPickupDirectory)
                          {
                              SpecifiedPickupDirectory = true;
                              PickupDirectoryLocation = mailOptionsSnapshot.Value.PickupDirectoryLocation;
                          }
                          
                      }
                      public bool SpecifiedPickupDirectory { get; set; }
                      public string PickupDirectoryLocation { get; set; }
              
                      public override Task SendAsync(MimeMessage message, CancellationToken cancellationToken = new CancellationToken(),
                          ITransferProgress progress = null)
                      {
                          if (!SpecifiedPickupDirectory)
                              return base.SendAsync(message, cancellationToken, progress);
                          return SaveToPickupDirectory(message, PickupDirectoryLocation);
              
                      }
              
                  
              
              
              
                      private async Task SaveToPickupDirectory(MimeMessage message, string pickupDirectory)
                      {
                          using (var stream = new FileStream($@"{pickupDirectory}\email-{Guid.NewGuid().ToString("N")}.eml", FileMode.CreateNew))
                          {
                              await message.WriteToAsync(stream);
                          }
                      }
              
                     
              
                      public override Task ConnectAsync(string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto,
                          CancellationToken cancellationToken = new CancellationToken())
                      {
                          if (!SpecifiedPickupDirectory)
                              return base.ConnectAsync(host, port, options, cancellationToken);
                          return Task.CompletedTask;
                      }
              
              
              
                      public override Task DisconnectAsync(bool quit, CancellationToken cancellationToken = new CancellationToken())
                      {
                          if (!SpecifiedPickupDirectory)
                              return base.DisconnectAsync(quit, cancellationToken);
              
                          return Task.CompletedTask;
                      }
                  }
              در کد بالا دو خصوصیت SpecifiedPickupDirectory  و PickupDirectoryLocation به آن اضافه شده اند و با رونویسی از متدهای مورد استفاده به جای ارسال ایمیل در صورت مقداردهی  SpecifiedPickupDirectory  ایمیل در آدرس  PickupDirectoryLocation   ذخیره میگردد. سپس به شکل زیر آن را مورد استفاده قرار میدهیم:
               services.AddTransient<DiskSmtpClient>();
                var email = "mail@dotnettips.info";
                          var subject = "subject";
                          var message = "message";
              
                          var emailMessage = new MimeMessage();
              
                          emailMessage.From.Add(new MailboxAddress("DNT", "do-not-reply@dotnettips.info"));
                          emailMessage.To.Add(new MailboxAddress("", email));
                          emailMessage.Subject = subject;
                          emailMessage.Body = new TextPart(TextFormat.Html)
                          {
                              Text = message
                          };
              
                          
                              _client.SpecifiedPickupDirectory = true;
                              _client.PickupDirectoryLocation = "c:\\mail";
              
                              _client.LocalDomain = "dotnettips.info";
                              await _client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
                              await _client.SendAsync(emailMessage).ConfigureAwait(false);
                              await _client.DisconnectAsync(true).ConfigureAwait(false);
              در صورتی که قصد ندارید کد اضافه‌تری را نیز اعمال نمایید میتوانید با اضافه کردن تکه کد زیر به فایل startup و محتوای تنظیمات آن به فایل appsettings.json دو خط بالا را حذف نمایید:
              services.Configure<MailKitOptions>(options => Configuration.GetSection("MailKitOptions").Bind(options));
                "MailKitOptions": {
                  "SpecifiedPickupDirectory": true,
                  "PickupDirectoryLocation": "c:\\mail"
                }
              در این صورت میتوان تنظیمات جداگانه ای برای حالت انتشار و توسعه نیز در نظر گرفت.
              کلاس متناظر MailKitOptions
                  public class MailKitOptions
                  {
                      public  bool SpecifiedPickupDirectory { get; set; }
                      public  string PickupDirectoryLocation { get; set; }
                  }