مطالب
بررسی ساختارهای جدید DateOnly و TimeOnly در دات نت 6
به همراه دات نت 6، دو ساختار داده‌ی جدید DateOnly و TimeOnly نیز معرفی شده‌اند که امکان کار کردن ساده‌تر با قسمت‌های فقط تاریخ و یا فقط زمان DateTime را میسر می‌کنند. این دو نوع جدید نیز همانند DateTime، از نوع struct هستند و بنابراین value type محسوب می‌شوند. در فضای نام System قرار گرفته‌اند و همچنین با نوع‌های date و time مربوط به SQL Server، سازگاری کاملی دارند.


روش استفاده از نوع DateOnly در دات نت 6

نوع‌های جدید معرفی شده، بسیار واضح هستند و مقصود از بکارگیری آن‌ها را به خوبی بیان می‌کنند. برای مثال اگر نیاز بود تاریخی را بدون در نظر گرفتن قسمت زمان آن معرفی کنیم، می‌توان از نوع DateOnly استفاده کرد؛ مانند تاریخ تولد، روزهای کاری و امثال آن. تا پیش از این برای معرفی یک چنین تاریخ‌هایی، عموما قسمت زمان DateTime را با 00:00:00.000 مقدار دهی می‌کردیم؛ اما دیگر نیازی به این نوع تعاریف نیست و می‌توان مقصود خود را صریح‌تر بیان کرد.
روش معرفی نمونه‌ای از آن با معرفی سال، ماه و روز است:
 var date = new DateOnly(2020, 04, 20);
و یا اگر خواستیم یک DateTime موجود را به DateOnly تبدیل کنیم، می‌توان به صورت زیر عمل کرد:
 var currentDate = DateOnly.FromDateTime(DateTime.Now);

همچنین در اینجا نیز همانند DateTime می‌توان از متدهای Parse و یا TryParse، برای تبدیل یک رشته به معادل DateOnly آن، کمک گرفت:
if (DateOnly.TryParse("28/09/1984", new CultureInfo("en-US"), DateTimeStyles.None, out var result))
{
   Console.WriteLine(result);
}
در یک چنین حالتی ذکر CultureInfo، دقت کار را افزایش می‌دهد؛ در غیراینصورت از CultureInfo ترد جاری برنامه استفاده خواهد شد که می‌تواند در سیستم‌های مختلف، متفاوت باشد.

و یا می‌توان توسط متد ParseExact، ساختار تاریخ دریافتی را دقیقا مشخص کرد:
DateOnly d1 = DateOnly.ParseExact("31 Dec 1980", "dd MMM yyyy", CultureInfo.InvariantCulture);  // Custom format
Console.WriteLine(d1.ToString("o", CultureInfo.InvariantCulture)); // "1980-12-31"  (ISO 8601 format)

در حین نمونه سازی DateOnly، امکان ذکر تقویم‌های خاص، مانند PersianCalendar نیز وجود دارد:
var persianCalendar = new PersianCalendar();
DateOnly d2 = new DateOnly(1400, 9, 6, persianCalendar);
Console.WriteLine(d2.ToString("d MMMM yyyy", CultureInfo.InvariantCulture));

در اینجا همچنین متدهایی مانند AddDays، AddMonths و AddYears نیز بر روی date مهیا کار می‌کنند:
var newDate = date.AddDays(1).AddMonths(1).AddYears(1)

یک نکته: برخلاف DateTime، نوع DateOnly به همراه DateTimeKind مانند Utc و امثال آن نیست و همواره DateTimeKind آن Unspecified است.


روش استفاده از نوع TimeOnly در دات نت 6

نوع و ساختار TimeOnly، قسمت زمان را به نحو صریحی مشخص می‌کند؛ مانند ساعتی که باید هر روز راس آن، آلارمی به صدا درآید و یا جلسه‌ای تشکیل شود و یا وظیفه‌ای صورت گیرد. سازنده‌ی آن overload‌های قابل توجهی را داشته و می‌تواند یکی از موارد زیر باشد:
public TimeOnly(int hour, int minute)
public TimeOnly(int hour, int minute, int second)
public TimeOnly(int hour, int minute, int second, int millisecond)
برای نمونه برای نمایش 10:30 صبح، می‌توان به صورت زیر عمل کرد:
var startTime = new TimeOnly(10, 30);
در اینجا قسمت ساعت، 24 ساعتی تعریف شده‌است. بنابراین برای نمونه، ساعت 1 عصر را باید به صورت 13 قید کرد:
var endTime = new TimeOnly(13, 00, 00);

و یا برای مثال می‌توان این نمونه‌ها را از هم کم کرد:
var diff = endTime - startTime;
خروجی این تفاوت محاسبه شده، بر حسب TimeSpan است:
Console.WriteLine($"Hours: {diff.TotalHours}");
و یا با استفاده از متد الحاقی ToTimeSpan می‌توان یک TimeOnly را به TimeSpan معادلی تبدیل نمود:
TimeSpan ts = endTime.ToTimeSpan();

برای تبدیل قسمت زمان DateTime به TimeOnly، می‌توان از متد FromDateTime به صورت زیر استفاده کرد:
var currentTime = TimeOnly.FromDateTime(DateTime.Now);
و یا اگر بخواهیم یک DateOnly را به DateTime تبدیل کنیم، می‌توان از متد الحاقی ToDateTime به همراه ذکر قسمت زمان آن بر حسب TimeOnly کمک گرفت:
DateTime dt = date.ToDateTime(new TimeOnly(0, 0));
Console.WriteLine(dt);

و در این حالت اگر خواستیم بررسی کنیم که آیا زمانی بین دو زمان دیگر واقع شده‌است یا خیر، می‌توان از متد IsBetween استفاده نمود:
 var isBetween = currentTime.IsBetween(startTime, endTime);
Console.WriteLine($"Current time {(isBetween ? "is" : "is not")} between start and end");

در اینجا امکان مقایسه این نمونه‌ها، توسط عملگرهایی مانند < نیز وجود دارد:
var startTime = new TimeOnly(08, 00);
var endTime = new TimeOnly(09, 00);
 
Console.WriteLine($"{startTime < endTime}");

اگر نیاز به تبدیل رشته‌ای به TimeOnly بود، می‌توان از متد ParseExact به همراه ذکر ساختار مدنظر، استفاده کرد:
TimeOnly time = TimeOnly.ParseExact("5:00 pm", "h:mm tt", CultureInfo.InvariantCulture);  // Custom format
Console.WriteLine(time.ToString("T", CultureInfo.InvariantCulture)); // "17:00:00"  (long time format)


عدم پشتیبانی System.Text.Json از نوع‌های جدید DateOnly و TimeOnly

فرض کنید رکوردی را به صورت زیر تعریف کرده‌ایم که از نوع‌های جدید DateOnly و TimeOnly، تشکیل شده‌است:
public record DataTypeTest(DateOnly Date, TimeOnly Time);
اگر سعی کنیم نمونه‌ای از آن را به JSON تبدیل کنیم:
var date = DateOnly.FromDateTime(DateTime.Now);
var time = TimeOnly.FromDateTime(DateTime.Now);
var test = new DataTypeTest(date, time);
var json = JsonSerializer.Serialize(test);
با استثنای زیر مواجه خواهیم شد:
Serialization and deserialization of 'System.DateOnly' instances are not supported.

برای رفع این مشکل می‌توان ابتدا تبدیلگر ویژه‌ی DateOnly و
    public class DateOnlyConverter : JsonConverter<DateOnly>
    {
        private readonly string _serializationFormat;

        public DateOnlyConverter() : this(null)
        { }

        public DateOnlyConverter(string? serializationFormat)
        {
            _serializationFormat = serializationFormat ?? "yyyy-MM-dd";
        }

        public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            return DateOnly.ParseExact(value!, _serializationFormat, CultureInfo.InvariantCulture);
        }

        public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
            => writer.WriteStringValue(value.ToString(_serializationFormat));
    }
و سپس تبدیلگر ویژه‌ی TimeOnly را به صورت زیر تدارک دید:
    public class TimeOnlyConverter : JsonConverter<TimeOnly>
    {
        private readonly string _serializationFormat;

        public TimeOnlyConverter() : this(null)
        {
        }

        public TimeOnlyConverter(string? serializationFormat)
        {
            _serializationFormat = serializationFormat ?? "HH:mm:ss.fff";
        }

        public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            return TimeOnly.ParseExact(value!, _serializationFormat, CultureInfo.InvariantCulture);
        }

        public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
            => writer.WriteStringValue(value.ToString(_serializationFormat));
    }
و به نحو زیر مورد استفاده قرار داد:
var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
jsonOptions.Converters.Add(new DateOnlyConverter());
jsonOptions.Converters.Add(new TimeOnlyConverter());
var json = JsonSerializer.Serialize(test, jsonOptions);
مطالب
بررسی الگوی Chain of Responsibility در جاوا اسکریپت
الگوی Chain of Responsibility، یک زنجیر، از اشیاء متصل شده‌ی به هم را فراهم می‌کند که یکی از آنها می‌تواند درخواست رسیده را راضی کند؛ به عبارتی دیگر به محض دریافت درخواست، آن را پردازش می‌کند. این الگو اساسا یک جستجوی خطی ( linear search )، برای یک شیء می‌باشد که می‌تواند یک درخواست ویژه را handle کند. 

الگوی chain-of-responsibility، ارتباط با الگوی Chaining دارد که به دفعات در جاوا اسکریپت استفاده شده‌است (jQuery  استفاده‌ی گسترده‌ای از این الگو کرده‌است).
این الگو اجازه میدهد که یک درخواست ارسال شده‌ی توسط کلاینت، توسط یک یا بیش از یک object، دریافت شود. یک مثال عمومی از این الگو،  event bubbling در DOM  می‌باشد. یک رویداد از طریق element‌‌‌های تودرتوی متفاوت انتشار پیدا می‌کند؛ تا زمانیکه یکی از آن‌ها، آن را handle کند.


مثال زیر را در نظر بگیرید:
class HandlerChain 
{ 
   setNextObj(nextObjInChain){}
   processMultiple(req){
     console.log("No multiple for: " + req.getMultiple());
   }
} 
  
class Multiple
{
  constructor(multiple){
    this.multiple = multiple;
  }
  
  getMultiple(){ 
    return this.multiple; 
  } 
  
} 
  

class MultipleofTwoHandler extends HandlerChain
{
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  } 
  
  setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) { 
    if ((req.getMultiple() % 2) == 0) { 
      console.log("Multiple of 2: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
    } 
  } 
} 
  
class MultipleofThreeHandler extends HandlerChain 
{ 
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  } 
  
   setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) 
  { 
    if ((req.getMultiple() % 3) == 0) { 
      console.log("Multiple of 3: " + req.getMultiple()); 
    }else{ 
          this.nextObjInChain.processMultiple(req); 
        } 
    } 
} 


class MultipleofFiveHandler extends HandlerChain
{ 
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  }
  
  setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) { 
    if ((req.getMultiple() % 5) == 0) { 
      console.log("Multiple of 5: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
      } 
    } 
} 

//configuring the chain of handler objects
var c1 = new MultipleofTwoHandler(); 
var c2 = new MultipleofThreeHandler(); 
var c3 = new MultipleofFiveHandler(); 
c1.setNextObj(c2); 
c2.setNextObj(c3); 
  
//the chain handling different cases
c1.processMultiple(new Multiple(95));  // Multiple of 5: 95  
c1.processMultiple(new Multiple(50));  // Multiple of 2: 50 
c1.processMultiple(new Multiple(9));   // Multiple of 3: 9 
c1.processMultiple(new Multiple(4));   // Multiple of 2: 4 
c1.processMultiple(new Multiple(21));  // Multiple of 3: 21 
c1.processMultiple(new Multiple(23));  // No multiple for: 23  
مثال بالا، الگوی chain of responsibility را پیاده سازی می‌کند و بررسی می‌کند که آیا عدد وارد شده، مضربی از 2، 3 یا 5 است و یا نه.
چگونه می‌توانیم این قابلیت را پیاده سازی کنیم؟ ما می‌خواهیم که یک عدد داشته باشیم و سپس به handler‌ها در زنجیره اجازه بدهیم که تصمیم گیری کنند که آیا آنها می‌خواهند عدد وارد شده را پردازش کنند، یا به handler بعدی پاس بدهند.

ما 3 نوع handler در این زنجیره داریم:

  • MultipleofTwoHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 2 است یا نه. 
  • MultipleofThreeHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 3 است یا نه 
  • MultipleofFiveHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 5 است یا نه. 

اولین قدم، ایجاد یک زنجیره از handler‌های بالا می‌باشد. در اینجا یک کلاس را به نام HandlerChain، برای همین منظور داریم و این کلاس شامل دو تابع، به نام‌های setNextObj و processMultiple می‌باشد.
class HandlerChain 
{ 
   setNextObj(nextObjInChain){}
   processMultiple(req){
     console.log("No multiple for: " + req.getMultiple());
   }
}

پیاده سازی پیش فرض processMultiple، زمانی اجرا می‌شود که هیچ مضربی برای یک عدد، وجود نداشته باشد. به عبارت دیگر، عدد مورد نظر، مضربی از 2،3 و 5 نباشد ( از بین مضرب‌های تعین شده). 
تمام handler‌ها در این زنجیره، از این کلاس ارث بری می‌کنند. هر handler می‌تواند دو عملیات را انجام دهد:

  1. تنظیم کردن handler بعدی در زنجیره 
  2. پردازش عدد وارد شده به این منظور که آیا در مضرب مورد نظر قرار دارد یا نه. 

class MultipleofTwoHandler extends HandlerChain
{ 
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}
  
class MultipleofThreeHandler extends HandlerChain
{
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}


class MultipleofFiveHandler extends HandlerChain
{
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}

constructor برای هر handler، همانند زیر تعریف شده‌است:
constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
}

در ابتدا توسط  super ،  مقدار دهی اولیه‌ای برای متد‌های setNextObj و processMultiple از کلاس پدر انجام میشود. همچنین در constructor، متغیر nextObjInChain
مقدار دهی اولیه می‌شود که تعیین کننده‌ی شیء بعدی در زنجیره می‌باشد. 
 
 چگونه شیء جاری، شیء بعد را در زنجیره تعیین می‌کند؟ اجازه بدهید برای این منظور نگاهی به تابع setNextObj داشته باشیم. setNextObj در هر handler، همانند زیر تعیین شده‌است: 
setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
}

اکنون می‌توانیم زنجیره‌ای از handler‌ها را همانند زیر ایجاد کنیم: 
var c1 = new MultipleofTwoHandler(); 
var c2 = new MultipleofThreeHandler(); 
var c3 = new MultipleofFiveHandler(); 
c1.setNextObj(c2); 
c2.setNextObj(c3);

در اینجا ما handler‌‌‌های c2, c1  و c3 را به منظور پردازش کردن مضرب‌های 2، 3 و 5 ایجاد کرده‌ایم. آن‌ها به یکدیگر به حالت زیر متصل شده‌اند:
 c2 به عنوان handler بعدی برای c1 و c3 به عنوان handler بعدی برای c2. 


اکنون که زنجیره ایجاد شده‌است، زمان آن است که عدد وارد شده را پردازش کنیم. اجازه بدهید که کار را با ایجاد کردن شیء “multiple”  با استفاده از کلاس Multiple، شروع کنیم.

class Multiple
{
  constructor(multiple){
    this.multiple = multiple
  }
  
  getMultiple(){ 
    return this.multiple; 
  }  
}

یک شیء Multiple، شامل یک خصوصیت به نام multiple و یک متد است به نام getMultiple که به منظور برگشت دادن multiple می‌باشد.
 
MultipleofTwoHandler به صورت زیر تعریف شده‌است:
processMultiple(req) { 
    if ((req.getMultiple() % 2) == 0) { 
      console.log("Multiple of 2: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
    } 
}

در اینجا، یک multiple دریافت شده و چک می‌شود که آیا مضربی از 2 می‌باشد یا نه؛ اگر چنین است، سپس عدد دریافت شده را به عنوان مضربی از 2 نمایش می‌دهد. در صورتیکه مضربی از 2 نباشد، handler آن را به شیء بعدی در زنجیره، پاس میدهد و سپس همین روال دوباره تکرار می‌شود و تا زمانیکه یکی از handler ‌ها، جواب را برگشت دهد، این روال ادامه پیدا می‌کند. 
 
تعریف تابع processMultiple  برای هر 3 handler یکسان میباشد؛ با این تفاوت که MultipleofThreeHandler، برای بررسی مضربی از 3 بودن و MultipleofFiveHandler  برای بررسی مضربی از 5 بودن است.

اجازه بدهید یک مثال بزنیم و ببینیم که چه اتفاقی می‌افتد:
c1.processMultiple(new Multiple(95)) // Multiple of 5: 95  

اولین handler در زنجیره که c1 است، یک multiple (95) را دریافت می‌کند و چک می‌کند که آیا مضربی از 2 است یا نه. جواب خیر است؛ بنابراین multiple به دومین handler  در زنجیره پاس داده میشود؛ یعنی به handler بررسی کننده‌ی مضربی از 3 . در اینجا دوباره بررسی میشود که آیا عدد مورد نظر، مضربی از 3 است یا نه؟ که جواب خیر است. در ادامه multiple به handler سوم در زنجیره که بررسی کننده‌ی مضربی از 5 است، پاس داده میشود. در اینجا، شرط درست است و عدد 95 به عنوان مضربی از 5 چاپ میشود. 


چه زمانی از این الگو استفاده کنیم؟
به منظور مدیریت کردن انواع درخواست‌ها، به روش‌های متفاوت، بدون دانستن ترتیب و نوع درخواست از قبل، از این الگو استفاده می‌کنیم. این الگو به ما اجازه می‌دهد که زنجیره‌ای از چند handler را داشته باشیم. تمامی handler‌ها، یک شانس را جهت پردازش درخواست دارند.  
مطالب
Blazor 5x - قسمت دهم - مبانی Blazor - بخش 7 - مسیریابی
تا اینجا به صورت بسیار مختصری با نحوه‌ی مسیریابی برنامه‌های مبتنی بر Blazor توسط دایرکتیو page@ آشنا شدیم. برای مثال با داشتن تعریف زیر در ابتدای یک کامپوننت:
@page "/LearnRouting"

<h3>Learn Routing</h3>
این کامپوننت جدید، صرفنظر از محل قرارگیری فایل آن که برای مثال در پوشه‌ی Pages\LearnBlazor\LearnRouting.razor است، در مسیر https://localhost:5001/LearnRouting قابل دسترسی خواهد شد و برای تعریف مدخل منوی جدید آن، به کامپوننت Shared\NavMenu.razor مراجعه کرده و NavLink جدیدی را برای آن تعریف می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="LearnRouting">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Routing
    </NavLink>
</li>
در اینجا برچسب مدخل جدید تعریف شده، Learn Routing است و href لینک به آن، دقیقا به مسیریابی تعریف شده اشاره می‌کند.

یک نکته: مسیریابی‌های تعریف شده‌ی در Blazor، حساس به حروف کوچک و بزرگ نیستند.


امکان تعریف بیش از یک مسیریابی برای یک کامپوننت نیز وجود دارد

در کامپوننت‌های Blazor، محدودیتی از لحاظ تعداد بار تعریف دایرکتیو page@ وجود ندارد:
@page "/LearnRouting"
@page "/NewRouting"

<h3>Learn Routing</h3>
در این حالت می‌توان در حین تعریف یک مسیریابی جدید، مسیریابی قبلی را نیز حفظ کرد. در اینجا کامپوننت فوق، از طریق هر دو آدرس https://localhost:5001/LearnRouting و https://localhost:5001/NewRouting تعریف شده، قابل دسترسی‌است.


روش تعریف پارامترهای مسیریابی

تا اینجا اگر مسیر جدید https://localhost:5001/NewRouting/1/2 را درخواست کنیم چه اتفاقی رخ می‌دهد؟


در مورد نحوه‌ی تعریف قالب «یافت نشد» فوق، در قسمت دوم بیشتر بحث شد.
برای تعریف پارامترهای مسیریابی، می‌توان مسیریابی سومی را با پارامترهای مدنظر تعریف کرد که در مثال زیر، ذکر پارامتر دوم اختیاری است؛ چون سومین مسیریابی تعریف شده، امکان پردازش مسیرهایی با یک پارامتر را هم ممکن می‌کند:
@page "/LearnRouting"
@page "/NewRouting"
@page "/LearnRouting/{parameter1}"
@page "/LearnRouting/{parameter1}/{parameter2}"

<h3>Learn Routing</h3>

<p>Parameter1: @Parameter1</p>
<p>Parameter2: @Parameter2</p>

@code
{
    [Parameter]
    public string Parameter1 { set; get; }

    [Parameter]
    public string Parameter2 { set; get; }
}
سپس جهت دست‌یابی به مقادیر این پارامترها می‌توان در قسمت کدهای کامپوننت، از خواص عمومی مزین شده‌ی با ویژگی Parameter استفاده کرد. در اینجا هر خاصیت تعریف شده، باید هم نام پارامتر تعریف شده باشد (و این مورد نیز غیر حساس به حروف بزرگ و کوچک است).
پس از این تعاریف، مسیریابی مانند https://localhost:5001/LearnRouting/1 با یک پارامتر و یا https://localhost:5001/LearnRouting/1/2 که به همراه دو پارامتر است، قابل فراخوانی می‌شود.





روش تعریف لینک به سایر کامپوننت‌های Blazor

در ادامه کامپوننت جدید Pages\LearnBlazor\LearnAdvancedRouting.razor را اضافه می‌کنیم؛ با این محتوای آغازین:
@page "/LearnAdvancedRouting"

<h3>Learn Advanced Routing</h3>
در اینجا قصد نداریم لینک به این کامپوننت را به منوی اصلی برنامه اضافه کنیم؛ بلکه می‌خواهیم از طریق همان کامپوننت LearnRouting.razor ابتدای بحث، این مسیریابی را برقرار کنیم. برای اینکار یا می‌توان از یک anchor tag استاندارد استفاده کرد و یا همانند کامپوننت Shared\NavMenu.razor، از کامپوننت NavLink استفاده نمود. NavLink‌ها نیز همانند anchor tag‌های استاندارد HTML هستند، با این تفاوت که این کامپوننت، افزودن کلاس active مخصوص بوت استرپ را هم بر اساس فعال بودن مسیریابی مرتبط به آن، انجام می‌دهد ("class="nav-link active). به همین علت است که اگر گزینه‌ی منوی خاصی را انتخاب کنیم، این گزینه با رنگ متمایزی نشان داده می‌شود:


بنابراین یک روش تعریف لینک به کامپوننتی دیگر، استفاده از کامپوننت NavLink است که href آن به مسیریابی مقصد اشاره می‌کند:
<NavLink class="btn btn-secondary" href="LearnAdvancedRouting">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Advanced Routing
</NavLink>


ارسال کوئری استرینگ‌ها به کامپوننت‌های مختلف

پس از تعریف لینکی به کامپوننتی دیگر از درون یک کامپوننت، اکنون می‌خواهیم دو کوئری استرینگ param1 و param2 را نیز به آن ارسال کنیم:
<NavLink class="btn btn-secondary" href="LearnAdvancedRouting?param1=value1&param2=value2">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Advanced Routing
</NavLink>
در کامپوننت LearnAdvancedRouting برای دریافت این پارامترها، نیاز است آن‌ها را از URL جاری استخراج کرد:
@page "/LearnAdvancedRouting"
@inject NavigationManager NavigationManager

<h3>Learn Advanced Routing</h3>

<h4>Parameter 1 : @Param1</h4>
<h4>Parameter 2 : @Param2</h4>

@code
{
    string Param1;
    string Param2;

    protected override void OnInitialized()
    {
        base.OnInitialized();

        var absoluteUri = new Uri(NavigationManager.Uri);
        var queryParam = System.Web.HttpUtility.ParseQueryString(absoluteUri.Query);
        Param1 = queryParam["Param1"];
        Param2 = queryParam["Param2"];
    }
}
ابتدا سرویس توکار NavigationManager توسط دایرکتیو inject@ به کامپوننت جاری تزریق شده‌است که برای کار و دسترسی به آن، نیاز به تنظیمات ابتدایی خاصی نیست و پیشتر به مجموعه‌ی سرویس‌های برنامه افزوده شده‌است. برای نمونه توسط آن می‌توان به Uri در حال پردازش، دسترسی یافت. اکنون که این Uri را داریم، با استفاده از متد HttpUtility.ParseQueryString می‌توان به مجموعه‌ی کوئری استرینگ‌های ارسالی، به صورت key/value دسترسی یافت و برای مثال آن‌ها را در روال رویدادگردان OnInitialized، دریافت و با انتساب آن‌ها به دو فیلد تعریف شده، سبب نمایش مقادیر دریافتی شد:



هدایت به یک کامپوننت دیگر با کد نویسی

فرض کنید می‌خواهیم دکمه‌ای را اضافه کنیم که با کلیک بر روی آن، ما را به کامپوننت LearnRouting هدایت می‌کند:
@page "/LearnAdvancedRouting"
@inject NavigationManager NavigationManager

@*<NavLink href="/learnrouting" class="btn btn-secondary">Back to Routing</NavLink>*@
@*<a href="/learnrouting" class="btn btn-secondary">Back to Routing</a>*@
<button class="btn btn-secondary" @onclick="BackToRouting">Back to Routing</button>

@code
{
    private void BackToRouting()
    {
        NavigationManager.NavigateTo("learnrouting");
    }
}
در اینجا روش‌های مختلف تعریف لینک به کامپوننتی دیگر را مشاهده می‌کنید. یا می‌توان از کامپوننت NavLink استفاده کرد و یا از یک anchor tag استاندارد، که href هر دوی آن‌ها به مسیریابی مقصد اشاره می‌کنند و یا می‌توان با استفاده از سرویس NavigationManager و متد NavigateTo آن مانند کدهای فوق، سبب هدایت کاربر به صفحه‌ای دیگر شد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-10.zip
مطالب
کوئری نویسی در EF Core - قسمت هشتم - کوئری‌های بازگشتی
جدول اعضای این مجموعه، خود ارجاع دهنده طراحی شده‌است:
namespace EFCorePgExercises.Entities
{
    public class Member
    {
       // ...

        public virtual ICollection<Member> Children { get; set; }
        public virtual Member Recommender { set; get; }
        public int? RecommendedBy { set; get; }

       // ...
    }
}
در اینجا RecommendedBy، یک کلید خارجی نال پذیر است که به Id همین جدول اشاره می‌کند. دو خاصیت دیگر تعریف شده، مکمل این خاصیت عددی، جهت سهولت کوئری نویسی‌های EF-Core هستند که در قسمت‌های قبل نیز تعدادی کوئری را در این زمینه مشاهده کردید؛ مانند:
- تولید لیست کاربرانی که کاربر دیگری را توصیه کرده‌اند.
- تولید لیست کاربران، به همراه توصیه کننده‌ی آن‌ها.
- تولید لیست کاربران به همراه توصیه کننده‌ی آن‌ها، بدون استفاده از جوین.
- هر کاربر چه تعداد کاربر دیگری را توصیه کرده‌است؟

در این قسمت تعدادی مثال بازگشتی را می‌خواهیم بررسی کنیم.


مثال 1: رنجیره‌ی توصیه کنندگان کاربر با ID مساوی 27 را محاسبه کنید.

می‌خواهیم بدانیم چه کسی کاربر 27 را توصیه کرده و همچنین این کاربر نیز توسط چه شخص دیگری توصیه شده و به همین ترتیب تا بالاترین سطح ممکن.

روش معمول انجام این نوع کوئری‌ها استفاده از «WITH Hierachy» است. اما اگر بخواهیم بدون SQL نویسی مستقیم اینکار را انجام دهیم، می‌توان به صورت زیر عمل کرد:
var id = 27;
var entity27WithAllOfItsParents =
                        context.Members
                            .Where(member => member.MemId == id
                                            || member.Children.Any(m => member.MemId == m.RecommendedBy))
                            .ToList() //It's a MUST - get all children from the database
                            .FirstOrDefault(x => x.MemId == id);// then get the root of the tree


این کوئری ابتدا تمام رکوردهای جدول کاربران را لیست می‌کند. سپس خاصیت Recommender هر کدام را تا n سطح مقدار دهی می‌کند (خود EF-Core اینکار را انجام می‌دهد). تمام این اتفاقات تا قسمت ToList آن رخ می‌دهند. پس از آن یک FirstOrDefault سمت کاربر را هم داریم (LINQ to Objects). هدف از آن، بازگشت تنها ریشه‌ی مرتبط با ID=27 است و تمام Recommenderهای متصل به آن. این موارد را در تصویر ذیل بهتر می‌توانید مشاهده کنید:


لیست تمام کاربران وجود دارند. سپس سیزدهمین مورد آن، همان کاربر 27 است که توسط کاربر 20 توصیه شده. کاربر 20 توسط کاربر 5 توصیه شده و کاربر 5 توسط کاربر 1 و پس از آن خاصیت Recommender نال است که به معنای پایان پیمودن این زنجیره‌است.
بنابراین مرحله‌ی بعدی پس از یافتن ریشه‌ی کاربر 27، پیمودن خاصیت‌های Recommender به صورت بازگشتی است؛ کاری شبیه به متد FindParents زیر:
namespace EFCorePgExercises.Exercises.RecursiveQueries
{
    public static class RecursiveUtils
    {
        public static void FindParents(Member member, List<dynamic> actualResult)
        {
            if (member == null || member.Recommender == null)
            {
                return;
            }

            var item = member.Recommender;
            actualResult.Add(new { Recommender = item.MemId, item.FirstName, item.Surname });

            if (item.Recommender != null)
            {
                FindParents(item, actualResult);
            }
        }
    }
}
که به صورت زیر می‌تواند مورد استفاده قرار گیرد:
var actualResult = new List<dynamic>();
RecursiveUtils.FindParents(entity27WithAllOfItsParents, actualResult);


مثال 2: زنجیره‌ی توصیه شده‌های توسط کاربر با ID مساوی 1 را محاسبه کنید.

می‌خواهیم بدانیم کاربر 1، چه کسی را توصیه کرده و این کاربر نیز چه کاربر دیگری را توصیه کرده و به همین ترتیب تا پایین‌ترین سطح ممکن.
var id = 1;
var entity1WithAllOfItsDescendants =
                        context.Members
                            .Include(member => member.Children)
                            .Where(member => member.MemId == id
                                            || member.Children.Any(m => member.MemId == m.RecommendedBy))
                            .ToList() //It's a MUST - get all children from the database
                            .FirstOrDefault(x => x.MemId == id);// then get the root of the tree
این کوئری نیز شبیه به کوئری مثال قبلی است؛ با یک تفاوت. در اینجا Include(member => member.Children) هم ذکر شده‌است. هدف این است که EF-Core، خاصیت Children را تا n سطح ممکن به صورت خودکار مقدار دهی کند و این مورد دقیقا هدف اصلی مثال جاری است.
وجود Include، سبب تولید یک چنین کوئری می‌شود که در آن جدول کاربران با خودش جوین شده‌است:
SELECT   [m].[MemId],
         [m].[Address],
         [m].[FirstName],
         [m].[JoinDate],
         [m].[RecommendedBy],
         [m].[Surname],
         [m].[Telephone],
         [m].[ZipCode],
         [m0].[MemId],
         [m0].[Address],
         [m0].[FirstName],
         [m0].[JoinDate],
         [m0].[RecommendedBy],
         [m0].[Surname],
         [m0].[Telephone],
         [m0].[ZipCode]
FROM     [Members] AS [m]
         LEFT OUTER JOIN
         [Members] AS [m0]
         ON [m].[MemId] = [m0].[RecommendedBy]
WHERE    ([m].[MemId] = 1)
         OR EXISTS (SELECT 1
                    FROM   [Members] AS [m1]
                    WHERE  ([m].[MemId] = [m1].[RecommendedBy])
                           AND ([m].[MemId] = [m1].[RecommendedBy]))
ORDER BY [m].[MemId], [m0].[MemId];
پس از آن باید خاصیت member.Children را تا هر سطح ممکن به صورت بازگشتی پیمود تا به جواب اصلی این مثال رسید:
namespace EFCorePgExercises.Exercises.RecursiveQueries
{
    public static class RecursiveUtils
    {
        public static void FindChildren(Member member, List<dynamic> actualResult)
        {
            if (member == null)
            {
                return;
            }

            foreach (var item in member.Children)
            {
                actualResult.Add(new { item.MemId, item.FirstName, item.Surname });
                if (item.Children != null)
                {
                    FindChildren(item, actualResult);
                }
            }
        }
    }
}
که به صورت زیر می‌تواند مورد استفاده قرار گیرد:
var actualResult = new List<dynamic>();
RecursiveUtils.FindChildren(entity1WithAllOfItsDescendants, actualResult);


کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت دوم - اجرای قواعد اعتبارسنجی تعریف شده
در قسمت قبل، روش تعریف قواعد اعتبارسنجی را با استفاده از کتابخانه‌ی Fluent Validation بررسی کردیم. در این قسمت می‌خواهیم این قواعد را به صورت خودکار به یک برنامه‌ی ASP.NET Core معرفی کرده و سپس از آن‌ها استفاده کنیم.


روش اول: استفاده‌ی دستی از اعتبارسنج کتابخانه‌ی Fluent Validation

روش‌های زیادی برای استفاده‌ی از قواعد تعریف شده‌ی توسط کتابخانه‌ی Fluent Validation وجود دارند. اولین روش، فراخوانی دستی اعتبارسنج، در مکان‌های مورد نیاز است. برای اینکار در ابتدا نیاز است با اجرای دستور «dotnet add package FluentValidation.AspNetCore»، این کتابخانه را در پروژه‌ی وب خود نیز نصب کنیم تا بتوانیم از کلاس‌ها و متدهای آن استفاده نمائیم. پس از آن، روش دستی کار با کلاس RegisterModelValidator که در قسمت قبل آن‌را تعریف کردیم، به صورت زیر است:
using FluentValidationSample.Models;
using Microsoft.AspNetCore.Mvc;

namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult RegisterValidateManually(RegisterModel model)
        {
            var validator = new RegisterModelValidator();
            var validationResult = validator.Validate(model);
            if (!validationResult.IsValid)
            {
                return BadRequest(validationResult.Errors[0].ErrorMessage);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
ساده‌ترین روش کار با RegisterModelValidator تعریف شده، ایجاد یک وهله‌ی جدید از آن و سپس فراخوانی متد Validate آن شیء است. در این حالت می‌توان کنترل کاملی را بر روی قالب پیام نهایی بازگشت داده شده داشت. برای مثال در اینجا اولین خطای بازگشت داده شده، به اطلاع کاربر رسیده‌است. حتی می‌توان کل شیء Errors را نیز بازگشت داد.

یک نکته: متد الحاقی AddToModelState که در فضای نام FluentValidation.AspNetCore قرار دارد، امکان تبدیل نتیجه‌ی اعتبارسنجی حاصل را به ModelState استاندارد ASP.NET Core نیز میسر می‌کند:
public IActionResult RegisterValidateManually(RegisterModel model)
{
    var validator = new RegisterModelValidator();
    var validationResult = validator.Validate(model);
    if (!validationResult.IsValid)
    {
       validationResult.AddToModelState(ModelState, null);
       return BadRequest(ModelState);
    }

    // TODO: Save the model

    return Ok();
}


روش دوم: تزریق اعتبارسنج تعریف شده در سازنده‌ی کنترلر

بجای وهله سازی دستی RegisterModelValidator و ایجاد وابستگی مستقیمی به آن، می‌توان از روش تزریق وابستگی‌های آن نیز استفاده کرد. در این حالت اعتبارسنج RegisterModelValidator با طول عمر Transient به سیستم تزریق وابستگی‌ها معرفی شده:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>();
            services.AddControllersWithViews();
        }
 و پس از آن با تزریق <IValidator<RegisterModel به سازنده‌ی کنترلر مدنظر، می‌توان به امکانات آن همانند روش اول، دسترسی یافت:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IValidator<RegisterModel> _registerModelValidator;

        public HomeController(IValidator<RegisterModel> registerModelValidator)
        {
            _registerModelValidator = registerModelValidator;
        }

        [HttpPost]
        public IActionResult RegisterValidatorInjection(RegisterModel model)
        {
            var validationResult = _registerModelValidator.Validate(model);
            if (!validationResult.IsValid)
            {
                return BadRequest(validationResult.Errors[0].ErrorMessage);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
به این ترتیب new RegisterModelValidator را با وهله‌ای از <IValidator<RegisterModel، تعویض کردیم. کار با این روش بسیار انعطاف پذیر بوده و همچنین قابلیت آزمون پذیری بالایی را نیز دارد.


روش سوم: خودکار سازی اجرای یک تک اعتبارسنج تعریف شده

اگر متد الحاقی AddFluentValidation را به صورت زیر به سیستم معرفی کنیم:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>();
            services.AddControllersWithViews().AddFluentValidation();
        }
سبب اجرای خودکار تمام IValidatorهای اضافه شده‌ی به سیستم، پیش از اجرای اکشن متد مرتبط با آن‌ها می‌شود. برای مثال اگر اکشن متدی دارای پارامتری از نوع RegisterModel بود، چون IValidator مخصوص به آن به سیستم تزریق وابستگی‌ها معرفی شده‌است، متد الحاقی AddFluentValidation، کار وهله سازی خودکار این IValidator و سپس فراخوانی متد Validate آن‌را به صورت خودکار انجام می‌دهد. به این ترتیب، قطعه کدهایی را که تاکنون نوشتیم، به صورت زیر خلاصه خواهند شد که در آن‌ها اثری از بکارگیری کتابخانه‌ی FluentValidation مشاهده نمی‌شود:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        [HttpPost]
        public IActionResult RegisterValidatorAutomatically(RegisterModel model)
        {
            if (!ModelState.IsValid)
            {
                // re-render the view when validation failed.
                return View(model);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
زمانیکه model به سمت اکشن متد فوق ارسال می‌شود، زیرساخت model-binding موجود در ASP.NET Core، اینبار کار اعتبارسنجی آن‌را توسط RegisterModelValidator به صورت خودکار انجام داده و نتیجه‌ی آن‌را به ModelState اضافه می‌کند که برای مثال در اینجا سبب رندر مجدد فرم شده که تمام مباحث tag-helper‌های استانداردی مانند asp-validation-summary و asp-validation-for پس از آن به صورت متداولی و همانند قبل، قابل استفاده خواهند بود.

نکته 1: تنظیمات فوق برایASP.NET Web Pages و PageModels نیز یکی است. فقط با این تفاوت که اعتبارسنج‌ها را فقط می‌توان به مدل‌هایی که به صورت خواص یک page model تعریف شده‌اند، اعمال کرد و نه به کل page model.

نکته 2: اگر کنترلر شما به ویژگی [ApiController] مزین شده باشد:
namespace FluentValidationSample.Web.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class HomeController : Controller
    {
        [HttpPost]
        public IActionResult RegisterValidatorAutomatically(RegisterModel model)
        {
            // TODO: Save the model

            return Ok();
        }
    }
}
در این حالت دیگر نیازی به ذکر if (!ModelState.IsValid) نیست و خطای حاصل از شکست اعتبارسنجی، به صورت خودکار توسط FluentValidation تشکیل شده و بازگشت داده می‌شود (پیش از رسیدن به بدنه‌ی اکشن متد فوق) و برای نمونه یک چنین شکل و خروجی خودکاری را پیدا می‌کند:
{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|84df05e2-41e0d4841bb61293.",
    "errors": {
        "FirstName": [
            "'First Name' must not be empty."
        ]
    }
}
اگر علاقمند به سفارشی سازی این خروجی خودکار هستید، باید به این صورت با تنظیم ApiBehaviorOptions و مقدار دهی نحوه‌ی تشکیل ModelState نهایی، عمل کرد:
        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            // override modelstate
            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory = context =>
                {
                    var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => p.ErrorMessage)).ToList();
                    return new BadRequestObjectResult(new
                    {
                        Code = "00009",
                        Message = "Validation errors",
                        Errors = errors
                    });
                };
            });
        }


روش چهارم: خودکار سازی ثبت و اجرای تمام اعتبارسنج‌های تعریف شده

و در آخر بجای معرفی دستی تک تک اعتبارسنج‌های تعریف شده به سیستم تزریق وابستگی‌ها، می‌توان تمام آن‌ها را با فراخوانی متد RegisterValidatorsFromAssemblyContaining، به صورت خودکار از یک اسمبلی خاص استخراج نمود و با طول عمر Transient، به سیستم معرفی کرد. در این حالت متد ConfigureServices به صورت زیر خلاصه می‌شود:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews().AddFluentValidation(
                fv => fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>()
            );
        }
در اینجا امکان استفاده‌ی از متد fv.RegisterValidatorsFromAssembly نیز برای معرفی اسمبلی خاصی مانند ()Assembly.GetExecutingAssembly نیز وجود دارد.


سازگاری اجرای خودکار FluentValidation با اعتبارسنج‌های استاندارد ASP.NET Core

به صورت پیش‌فرض، زمانیکه FluentValidation اجرا می‌شود، اگر اعتبارسنج دیگری نیز در سیستم تعریف شده باشد، اجرا خواهد شد. به این معنا که برای مثال می‌توان FluentValidation و DataAnnotations attributes و IValidatableObject‌ها را با هم ترکیب کرد.
اگر می‌خواهید این قابلیت را غیرفعال کنید و فقط سبب اجرای خودکار FluentValidationها شوید، نیاز است تنظیم زیر را انجام دهید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews().AddFluentValidation(
       fv =>
       {
           fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>();
           fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
       }
    );
}
مطالب
Garbage Collector در #C - قسمت سوم
در قسمت قبلی درباره تفاوت‌های Stack و Heap، صحبت کرده و به این نتیجه رسیدیم که برای آزادسازی حافظه Heap، در صورتی‌که نخواهیم اینکار را بصورت دستی انجام دهیم، نیاز به Garbage Collector پیدا خواهیم کرد.


تاریخچه‌ای مختصر از GC در NET.

ایده اولیه ایجاد Garbage Collector در NET.، در سال 1990 بود که در آن زمان، مایکروسافت مشغول پیاده سازی خود از JavaScript بنام JScript بود. در ابتدا JScript توسط تیمی چهار نفره توسعه داده میشد و در آن زمان یکی از اعضای این تیم به نام Patrick Dussud که بعنوان پدر Garbage Collector در NET. شناخته میشود، یک Conservative GC را داخل تیم توسعه داد. در آن زمان CLR ای وجود نداشت و Patrick Dussud برروی JVM کار میکرد.

مایکروسافت سعی بر پیاده سازی نسخه‌ای اختصاصی از JVM را برای خود بجای ایجاد چیزی شبیه به NET Runtime. فعلی داشت؛ اما بعد از شکل گیری تیم CLR، به این نتیجه رسیدند که JVM برای آنها محدودیت‌هایی را ایجاد میکند و به همین دلیل شروع به ایجاد Environment خود کردند.

با این تصمیم، Patrick Dussud مجددا یک GC جدید را با ایده "بهترین GC ممکن" با زبان LISP که در آن بیشترین مهارت را داشت، بصورت Prototype نوشت و سپس یک Transpiler از LISP را به ++C نوشت که کدهای آن قابل استفاده در Runtime مایکروسافت باشد.

کدهای فعلی مربوط به Garbage Collector مورد استفاده در NET. در این فایل از ریپازیتوری runtime مایکروسافت قابل دسترسی هستند. در حال حاضر خانم Maoni Stephens مدیر فنی تیم GC مایکروسافت هستند که کنفرانس‌ها و مقالات زیادی نیز درباره نکات مختلف پیاده سازی GC در بلاگ خود نوشته و ارائه کرده‌اند.


در حال حاضر، سه حالت (flavor) از GC در NET. تعبیه شده‌است و هرکدام از این حالات برای انواع مختلفی از برنامه‌ها بهینه شده‌است که در ادامه به بررسی آنها میپردازیم.


Server GC

این نوع GC برای برنامه‌های سمت سرور نظیر ASP.NET Core و WCF بهینه سازی شده‌است که تعداد ریکوئست‌های زیادی به آنها وارد میشود و هر ریکوئست باعث allocate شدن اشیا مختلفی شده و بطور کلی، نرخ allocation و deallocation در آنها بالاست.

Server GC به ازای هر پردازنده، از یک Heap و یک GC Thread مجزا استفاده میکند. این بدین معناست که اگر شما یک پردازنده را با هشت Core داشته باشید، در زمان Garbage Collection، روی هرکدام از Coreها یک Heap و GC Thread مستقل وجود دارد که عمل Garbage Collection را انجام میدهند.

این شکل عملکرد باعث میشود که Collection، در سریعترین زمان ممکن، بدون وقفه اضافه انجام شود و برنامه شما اصطلاحا ((Freeze)) نشود.

Server GC فقط روی پردازنده‌های چند هسته‌ای قابل اجراست و اگر سعی کنید برنامه خود را روی یک سیستم با پردازنده تک هسته‌ای در حالت Server GC اجرا کنید، بصورت خودکار برنامه شما از Non-Concurrent Workstation GC استفاده کرده و اصطلاحا Fallback خواهد شد.

اگر نیاز دارید که در برنامه‌هایی به‌غیر از Server-Side Applicationها، نظیر WPF و Windows Service‌ها و ... از این نوع GC استفاده کنید (به شرط چند هسته بودن پردازنده)، میتوانید این تنظیمات را به فایل app.config یا web.config خود اضافه کنید:
<configuration>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

همچنین در برنامه‌های NET Core.ای نیز میتوانید این تنظیمات را داخل فایل csproj. برنامه خود اضافه کنید:
<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>


Concurrent Workstation GC


این حالت، حالت پیشفرض مورد استفاده در برنامه‌های Windows Forms و Windows Service است. این حالت از GC برای برنامه‌هایی بهینه شده‌است که در آنها، هنگام وقوع Garbage Collection، برنامه توقف و مکث حتی چند لحظه‌ای نداشته و Collection باعث نشود که کاربر نتواند روی یک دکمه کلیک کند و اصطلاحا برنامه ((Unresponsive)) شود.

برای فعالسازی Concurrent Workstation GC این تنظیمات را داخل config برنامه خود باید اعمال کنید:
<configuration>
  <runtime>
    <gcConcurrent enabled="true" />
  </runtime>
</configuration>


Non-Concurrent Workstation GC


این حالت شبیه به حالت Server GC است؛ با این تفاوت که عمل Collection روی Thread ای که درخواست allocate کردن یک object را کرده است، صورت میگیرد.

برای مثال:

  • Thread شماره یک درخواست allocate کردن یک string با طول 10000 کاراکتر را میدهد.
  • حافظه، فضای کافی برای تخصیص این حجم از حافظه را نداشته و سعی میکند با اجرای Garbage Collector، این حجم فضای مورد نیاز از حافظه را خالی کند.
  • CLR تمام Thread‌های برنامه را متوقف میکند و Garbage Collector شروع به کار کرده و اشیا بلااستفاده «روی Thread ای که آن را فراخوانی کرده است» را Collect میکند.
  • بعد از پایان Collection، تمامی Threadهای برنامه که در مرحله قبل متوقف شده بودند، مجددا شروع به کار خواهند کرد.


این حالت از GC برای برنامه‌های Server-Side ای که برروی پردازنده تک هسته‌ای اجرا میشوند، پیشنهاد میشود. برای فعالسازی این حالت، تنظیمات داخل config برنامه به این صورت باید تغییر پیدا کند:
<configuration>
  <runtime>
    <gcConcurrent enabled="false" />
  </runtime>
</configuration>



این جدول، کمک خواهد کرد که بر اساس نوع برنامه خود، تنظیمات درستی را برای GC اعمال نمایید (در اکثر موارد، تنظیمات پیشفرض بهترین انتخاب بوده و نیازی به تغییر روند کار GC نیست):

 Server GC  Non-Concurrent Workstation  Concurrent Workstation  
 Maximize throughput on multi-processor machines for server apps that create multiple threads to handle the same types of requests.  Maximize throughput on single-processor machines.  Balance throughput and responsiveness for client apps with UI. Design Goal 
1 per processor ( hyper thread aware )  1  Number of Heaps
 1 dedicated GC thread per processor The thread which performs the allocation that triggers the GC. The thread which performs the allocation that triggers the GC.  GC Threads
 EE is suspended during a GC. EE is suspended during a GC. EE is suspended much shorter but several times during a GC.  Execution Engine
Suspension
<gcServer enabled="true">
<gcConcurrent enabled="false">
 <gcConcurrent enabled="true"> 
 Config Setting
Non-Concurrent Workstation GC      On a single
processor
(fallback)
مطالب
Best practiceهای یک پروژه Blazor
Blazor، چارچوبی است ارائه شده توسط مایکروسافت که به ما اجازه می‌دهد برنامه‌های تعاملی وب را با CSharp و بدون JavaScript بنویسیم. ‌Blazor از اساس، Component based بوده و در برنامه‌هایی که Backend نیز با CSharp نوشته شده باشد ( مثلا با ASP.NET Core) امکان به اشتراک گذاری کد بین کلاینت و سرور را نیز فراهم می‌کند. معماری Blazor معماری‌ای مدرن است که در دل خود از امکانات CSharp نیز به خوبی بهره برده است تا بتوان پروژه را به‌صورتی که قابلیت نگهداری بالایی داشته باشد، توسعه داد. Blazor در ذات معماری خود امکان نوشتن برنامه‌های Native موبایل را نیز می‌دهد و برای اثبات این مهم چندین دمو برای Proof of concept ارائه شده است؛ اما تا به امروز امکانی که به صورت Production ready باشد، ارائه نشده است.
Blazor به دو شکل کار می‌کند. Blazor Server و Blazor Client
در Blazor Client با استفاده از پشتیبانی نزدیک به 90% ای مرورگرها از Web Assembly که اجازه اجرای کدهای غیر JavaScript ای را در مرورگر می‌دهد، ابتدا DLLها به همراه HTML-CSS و عکس‌ها و ... دانلود شده و برنامه به صورت Single Page App کار می‌کند. در این مدل شما می‌توانید از تکنیک Pre rendering یا SSR نیز استفاده کنید تا تجربه کاربری بهتری را نیز ارائه دهید و یا در بحث SEO بهتر عمل کنید.
‌Blazor در سمت کلاینت از NET Standard 2.1 پشتیبانی می‌کند که عملا به شما اجازه می‌دهد بازه‌ی گسترده‌ای از Nuget Packageها را استفاده کنید.

البته Blazor Client به صورت Preview ارائه شده است و "فعلا" دو مشکل را دارد:
۱- امکان دیباگ خوبی ندارد.
۲- در عمل باید فایل‌های اجرایی برنامه به صورت wasm یا web assembly دانلود شوند که در این حالت سرعتی بسیار بالا و بیش از جاوااسکریپت خواهند داشت؛ اما تا این لحظه این فایلها به صورت DLL دانلود میشوند و در سمت مرورگر "تفسیر" میشوند که باعث میشود سرعت گاه تا 70 برابر کمتر شود.
البته این دو مشکل در زمان ارائه نسخه NET 5. حل خواهند شد و در پروژه نسخه نهایی خود این دو مشکل را نخواهد داشت.

Blazor مدل اجرای دومی نیز دارد که به آن Blazor Server نیز می‌گوییم. در این مدل کدها تماما سمت سرور اجرا می‌شوند و تعاملات UI به صورت Web Socket به کلاینت منتقل و یا از آن دریافت می‌شوند که در این مدل، امکان Debug بدون کوچک‌ترین مشکلی کار می‌کند و مشکلی از بابت کندی دانلود و اجرای DLLها ندارد. فقط چون کدها تماما سمت سرور اجرا می‌شوند، در زمانیکه ارتباط شبکه‌ای، مناسب نباشد، می‌توانیم در سناریوهای مختلف، شاهد لگ و کندی باشیم.

آینده‌ی Blazor در مدل اول آن تعریف شده‌است و در گذر زمان برای مدل Blazor Server، نمیتوان آینده‌ی زیادی را متصور بود. اما نکته‌ی مهم و جالب اینجاست که کد پروژه در هر دو مدل یکی است و فقط Configuration این دو از هم متفاوت است. پس اگر علاقمند به شروع به استفاده از Blazor هستید، یک راه این است که با مدل Blazor Server شروع و بعدا با تغییر Configuration، به Blazor Client سوئیچ کنید. البته در این میان باید به نکاتی دقت کنید:
۱- Blazor Server از NET Core 3.1. پشتیبانی می‌کند و Blazor Client از NET Standard 2.1. در مواقعی خاص ممکن است یک کلاس یا یک متد در NET Core 3.1. باشد و در NET Standard 2.1. نباشد.
۲- در Blazor Server شما حتی می‌توانید با Entity Framework Core مثلا به Sql Server وصل شوید؛ ولی طبیعتا Browser امکان اتصال مستقیم به Sql Server را در مدل Client به شما نخواهد داد.
این موارد باعث می‌شود که اگر پروژه را با Blazor Server شروع کنید، شاید در آن کارهایی را انجام دهید که در BlazorServer کار می‌کنند، ولی در BlazorClient کار نمی‌کنند و این باعث شود بعدا که نسخه نهایی و کامل BlazorClient آماده شد، شاید نتوانید به آن، به صورت ساده و آسانی سوئیچ کنید.

ایده آل این است که پروژه‌ای داشته باشید که به سادگی عوض کردن یک کلمه، بین این دو مدل سوئیچ کنید و بر صحت عملکرد پروژه در هر دو حالت نظارت داشته باشید. برای رسیدن به این مهم، پروژه‌ای را ساخته‌ام به نام BlazorDualMode که عمده‌ی بررسی‌ها را به صورت اتوماتیک انجام می‌دهد و عملا تضمینی بر مهاجرت آسان شما از BlazorServer به BlazorClient در آینده خواهد بود
اگر در نت جستجو کنید، پروژه‌هایی با این اسم را خواهید دید؛ اما این پروژه دو مزیت مهم را دارد که الباقی فاقد آن هستند:
۱- مدل Blazor Client آن دارای تکنیک Pre rendering یا SSR است.
۲- پروژه Api آن، از پروژه‌ی Blazor آن جداست. اگر پروژه‌ی Api را پروژه‌ای بدانیم که به همراه Model‌، Data و ... امکان دسترسی به DbContext و دیتابیس را دارد، جدا بودن پروژه‌ی Blazor باعث می‌شود که حتی در مدل Server آن نیز مجبور باشیم دیتا را از Api به صورت Rest Api call بگیریم و به واسطه پروژه Shared مابین Api و Blazor نهایت Dto‌ها و سایر کدهای مشترک را ببینیم.
البته در پروژه DualMode فقط پروژه Api درست شده‌است و ما کاری با جزئیات آن، اینکه مثلا CQRS می‌خواهیم یا نه، آیا میخواهیم لایه‌ای کار کنیم و ... نداریم و فقط روی موارد مرتبط با Blazor متمرکز شده‌ایم.
در پروژه یک فایل داریم به نام Directory.build.props. زمانیکه چنین فایلی در فولدری قرار بگیرد، تمامی موارد نوشته شده در آن به صورت ضمنی در تمامی فایل‌های csproj زیر مجموعه آن اعمال می‌شود.
در این فایل داریم:
<Project>
  <PropertyGroup>
    <BlazorMode>Client</BlazorMode>
    <DefineConstants Condition=" '$(BlazorMode)' == 'Client' ">$(DefineConstants);BlazorClient</DefineConstants>
    <DefineConstants Condition=" '$(BlazorMode)' == 'Server' ">$(DefineConstants);BlazorServer</DefineConstants>
    <LangVersion>8.0</LangVersion>
  </PropertyGroup>
</Project>
ابتدا یک متغیر را تعریف کرده‌ایم، به نام BlazorMode که می‌تواند یا Server باشدو یا Client، که فعلا Client است و به راحتی می‌توانید آن‌را عوض کنید.
در ادامه Define Constant کرده‌ایم که اجازه می‌دهد کدهای CSharp دخیل در Configuration پروژه Blazor را شرطی کنیم. برای مثال بنویسیم:
#if BlazorClient
...
#elif BlazorServer
...
#endif
این if‌ها که با # شروع می‌شوند، در هنگام Compile چک می‌شوند و کدی که در شرط با نتیجه true قرار بگیرد، Compile میشود و در خروجی برنامه قرار می‌گیرد و کدی که در شرط با نتیجه false قرار بگیرد، از اساس Compile نمیشود.
نسخه CSharp تمامی پروژه‌ها نیز 8.0 قرار داده شده است.

پروژه‌ی BlazorDualMode.Api آن پروژه‌ای است که Api Controllerها در آن قرار می‌گیرند و همچنین در حالت Blazor Client، پروژه را برای مرورگرها ارائه یا Serve می‌کند. به واقع در این مدل، درخواستی که به هیچ Api Controller ای نرسد، به Blazor تحویل میشود. Blazor نیز ابتدا به دنبال Component ای می‌گردد که برای Route مربوطه نوشته شده باشد و آن را اجرا می‌کند و سپس Response آماده، به کلاینت ارسال می‌شود. در ادامه، مرورگر فایل‌های DLL را دانلود و برنامه به صورت Single Page App به کار خود ادامه می‌دهد.
پروژه BlazorDualMode.Shared  پروژه‌ای است که کد مشترک بین Api و Blazor در آن قرار می‌گیرد. برای مثال میتوانید Dtoها را در این قسمت قرار دهید.
پروژه BlazorDualMode.Web پروژه‌ای است که در آن Component‌های Blazor قرار دارند. در حالت Server این پروژه نیز قابلیت اجرا می‌یابد و باید با امکانات Visual Studio یا IDE دلخواه خود پروژه Web و Api را به صورت همزمان اجرا کنید تا به درستی کار کند.

فایلهای Program.cs، Startup.cs و همچنین خود csproj‌ها و در نهایت فایل Host.cshtml، نهایت تفاوت این دو حالت بوده و کدهای بیزینسی پروژه و حتی Componentها و Api Controllerها در هر دو حالت یکی هستند. Configuration با شرط if server یا if client هندل شده‌اند و درک جزئیات تنظیمات مربوطه نیاز به تسلط بر روی خود Blazor را دارد که از موضوع این پست خارج است؛ ولی در صورت داشتن هر گونه سوالی، می‌توانید از قسمت پرسش و پاسخ همین سایت استفاده کنید.
مطالب
React 16x - قسمت 10 - ترکیب کامپوننت‌ها - بخش 4 - یک تمرین
در قسمت 6، تمرینی را جهت پیاده سازی نمایش لیست یک سری فیلم، انجام دادیم. در اینجا قصد داریم این تمرین را جهت دریافت امتیاز و Like از کاربر، به ازای هر ردیف نمایش داده شده، تکمیل کنیم.


بررسی ساختار کامپوننت Like

در پوشه‌ی components، ابتدا پوشه‌ی جدید common را ایجاد می‌کنید. در اینجا تمام کامپوننت‌های عمومی برنامه را که منحصر به دومین آن برنامه نیستند، قرار می‌دهیم. کامپوننت‌هایی را که اگر آن‌ها را به برنامه‌های دیگری نیز کپی کردیم، بدون هیچ مشکلی قابلیت استفاده‌ی مجدد را داشته باشند و متصل به سرویس‌ها و زیرساخت برنامه‌ی جاری نباشند. سپس در پوشه‌ی common، فایل جدید src\components\common\like.jsx را ایجاد می‌کنیم و داخل آن توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت Like را ایجاد می‌کنیم.
ساختار کلی این کامپوننت به صورت زیر است:
- ورودی این کامپوننت به این صورت است که در آن مشخص شده آیا یک فیلم، مورد علاقه واقع شده یا خیر؛ مانند خاصیت liked که یک boolean است. اگر true باشد، یک آیکن قلب توپر را نمایش می‌دهد و برعکس.
- خروجی این کامپوننت نیز به صورت یک رخ‌داد است. هر زمانیکه بر روی آیکن قلب آن کلیک می‌شود، این کامپوننت یک ر‌خ‌داد onClick را سبب خواهد شد. اکنون هر کامپوننت دیگری که در حال استفاده‌ی از آن است، مطلع شده و خاصیت liked شیء مرتبط را تغییر می‌دهد.

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

فعلا ساختار ابتدایی آن‌را به رندر یک قلب خالی که توسط قلم آیکن‌های font-awesome تامین می‌شود، تنظیم می‌کنیم:
import React, { Component } from "react";


class Like extends Component {
  render() {
    return <i className="fa fa-heart-o" aria-hidden="true"></i>;
  }
}

export default Like;


نمایش ابتدایی کامپوننت Like در جدول لیست فیلم‌ها

فعلا مهم نیست که این کامپوننت کار خاصی را انجام نمی‌دهد. فقط قصد داریم آن‌را در UI برنامه نمایش دهیم. به همین جهت ابتدا یک ستون جدید را مخصوص آن، در جدول فعلی نمایش لیست فیلم‌ها، ایجاد کرده و المان آن‌را درج می‌کنیم. برای این منظور به فایل movies.jsx مراجعه کرده و ابتدا این کامپوننت را import می‌کنیم:
import Like from "./common/like";

سپس در سرستون‌های جدول، یک th جدید را تعریف می‌کنیم تا ستونی برای درج آن ایجاد شود. همچنین در قسمت بدنه‌ی جدول، پیش از دکمه‌ی حذف، یک td مخصوص درج المان </Like> را اضافه می‌کنیم:


تا اینجا ستون جدید Like را مشاهده می‌کنید که کار رندر کامپوننت‌های Like در آن انجام شده‌است.


واکنش نشان دادن به ورودی‌ها، در کامپوننت Like

در ادامه باید این کامپوننت بر اساس مقدار Boolean ای که از والد خود دریافت می‌کند، یک آیکن قلب توپر و یا خالی را نمایش دهد. برای این منظور فعلا در کامپوننت movies، جائیکه المان کامپوننت Like درج شده‌است، ویژگی جدید liked را به مقدار ثابت true تنظیم می‌کنیم </Like liked={true}> تا بتوان قسمت props این کامپوننت را تکمیل کرد.
در کامپوننت Like، تفاوت بین آیکن قلب توپر و خالی در یک o- در انتهای کلاس‌های font-awesome است:
import React, { Component } from "react";

class Like extends Component {
  render() {
    let classes = "fa fa-heart";
    if (!this.props.liked) {
      classes += "-o";
    }
    return <i className={classes} aria-hidden="true"></i>;
  }
}

export default Like;
در اینجا اگر بر اساس مقدار ورودی this.props.liked، یک مقدار false را دریافت کردیم، به classes یک o- را اضافه می‌کنیم تا یک آیکن قلب خالی را رندر کند. سپس این classes را به خاصیت className انتساب داده‌ایم.
پس از این تغییرات اگر برنامه را ذخیره کرده و مجددا در مرورگر بارگذاری کنیم، با توجه به تنظیم liked={true} در کامپوننت movies، ستون like آن با آیکن‌های قلب توپر نمایش داده می‌شود که بیانگر واکنش نشان دادن صحیح به ورودی‌ها در کامپوننت Like است:



پویا سازی مقدار پیش‌فرض ویژگی liked در کامپوننت movies

برای پویاسازی نمایش مقدار liked در کامپوننت movies، از آنجائیکه هر ردیف بیانگر یک شیء movie است، می‌توان به این صورت عمل کرد:
<Like liked={movie.liked} />
البته اگر به فایل fakeMovieService.js مراجعه کنید، خاصیت liked در ساختار اشیاء فیلم‌ها وجود ندارد که فعلا آن‌را برای اولین شیء تعریف شده، اضافه می‌کنیم:
const movies = [
  {
    _id: "5b21ca3eeb7f6fbccd471815",
    title: "Terminator",
    genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
    numberInStock: 6,
    dailyRentalRate: 2.5,
    publishDate: "2018-01-03T19:04:28.809Z",
    liked: true
  },
پس از این تغییرات، اکنون خروجی برنامه در مرورگر به صورت زیر تغییر کرده که اولین آیتم آن بر اساس مقدار تنظیم شده‌ی فوق، با آیکن قلب توپر نمایش داده شده‌است:



افزودن رخ‌داد کلیک به کامپوننت Like

برای اینکه کامپوننت Like، رویداد کلیک بر روی آیکن قلب را به والد خود گزارش دهد، ابتدا ویژگی جدید onClick را بر روی تعریف المان آن در کامپوننت movies اضافه می‌کنیم:
 <Like liked={movie.liked} onClick={() => this.handleLike(movie)} />
که به متد handleLike در همان کامپوننت متصل خواهد شد:
handleLike = movie => {
    console.log("handleLike", movie);
  };
سپس در کامپوننت Like، این ویژگی onClick را از طریق خاصیت props دریافت کرده و به رویداد onClick المان نمایش آیکن، متصل می‌کنیم:
    return (
      <i
        className={classes}
        onClick={this.props.onClick}
        aria-hidden="true"
        style={{ cursor: "pointer" }}
      ></i>
);
اینبار اگر بر روی المان نمایش آیکن کلیک شود، سبب فراخوانی متد handleLike والد متصل به آن خواهد شد.
در اینجا همچنین style این المان نیز جهت نمایش cursor با آیکن pointer، توسط یک شیء از نوع inline style تنظیم شده‌است.

یک نکته: کامپوننت Like تا اینجا یک controlled component است؛ دارای state نیست و همچنین تمام اطلاعات خودش را از طریق props تامین می‌کند و تنها دارای یک متد render است. بنابراین اگر علاقمند بودید می‌توان این کامپوننت را به یک «Stateless Functional Component» که در قسمت 8 معرفی شد نیز تبدیل کرد.


تغییر حالت کامپوننت Like جهت نمایش تغییرات

تا اینجا کامپوننت Like ما می‌تواند ورودی true/false را به آیکن‌های متناظری تبدیل کند. همچنین اگر بر روی این آیکن کلیک شود، آن‌را توسط رخ‌دادی به والد خود اطلاع رسانی می‌کند. اکنون می‌خواهیم با تکمیل متد handleLike، خاصیت like اشیاء انتخابی (آیکن‌هایی که بر روی آن‌ها کلیک شده‌اند) را از true به false و برعکس تبدیل کرده و سپس UI را نیز به روز رسانی کنیم:
  handleLike = movie => {
    console.log("handleLike", movie);
    const movies = [...this.state.movies]; // cloning an array
    const index = movies.indexOf(movie);
    movies[index] = { ...movies[index] }; // cloning an object
    movies[index].liked = !movies[index].liked;
    this.setState({ movies });
  };
با یک چنین مثالی که در آن cloning اشیاء و آرایه‌ها صورت می‌گیرد، پیشتر آشنا شده‌اید. هدف از cloning، قطع ارتباط شیء، یا آرایه‌ی ایجاد شده، از شیء، یا آرایه‌ی اصلی است تا با اعمال تغییرات بر روی شیء clone شده، تغییری در شیء اصلی صورت نگیرد؛ چون در React مجاز به تغییر مستقیم اشیاء state نیستیم.
پس از این تغییرات اگر برنامه را اجرا کنیم، با کلیک بر روی هر آیکن، عکس آن آیکن نمایش داده می‌شود؛ برای مثال آیکن قلب توپر، تبدیل به آیکن قلب توخالی خواهد شد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-10.zip
مطالب
بررسی Environment در Angular
در بین توسعه دهندگان، خیلی رایج است، چند نسخه از Application خود را داشته باشند که Environment‌‌های مختلفی را مورد هدف قرار می‌دهد؛ مثل development که مربوط به حالت توسعه می‌باشد و production که مربوط به حالت ارائه نهایی است. هر Environment تعدادی متغیر منحصر بفرد خود را خواهد داشت؛ مثلAPI Endpoint ،  app version  و ... . انگیولار تنظیمات Environment  را فراهم کرده است تا بتوانیم متغیر‌های منحصر بفردی را برای هر Environment، تعریف کنیم. 
اگر شما می‌خواهید به صورت خودکار یک flag را اعمال کنید، اجازه بدهید بگوییم prod-- ، در این حالت angular compiler متغیر API endpoint را با API endpoint مربوط به حالت ارائه نهایی برای شما جایگزین می‌کند. چیزی که شما نمی‌خواهید این است که به صورت دستی endpoint را قبل از build پروژه، تغییر دهید. این موضوع شانس دچار اشتباه شدن را کاهش می‌دهد.

Dealing with only 2 Environments  

به صورت پیش فرض انگیولار از دو Environment پشتیبانی می‌کند. فایل‌های Environment در دایرکتوری environment  آن قرار دارند که در مسیر زیر می‌باشد:
داخل دایرکتوری src، در ریشه WorkSpace یا پوشه پروژه شما( آشنایی با مفهوم WorkSpace  در انگیولار ). 
در داخل دایرکتوری environment، دو فایل با نام‌های environment.ts و environment.prod.ts وجود دارند. همانطور که ممکن است حدس زده باشید، فایل دوم برای حالت ارائه نهایی می‌باشد؛ در حالیکه فایل اول برای حالت توسعه است و همچنین به عنوان Environment پیش فرض نیز می‌باشد. Angular CLI compiler ، به صورت خودکار فایل اول را با فایل دوم،  هر زمان که شما build را با prod--  انجام میدهید، جایگزین می‌کند:
ng build --prod

اگر فایل environment.ts را باز کنید، خصوصیت production  به false تنظیم شده است؛ درحالیکه در environment.prod.ts خصویت production به true تنظیم شده‌است. همه متغیرها بر اساس Environment مشخصی، تفاوت ایجاد می‌کنند که به صورت مناسب باید در دو فایل قرار داده شوند:
// environment.ts environment variables
export const environment = {
  production: false,
  APIEndpoint: 'https://dev.endpoint.com'
};

// environment.prod.ts environment variables
export const environment = {
  production: true,
  APIEndpoint: 'https://prod.endpoint.com'
};
در اینجا خصوصیت APIEndpoint را داریم. در صورتیکه در حالت توسعه باشد، از آدرس https://dev.endpoint.com و در حالت ارائه نهایی از https://prod.endpoint.com استفاده خواهد کرد.

برای دستیابی به این متغیر‌ها، environment را import کرده و از آن همانند زیر استفاده می‌کنیم:
import { environment } from './../environments/environment';
که به صورت زیر از آن استفاده می‌شود:
const APIEndpoint = environment.APIEndpoint;
در ادامه، هر زمان که شما پروژه خود را بدون build , --prod کنید در متغیر APIEndpoint تعریف شده در بالا، مقدار https://dev.endpoint.com قرار خواهد گرفت که در این حالت از Environment پیش فرض استفاده می‌شود؛ در حالیکه، وقتی از prod-- استفاده می‌کنید، در متغیر APIEndpoint، مقدار https://prod.endpoint.com قرار خواهد گرفت که در این حالت از environment.prod.ts  استفاده می‌شود.

Dealing with 3 or More Environment 

این موضوع خیلی رایج است که برای Application ‌های خود، بیش از دو Environment داشته باشیم. ممکن است که شما نیاز داشته باشید به:
staging environment ،  beta environment ، production environment ، development environment  و ...  . انگیولار یک راه را برای ما فراهم کرده است که به صورت دستی Environment‌‌‌های بیشتری را که ممکن است نیاز داشته باشیم، اضافه کنیم. در اینجا ما نیاز به دو Environment دیگر به نام‌های staging و beta داریم. کار را با ایجاد کردن دو فایل دیگر در کنار environment ‌های موجود شروع می‌کنیم:
1-environment.staging.ts
2-environment.beta.ts
سپس هر کدام از آن‌ها را به صورت زیر ویرایش می‌کنیم: 
// environment.staging.ts environment variables
export const environment = {
  production: true
  APIEndpoint: "https://staging.endpoint.com"
};

// environment.beta.ts environment variables
export const environment = {
  production: true,
  APIEndpoint: "https://beta.endpoint.com"
};

در ادامه نیاز است در فایل angular.json، تنظیمات را تغییر دهیم ( که در ریشه Workspace  می‌باشد) . با انجام این‌کار، این امکان به Angular CLI داده خواهد شد که دو environment جدید ایجاد شده را شناسایی و در صورت نیاز از آن‌ها استفاده کند.

در ابتدا فایل angular.json را باز می‌کنیم و کلید configurations را می‌یابیم که در مسیر زیر می‌باشد:
projects -> yourappname -> architect -> build -> configurations
در اینجا به صورت پیش فرض یک کلید با نام production و تنظیمات زیر وجود دارد:
"configurations": {
   "production": {
       "fileReplacements": [
           {
              "replace": "src/environments/environment.ts",
               "with": "src/environments/environment.prod.ts"
           }
        ],
        "optimization": true,
        "outputHashing": "all",
        "sourceMap": false,
        "extractCss": true,
        "namedChunks": false,
        "aot": true,
        "extractLicenses": true,
        "vendorChunk": false,
        "buildOptimizer": true,
        "serviceWorker": true
   }
}

سپس از کلید production  و تنظمیات درون آن، یک نمونه تهیه می‌کنیم و در زیر کلید production  قرار می‌دهیم. سپس کلید production را به staging تغیر می‌دهیم. در قسمت fileReplacements مقدار کلید with را از
"with":"src/environments/environment.prod.ts"
به
"with":"src/environments/environment.staging.ts"
تغیر می‌دهیم. 
اکنون تنظیمات جدید شما برای staging environment باید به صورت زیر باشد: 
"configurations": {
   "production": {
        // ...
    },
    "staging": {
    "fileReplacements": [
     {
         "replace": "src/environments/environment.ts",
         "with": "src/environments/environment.staging.ts"
     }],
     "optimization": true,
     "outputHashing": "all",
     "sourceMap": true,
     "extractCss": false,
     "namedChunks": false,
     "aot": false,
     "extractLicenses": true,
     "vendorChunk": false,
     "buildOptimizer": true,
     "serviceWorker": true
   }
}

در ادامه همین روال را برای beta environment هم تکرار کنید.

نکته 
ممکن است شما نیاز داشته باشید تا تنظیمات بالا را به حالتی دقیق‌تر نسبت به environment مورد نظر اصلاح کنید. مثلا ممکن است نیاز داشته باشید، Service worker را در حالت staging فعال نگه دارید و در حالت beta آن را غیر فعال کنید که این تضمین می‌کند وقتی ریفرش انجام می‌شود، app ، Service worker  و منابع مربوط به آن را در مرورگر کش نکرده باشد.

Building your App 

در نهایت برای build کردن application خود با environment‌‌‌های سفارشی ایجاد شده، می‌توانید از پرچم configurations-- استفاده کنید؛ همانند زیر:
//for staging environment
ng build --configuration=staging

//for beta environment
ng build --configuration=beta

و در نهایت برای استفاده کردن از environment پیش فرض، استفاده از دستور زیر به تنهایی کافی می‌باشد:
//for production environment
ng build --prod

//for dev environment
ng build

مطالعه بیشتر جهت توزیع برنامه  : Angular CLI - ساخت و توزیع برنامه 
اکنون شما می‌توانید پروژه انگیولار خود را با هر تعداد environment ای که دوست دارید، configure کنید. 
مطالب
GraphQL Mutations در ASP.NET Core ( عملیات POST, PUT, DELETE )
Mutation‌ها در GraphQL، اکشن‌های می‌باشند که با استفاده از آن‌ها Add ، Delete و Update را انجام می‌دهیم. تا کنون ما query ‌های بازیابی اطلاعات را اجرا کرده‌ایم. در این قسمت می‌خواهیم در رابطه با Mutation داده‌ها صحبت کنیم.


Input Types and Schema Enhancing for the GraphQL Mutations 

کار را با ایجاد کردن یک کلاس جدید به نام OwnerInputType در پوشه Types، شروع می‌کنیم:
public class OwnerInputType : InputObjectGraphType
{
    public OwnerInputType()
    {
        Name = "ownerInput";
        Field<NonNullGraphType<StringGraphType>>("name");
        Field<NonNullGraphType<StringGraphType>>("address");
    }
}
این همان typeی می‌باشد که قرار است به عنوان یک آرگومان برای Mutation از طرف کلاینت ارسال شود. این کلاس از InputObjectGraphType مشتق شده است؛ نه از ObjectGraphType همانند Typeهای تعریف شده قبل.
در سازنده کلاس، خصوصیت Name را مقدار دهی و دو فیلد را ایجاد می‌کنیم. همانطور که می‌بینیم دو خصوصیت Id و Accounts را نداریم؛ به دلیل اینکه نیازی به آن‌ها نیست. 
اگر قسمت قبل را دنبال کرده باشید متوجه هستیم برای ایجاد کردن query ‌ها مجبور بودیم که یک کلاس را به نام AppQuery ایجاد کنیم. برای Mutation هم همین گونه‌است. یک کلاس به نام AppMutation را در پوشه GraphQLQueries ایجاد می‌کنیم:
public class AppMutation : ObjectGraphType
{
    public AppMutation()
    {
    }
}

در نهایت نیاز است کلاس AppSchema را باز کرده و خصوصیت Mutation را به آن اضافه می‌کنیم:
public class AppSchema : Schema
{
    public AppSchema(IDependencyResolver resolver)
        :base(resolver)
    {
        Query = resolver.Resolve<AppQuery>();
        Mutation = resolver.Resolve<AppMutation>();
    }
}
اکنون همه چیز آماده است تا تعدادی Mutation، در پروژه ایجاد کنیم. 

Create Mutation 
  در ابتدا واسط IOwnerRepository  و کلاس OwnerRepository را همانند زیر ویرایش می‌کنیم (اضافه کردن متد CreateOwner):
public interface IOwnerRepository
{
   ...
    Owner CreateOwner(Owner owner);
}

public class OwnerRepository : IOwnerRepository
{
   ...
    public Owner CreateOwner(Owner owner)
     {
         owner.Id = Guid.NewGuid();
         _context.Add(owner);
         _context.SaveChanges();
         return owner;
     }
}

متد CreateOwner یک شیء Owner جدید ایجاد شده را بازگشت می‌دهد. سپس در کلاس AppMutation: 
public class AppMutation : ObjectGraphType
{
   // Add
    public AppMutation(IOwnerRepository repository)
    {  
        Field<OwnerType>(
            "createOwner",
            arguments: new QueryArguments(new QueryArgument<NonNullGraphType<OwnerInputType>> { Name = "owner" }),
            resolve: context =>
            {
                var owner = context.GetArgument<Owner>("owner");
                return repository.CreateOwner(owner);
            }
        );
    }
}
 در ابتدا یک فیلد را به منظور بازگشت دادن شیء OwnerType ایجاد می‌کنیم. در بخش نام، createOwner را وارد می‌کنیم و در بخش arguments، یک آرگومان از نوع OwnerInputType را داریم (نشان دهنده ورودی‌ها می‌باشند) و در نهایت در بخش resolve، که قرار است متد CreateOwner را از IOwnerRepository اجرا کند. 
  اکنون پروژه را اجرا کرده و سپس درخواست را در UI.Playground به صورت زیر ارسال کنید:
mutation($owner:ownerInput!){
  createOwner(owner:$owner){
    id,
    name,
    address
  }
}
سپس در قسمت Query Variables : 
{
  "owner":{
    "name":"Abolfazl-Roshanzamir",
    "address":"Address - User 4"
  }
}
و در نهایت، نتیجه اجرا، در بخش سمت راست تصویر زیر قابل مشاهده‌است:
 


بجای کلمه کلیدی query، از کلمه کلیدی mutation برای mutations‌ها استفاده می‌کنیم. این تنها یک تفاوت جدید است.

Update Mutation  

در ابتدا واسط IOwnerRepository و کلاس OwnerRepository را مطابق زیر ویرایش می‌کنیم: 
public interface IOwnerRepository
{
    ...
    Owner UpdateOwner(Owner dbOwner, Owner owner);
}

public class OwnerRepository : IOwnerRepository
{
   ...
   public Owner UpdateOwner(Owner dbOwner, Owner owner)
   {
         dbOwner.Name = owner.Name;
         dbOwner.Address = owner.Address;

         _context.SaveChanges();
         return dbOwner;
     }
}

در نهایت همانند بخش (Create Mutation) یک فیلد را در سازنده کلاس AppMutation برای Update، اضافه می‌کنیم:
    public class AppMutation : ObjectGraphType
    {
        public AppMutation(IOwnerRepository repository)
        {
            ...
            // Update
            Field<OwnerType>(
                "updateOwner",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<OwnerInputType>> { Name = "owner" },
                    new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "ownerId" }),
                resolve: context =>
                {
                    var owner = context.GetArgument<Owner>("owner");
                    var ownerId = context.GetArgument<Guid>("ownerId");

                    var dbOwner = repository.GetById(ownerId);
                    if (dbOwner == null)
                    {
                        context.Errors.Add(new ExecutionError("Couldn't find owner in db."));
                        return null;
                    }

                    return repository.UpdateOwner(dbOwner, owner);
                }
            );

        }
    }

  اکنون پروژه را اجرا کرده و سپس درخواست را در UI.Playground به صورت زیر ارسال کنید:
mutation($owner:ownerInput!,$ownerId:ID!){
  updateOwner(owner:$owner,ownerId:$ownerId){
    id,
    name,
    address
  }
}

سپس در قسمت Query Variables:
{
  "owner":{
    "name":"Andy Madaidan",
    "address":"Address - User 1"
  },
  "ownerId": "53270061-3ba1-4aa6-b937-1f6bc57d04d2"
}


Delete Mutation   
همان الگوی Create / Update را دوباره دنبال می‌کنیم. در ابتدا واسط IOwnerRepository و کلاس OwnerRepository را همانند زیر ویرایش می‌کنیم:
public interface IOwnerRepository
{
   ...
    void DeleteOwner(Owner owner);
}

public class OwnerRepository : IOwnerRepository
{
   ...
   public void DeleteOwner(Owner owner)
   {
        _context.Remove(owner);
        _context.SaveChanges();
    }
}

و آخرین کاری که نیاز است انجام شود، ویرایش کلاس AppMutation می‌باشد:
 public class AppMutation : ObjectGraphType
    {
        public AppMutation(IOwnerRepository repository)
        {
            ...
           
            //Delete
            Field<StringGraphType>(
                "deleteOwner",
                arguments: new QueryArguments(new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "ownerId" }),
                resolve: context =>
                {
                    var ownerId = context.GetArgument<Guid>("ownerId");
                    var owner = repository.GetById(ownerId);
                    if (owner == null)
                    {
                        context.Errors.Add(new ExecutionError("Couldn't find owner in db."));
                        return null;
                    }
            
                    repository.DeleteOwner(owner);
                    return $"The owner with the id: {ownerId} has been successfully deleted from db.";
                }
            );
        }
    }

 اکنون پروژه را اجرا کنید و سپس درخواست را در UI.Playground به صورت زیر ارسال کنید:
mutation($ownerId:ID!){
  deleteOwner(ownerId:$ownerId)
}

سپس در قسمت Query Variables : 
{
  "ownerId": "6f513773-be46-4001-8adc-2e7f17d52d83"
}
 نتیجه اجرا در بخش سمت راست تصویر زیر قابل مشاهده است : 


نتیجه گیری
اکنون ما می‌دانیم که چگونه از Input Type ها برای Mutation ها استفاده کنیم. چگونه mutation action های متفاوتی را ایجاد کنیم و هم چنین چگونه در خواست‌های  mutation را در سمت کلاینت ایجاد کنیم.

کد‌های کامل مربوط به این قسمت را از اینجا دریافت کنید: ASPCoreGraphQL_3.rar