مطالب
MVVM و رویدادگردانی

در دو قسمت قبل به اینجا رسیدیم که بجای شروع به کدنویسی مستقیم در code behind یک View (یک پنجره، یک user control ...)، کلاس مجزای دیگری را به نام ViewModel به برنامه اضافه خواهیم کرد و این کلاس از وجود هیچ فرمی در برنامه مطلع نیست.
بنابراین جهت انتقال رخدادها به ViewModel، بجای روش متداول تعریف روال‌های رخدادگردان در Code behind:
<Button  Click="btnClick_Event">Last</Button>

آن‌ها را با Commands به ViewModel ارسال خواهیم کرد:
<Button Command="{Binding GoLast}">Last</Button>  


همچنین بجای اینکه مستقیما بخواهیم از طریق نام یک شیء به مثلا خاصیت متنی آن دسترسی پیدا کنیم:
<TextBox Name="txtName" />  

از طریق Binding، اطلاعات مثلا متنی آن‌را به ViewModel منتقل خواهیم کرد:
<TextBox Text="{Binding Name}" />  


و همینجا است که 99 درصد آموزش‌های MVVM موجود در وب به پایان می‌رسند؛ البته پس از مشاهده 10 تا 20 ویدیو و خواندن بیشتر از 30 تا مقاله! و اینجا است که خواهید گفت: فقط همین؟! با این‌ها میشه یک برنامه رو مدیریت کرد؟!
البته همین‌ها برای مدیریت قسمت عمده‌ای از اکثر برنامه‌ها کفایت می‌کنند؛ اما خیلی از ریزه‌ کاری‌ها وجود دارند که به این سادگی‌ها قابل حل نیستند و در طی چند مقاله به آن‌ها خواهیم پرداخت.

سؤال: در همین مثال فوق، اگر متن ورودی در TextBox تغییر کرد، چگونه می‌توان بلافاصله از تغییرات آن در ViewModel مطلع شد؟ قدیم‌ترها می‌شد نوشت:
<TextBox TextChanged="TextBox_TextChanged" />


اما الان که قرار نیست در code behind کد بنویسیم (تا حد امکان البته)، باید چکار کرد؟
پاسخ: امکان Binding به TextChanged وجود ندارد، پس آن‌را فراموش می‌کنیم. اما همان Binding معمولی را به این صورت هم می‌شود نوشت (همان مثال قسمت قبل):
<TextBox Text="{Binding 
MainPageModelData.Name,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />


و نکته مهم آن UpdateSourceTrigger است. اگر روی حالت پیش فرض باشد، ViewModel پس از تغییر focus از این TextBox به کنترلی دیگر، از تغییرات آگاه خواهد شد. اگر آن‌را صریحا ذکر کرده و مساوی PropertyChanged قرار دهیم (این مورد در سیلورلایت 5 جدید است؛ هر چند از روز نخست WPF وجود داشته است)، با هر تغییری در محتوای TextBox، خاصیت MainPageModelData.Name به روز رسانی خواهد شد.
اگر هم بخواهیم این تغییرات آنی‌را در ViewModel تحت نظر قرار دهیم، می‌توان نوشت:

using System.ComponentModel;

namespace SL5Tests
{
public class MainPageViewModel
{
public MainPageModel MainPageModelData { set; get; }
public MainPageViewModel()
{
MainPageModelData = new MainPageModel();
MainPageModelData.Name = "Test1";
MainPageModelData.PropertyChanged += MainPageModelDataPropertyChanged;
}

void MainPageModelDataPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Name":
//do something
break;
}
}
}
}

تعریف MainPageModel را در قسمت قبل مشاهده کرده‌اید و این کلاس اینترفیس INotifyPropertyChanged را پیاده سازی می‌کند. بنابراین می‌توان از رویدادگردان PropertyChanged آن در ViewModel هم استفاده کرد.
به این ترتیب همان کار رودیدادگردان TextChanged را اینطرف هم می‌توان شبیه سازی کرد و تفاوتی نمی‌کند. البته با این تفاوت که در ViewModel فقط به اطلاعات به روز موجود در MainPageModelData.Name دسترسی داریم، اما نمی‌دانیم و نمی‌خواهیم هم بدانیم که منبع آن دقیقا کدام شیء رابط کاربری برنامه است.

سؤال: ما قبلا مثلا می‌توانستیم بررسی کنیم که اگر کاربر حین تایپ در یک TextBox بر روی دکمه‌ی Enter کلیک کرد، آن‌گاه برای نمونه، جستجویی بر اساس اطلاعات وارد شده صورت گیرد. الان این فشرده شدن دکمه‌ی Enter را چگونه دریافت و چگونه به ViewModel ارسال کنیم؟
این مورد کمی پیشرفته‌تر از حالت‌های قبلی است. برای حل این مساله ابتدا باید UpdateSourceTrigger یاد شده را مساوی Explicit قرار داد. یعنی اینبار می‌خواهیم نحوه ی به روز رسانی خاصیت MainPageModelData.Name را از طریق Binding خودمان مدیریت کنیم. این مدیریت کردن هم با استفاده از امکاناتی به نام Attached properties قابل انجام است که به آن‌ها Behaviors هم می‌گویند. مثلا:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace SL5Tests
{
public static class InputBindingsManager
{
public static readonly DependencyProperty UpdatePropertySourceWhenEnterPressedProperty
= DependencyProperty.RegisterAttached(
"UpdatePropertySourceWhenEnterPressed",
typeof(bool),
typeof(InputBindingsManager),
new PropertyMetadata(false, OnUpdatePropertySourceWhenEnterPressedPropertyChanged));

static InputBindingsManager()
{ }

public static void SetUpdatePropertySourceWhenEnterPressed(DependencyObject dp, bool value)
{
dp.SetValue(UpdatePropertySourceWhenEnterPressedProperty, value);
}

public static bool GetUpdatePropertySourceWhenEnterPressed(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatePropertySourceWhenEnterPressedProperty);
}

private static void OnUpdatePropertySourceWhenEnterPressedPropertyChanged(DependencyObject dp,
DependencyPropertyChangedEventArgs e)
{
var txt = dp as TextBox;
if (txt == null)
return;

if ((bool)e.NewValue)
{
txt.KeyDown += HandlePreviewKeyDown;
}
else
{
txt.KeyDown -= HandlePreviewKeyDown;
}
}

static void HandlePreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter) return;

var txt = sender as TextBox;
if (txt == null)
return;

var binding = txt.GetBindingExpression(TextBox.TextProperty);
if (binding == null) return;
binding.UpdateSource();
}
}
}

تعریف Attached properties یک قالب استاندارد دارد که آن را در کد فوق ملاحظه می‌کنید. یک تعریف به صورت static و سپس تعریف متدهای Get و Set آن. با تغییر مقدار آن که اینجا از نوع bool تعریف شده، متد OnUpdatePropertySourceWhenEnterPressedPropertyChanged به صورت خودکار فراخوانی می‌شود. اینجا است که ما از طریق آرگومان dp به textBox جاری دسترسی کاملی پیدا می‌کنیم. مثلا در اینجا بررسی شده که آیا کلید فشرده شده enter است یا خیر. اگر بله، یک سری فرامین را انجام بده. به عبارتی ما توانستیم، قطعه کدی را به درون شیءایی موجود تزریق کنیم. Txt تعریف شده در اینجا، واقعا همان کنترل TextBox ایی است که به آن متصل شده‌ایم.

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

<UserControl x:Class="SL5Tests.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:VM="clr-namespace:SL5Tests"
mc:Ignorable="d" Language="fa"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<VM:MainPageViewModel x:Name="vmMainPageViewModel" />
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource vmMainPageViewModel}}"
x:Name="LayoutRoot"
Background="White">
<TextBox Text="{Binding
MainPageModelData.Name,
Mode=TwoWay,
UpdateSourceTrigger=Explicit}"
VerticalAlignment="Top"
VM:InputBindingsManager.UpdatePropertySourceWhenEnterPressed="True" />
</Grid>
</UserControl>

همانطور که مشاهده می‌کنید، UpdateSourceTrigger به Explicit تنظیم شده و سپس InputBindingsManager.UpdatePropertySourceWhenEnterPressed به این کنترل متصل گردیده است. یعنی تنها زمانیکه در متد HandlePreviewKeyDown ذکر شده، متد UpdateSource فراخوانی گردد، خاصیت MainPageModelData.Name به روز رسانی خواهد شد (کنترل آن‌را خودمان در دست گرفته‌ایم نه حالت‌های از پیش تعریف شده).

این روش، روش متداولی است برای تبدیل اکثر حالاتی که Binding و Commanding متداول در مورد آن‌ها وجود ندارد. مثلا نیاز است focus را به آخرین سطر یک ListView از داخل ViewModel انتقال داد. در حالت متداول چنین امری میسر نیست، اما با تعریف یک Attached properties می‌توان به امکانات شیء ListView مورد نظر دسترسی یافت (به آن متصل شد، یا نوعی تزریق)، آخرین عنصر آن‌را یافته و سپس focus را به آن منتقل کرد یا به هر اندیسی مشخص که بعدا در ViewModel به این Behavior از طریق Binding ارسال خواهد شد.

مطالب
FluentValidation #2
کتابخانه FluentValidation به صورت پیش فرض دارای تعدادی Validatior می‌باشد که برای اکثر کارهای ابتدایی کافی می‌باشد.

 NotNull   اطمینان از اینکه خاصیت مورد نظر Null نباشد 
 NotEmpty   اطمینان از اینکه خاصیت مورد نظر Null یا رشته خالی نباشد (یا مقدار پیش فرض نباشد، مثلا 0 برای int) 
 NotEqual   اطمینان از اینکه خاصیت مورد نظر برابر مقدار تعیین شده نباشد (یا برابر مقدار خاصیت دیگری نباشد) 
 Equal   اطمینان از اینکه خاصیت مور نظر برابر مقدار تعیین شده باشد (یا برابر مقدار خاصیت دیگری باشد) 
 Length   اطمینان از اینکه طول رشته‌ی خاصیت مورد نظر در محدوده خاصی باشد 
 LessThan   اطمینان از اینکه مقدار خاصیت مورد نظر کوچکتر از مقدار تعیین شده باشد (یا کوچکتر از خاصیت دیگری) 
 LessThanOrEqual   اطمینان از اینکه مقدار خاصیت مورد نظر کوچکتر یا مساوی مقدار تعیین شده باشد (یا کوچکتر مساوی مقدار خاصیت دیگری) 
 GreaterThan   اطمینان از اینکه مقدار خاصیت مورد نظر بزرگتر از مقدار تعیین شده باشد (یا بزرگتر از مقدار خاصیت دیگری) 
 GreaterThanOrEqual   اطمینان از اینکه مقدار خاصیت مورد نظر بزرگتر مساوی مقدار تعیین شده باشد (یا بزرگتر مساوی مقدار خاصیت دیگری) 
 Matches  اطمینان از اینکه مقدار خاصیت مورد نظر با عبارت باقائده (Regular Expression) تنظیم شده مطابقت داشته باشد 
 Must  اعتبارسنجی یک predicate با استفاده از Lambada Expressions. اگر عبارت Lambada مقدار true برگرداند اعتبارسنجی با موفقیت انجام شده و اگر false برگرداند، اعتبارسنجی با شکست مواجه شده است.
 Email   اطمینان از اینکه مقدار خاصیت مورد نظر یک آدرس ایمیل معتبر باشد
 CreditCard   اطمینان از اینکه مقدار خاصیت مورد نظر یک Credit Card باشد
همان طور که در جدول بالا ملاحظه می‌کنید بعضی از اعتبارسنجی‌ها را می‌توان با استفاده از مقدار خاصیت‌های دیگر انجام داد. برای درک این موضوع مثال زیر را در نظر بگیرید:
RuleFor(customer => customer.Surname).NotEqual(customer => customer.Forename); 
در مثال بالا مقدار خاصیت Surname نباید برابر مقدار خاصیت Forename باشد. 
برای تعیین اینکه در هنگام اعتبارسنجی چه پیامی به کاربر نمایش داده شود نیز می‌توان از متد WithMessage استفاده کرد:
RuleFor(customer => customer.Surname).NotNull().WithMessage("Please ensure that you have entered your Surname");


اعتبارسنجی تنها در مواقع خاص


با استفاده از شرط‌های When و Unless می‌توان تعیین کرد که اعتبارسنجی فقط در مواقعی خاص انجام شود. به عنوان مثال در قطعه کد زیر با استفاده از متد When، تعیین می‌کنیم که اعتبارسنجی روی خاصیت CustomerDiscount تنها زمانی اتفاق بیفتد که خاصیت IsPreferredCustomer برابر true باشد.
RuleFor(customer => customer.CustomerDiscount).GreaterThan(0).When(customer => customer.IsPreferredCustomer);
متد Unless نیز برعکس متد When می‌باشد.
اگر نیاز به تعیین یک شرط یکسان برای چند خاصیت باشد، میتوان به جای تکرار شرط برای هرکدام از خاصیت‌ها به صورت زیر عمل کرد:
When(customer => customer.IsPreferred, () => {
   RuleFor(customer => customer.CustomerDiscount).GreaterThan(0);
   RuleFor(customer => customer.CreditCardNumber).NotNull();
});

تعیین نحوه برخورد با اعتبارسنجی‌های زنجیره ای


در قطعه کد زیر ملاحظه می‌کنید که از دو Validator برای یک خاصیت استفاده شده است. (NotNull و NotEqual)
RuleFor(x => x.Surname).NotNull().NotEqual("foo");
قطعه کد بالا بررسی می‌کند که مقدار خاصیت Surname، ابتدا برابر Null نباشد و پس از آن برابر رشته "Foo" نیز نباشد. در این حالت (حالت پیش فرض) اگر اعتبارسنجی اول (NotNull) با شکست مواجه شود، اعتبارسنجی دوم (NotEqual) نیز انجام خواهد شد. برای جلوگیری از این حالت می‌توان از CascadeMode به صورت زیر استفاده کرد:
RuleFor(x => x.Surname).Cascade(CascadeMode.StopOnFirstFailure).NotNull().NotEqual("foo");
اکنون اگر اعتبارسنجی NotNull با شکست مواجه شود، دیگر اعتبارسنجی دوم انجام نخواهد شد. این ویژگی در مواردی کاربرد دارد که یک زنجیره پیچیده از اعتبارسنجی‌ها داریم که شرط انجام هرکدام از آنها موفقیت در اعتبارسنجی‌های قبلی است.
اگر نیاز بود تا CascadeMode را برای تمام خاصیت‌های یک کلاس Validator تعیین کنیم می‌توان به صورت خلاصه از روش زیر استفاده کرد:
public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {
    // First set the cascade mode
    CascadeMode = CascadeMode.StopOnFirstFailure;
    
    // Rule definitions follow
    RuleFor(...) 
    RuleFor(...)
   }
}
سفارشی سازی اعتبارسنجی
برای ایجاد اعتبارسنجی سفارشی دو راه وجود دارد:
راه اول ایجاد یک کلاس که از PropertyValidator مشتق می‌شود. برای توضیح نحوه استفاده از این راه، تصور کنید که میخواهیم یک اعتبارسنج سفارشی درست کنیم تا چک کند که یک لیست حتماً کمتر از 10 آیتم داخل خود داشته باشد. در این صورت کدی که بایستی نوشته شود به صورت زیر خواهد بود:
using System.Collections.Generic;
using FluentValidation.Validators;

public class ListMustContainFewerThanTenItemsValidator<T> : PropertyValidator {

public ListMustContainFewerThanTenItemsValidator() 
: base("Property {PropertyName} contains more than 10 items!") {

}

protected override bool IsValid(PropertyValidatorContext context) {
var list = context.PropertyValue as IList<T>;

if(list != null && list.Count >= 10) {
return false;
}

return true;
}
}
کلاسی که از PropertyValidator مشتق می‌شود بایستی متد IsValid آن را override کند. متد IsValid یک PropertyValidatorContext را به عنوان ورودی می‌گیرد و یک boolean را که مشخص کننده نتیجه اعتبارسنجی است، بر می‌گرداند. همان طور که در مثال بالا ملاحظه می‌کنید پیغام خطا نیز در constructor مشخص شده است.
برای استفاده از این Validator سفارشی نیز می‌توان از متد SetValidator به صورت زیر استفاده نمود:

public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
       RuleFor(person => person.Pets).SetValidator(new ListMustContainFewerThanTenItemsValidator<Pet>());
    }
}

راه دیگر استفاده از آن تعریف یک Extension Method می‌باشد که در این صورت می‌توان از آن به صورت زنجیره ای مانند دیگر Validator‌ها استفاده نمود:

public static class MyValidatorExtensions {
   public static IRuleBuilderOptions<T, IList<TElement>> MustContainFewerThanTenItems<T, TElement>(this IRuleBuilder<T, IList<TElement>> ruleBuilder) {
      return ruleBuilder.SetValidator(new ListMustContainFewerThanTenItemsValidator<TElement>());
   }
}
اکنون برای استفاده از Extension Method می‌توان به راحتی مانند زیر عمل کرد:

public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
       RuleFor(person => person.Pets).MustContainFewerThanTenItems();
    }
}

راه دوم استفاده از متد Custom می‌باشد. برای توضیح نحوه استفاه از این متد مثال قبل (چک کردن تعداد آیتم‌های لیست) را به صورت زیر بازنویسی می‌کنیم:

public class PersonValidator : AbstractValidator<Person> {
   public PersonValidator() {
       Custom(person => { 
           return person.Pets.Count >= 10 
              ? new ValidationFailure("More than 10 pets is not allowed.")
              : null; 
       });
   }
}
توجه داشته باشید که متد Custom تنها برای اعتبارسنجی‌های خیلی پیچیده طراحی شده است و در اکثر مواقع می‌توان خیلی راحت‌تر و تمیز‌تر از PredicateValidator (Must) برای اعتبارسنجی سفارشی مان استفاده کرد، مانند مثال زیر:

public class PersonValidator : AbstractValidator<Person> {
   public PersonValidator() {
      RuleFor(person => person.Pets).Must(HaveFewerThanTenPets).WithMessage("More than 9 pets is not allowed");
   }

   private bool HaveFewerThanTenPets(IList<Pet> pets) {
      return pets.Count < 10;
   }
}

پ.ن.
در این دو مقاله سعی شد تا ویژگی‌های FluentValidation به صورت انتزاعی توضیح داده شود. در قسمت بعد نحوه استفاده از این کتابخانه در یک برنامه ASP.NET MVC نشان داده خواهد شد.
مطالب
استفاده از قالب مخصوص Redux Toolkit جهت ایجاد پروژه‌های React/Redux
استفاده از Redux درون پروژه‌های React، به روش‌های مختلفی قابل انجام است؛ یعنی محدودیتی از لحاظ نحوه چیدمان فایل‌ها، تغییر state و نحوه‌ی dispatch کردن action وجود ندارد. به عبارتی این آزادی عمل را خواهیم داشت تا خودمان سیم‌کشی پروژه را انجام دهیم؛ ولی مشکل اصلی اینجاست که نمی‌توانیم مطمئن شویم روشی که پروژه را با آن ستاپ کرده‌ایم آیا به عنوان یک best-practice محسوب می‌شود یا خیر. در نهایت خروجی را که خواهیم داشت، حجم انبوهی از کدهای boilerplate و پکیج‌های زیادی است که در حین توسعه‌ی پروژه، به همراه Redux اضافه شده‌اند. همچنین در نهایت یک store پیچیده را خواهیم داشت که مدیریت آن به مراتب سخت خواهد شد. یک مشکل دیگر این است که روال گفته شده را باید به ازای هر پروژه‌ی جدید تکرار کنیم. برای حل این مشکل، یکی از maintainerهای اصلی تیم ریداکس، یک پروژه را تحت عنوان Redux Toolkit، مدتها قبل برای حل مشکلات عنوان شده شروع کرده است و این پکیج، جدیداً به قالب رسمی create-react-app اضافه شده است. که در واقع یک روش استاندارد و به اصطلاح opinionated برای ایجاد پروژه‌های ریداکسی می‌باشد و شامل تمامی وابستگی‌های موردنیاز برای کار با Redux از قبیل redux-thunk و همچنین Redux DevTools است. 

 ایجاد یک برنامه‌ی خالی React با قالب redux
در ادامه برای بررسی این قالب جدید، یک پروژه‌ی جدید React را ایجاد خواهیم کرد:
> npx create-react-app redux-template --template redux
> cd redux-template
> yarn start


بررسی ساختار پروژه‌ی ایجاد شده
ساختار پروژه‌ی ایجاد شده به صورت زیر است:


این ساختار خیلی شبیه به قالب پیش‌فرض create-react-app می‌باشد. همانطور که در تصویر فوق نیز مشاهده میکنید، پروژه‌ی ایجاد شده‌ی با قالب redux (تصویر سمت چپ)، یک فایل با نام store و همچنین یک دایرکتوری را به نام features دارد. اگر به فایل store.js مراجعه کنید، خواهید دید که تنظیمات اولیه‌ی ایجاد store را در قالب یک مثال Counter ایجاد کرده‌است:
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
});
در کد فوق، نحوه‌ی ایجاد store، نسبت به حالت معمول، خیلی تمیزتر است. نکته‌ی جالب این است که به همراه کد فوق، Redux DevTools و همچنین redux-thunk هم از قبل تنظیم شده‌اند و در نتیجه، نیازی به تنظیم و نصب آنها نیست. تغییر مهم دیگر، پوشه‌ی features می‌باشد که یک روش رایج برای گروه‌بندی کامپوننت‌ها، همراه با فایل‌های وابسته‌ی آن‌ها است. درون این پوشه، یک پوشه جدید را تحت عنوان counter داریم که حاوی فایل‌های زیر می‌باشد: 
Counter
Counter.module.css
counterSlice.js
Counter.js، کامپوننتی است که در نهایت درون صفحه رندر خواهد شد. درون این فایل با استفاده از Redux Hooks کار اتصال به store و همچنین dispatch کردن اکشن‌ها انجام گرفته است:
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  decrement,
  increment,
  incrementByAmount,
  selectCount,
} from './counterSlice';
import styles from './Counter.module.css';

export function Counter() {
  const count = useSelector(selectCount);
  const dispatch = useDispatch();
  const [incrementAmount, setIncrementAmount] = useState(2);

  return (
    <div>
      <div className={styles.row}>
        <button
          className={styles.button}
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          +
        </button>
        <span className={styles.value}>{count}</span>
        <button
          className={styles.button}
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          -
        </button>
      </div>
      <div className={styles.row}>
        <input
          className={styles.textbox}
          value={incrementAmount}
          onChange={e => setIncrementAmount(e.target.value)}
        />
        <button
          className={styles.button}
          onClick={() =>
            dispatch(
              incrementByAmount({ amount: Number(incrementAmount) || 0 })
            )
          }
        >
          Add Amount
        </button>
      </div>
    </div>
  );
}

فایل Counter.module.css نیز در واقع استایل‌های مربوط به کامپوننت فوق میباشد که به صورت CSS module اضافه شده‌است. در نهایت فایل counterSlice.js را داریم که  کار همان reducer سابق را برایمان انجام خواهد داد؛ اما اینبار با یک ساختار جدید و تحت عنوان slice. اگر به فایل عنوان شده مراجعه کنید، کدهای زیر را خواهید دید:
import { createSlice } from '@reduxjs/toolkit';

export const slice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    increment: state => {
      // Redux Toolkit allows us to 'mutate' the state. It doesn't actually
      // mutate the state because it uses the immer library, which detects
      // changes to a "draft state" and produces a brand new immutable state
      // based off those changes
      state.value += 1;
    },
    decrement: state => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload.amount;
    },
  },
});

export const selectCount = state => state.counter.value;
export const { increment, decrement, incrementByAmount } = slice.actions;

export default slice.reducer;
در این قالب جدید، ترکیب این قطعات هستند که شیء اصلی یا در واقع همان state کلی پروژه را تشکیل خواهند داد. همانطور که مشاهده میکنید، برای ایجاد یک قطعه جدید، از تابع createSlice استفاده شده است. این تابع، تعدادی پارامتر را از ورودی دریافت می‌کند:
  • name: برای هر بخش از state، می‌توانیم یک نام را تعیین کنیم و این همان عنوانی خواهد بود که می‌توانید توسط Redux DevTools مشاهده کنید.
  • initialValue: در اینجا می‌توانیم مقادیر اولیه‌ای را برای این بخش از state، تعیین کنیم که در مثال فوق، value به مقدار صفر تنظیم شده‌است.
  • reducers: این قسمت محل تعریف actionهایی هستند که قرار است state را تغییر دهند. نکته جالب توجه این است که state در هر کدام از متدهای فوق، به ظاهر mutate شده است؛ اما همانطور که به صورت کامنت نیز نوشته‌است، در پشت صحنه از کتابخانه‌ای با عنوان immer استفاده می‌کند که در عمل بجای تغییر state اصلی، یک کپی از state جدید را جایگزین state قبلی خواهد کرد.
توسط selectCount نیز کار انتخاب مقدار موردنظر از state انجام شده‌است که معادل همان mapPropsToState است و در اینجا امکان دسترسی به state ذخیره شده در مخزن redux فراهم شده است. همچنین در خطوط پایانی فایل نیز اکشن‌ها برای دسترسی ساده‌تر به درون کامپوننت، به صورت Object Destructuring به بیرون export شده‌اند. در نهایت reducer نهایی را از slice ایجاد شده استخراج کرده‌ایم. این پراپرتی برای ایجاد store مورداستفاده قرار می‌گیرد.

چرا قالب Redux Toolkit از immer برای تغییر state استفاده میکند؟
همانطور که در این قسمت از سری Redux توضیح داده شد، اعمال تغییرات بر روی آرایه‌ها و اشیاء، باعث ایجاد ناخالصی خواهد شد؛ بنابراین به جای تغییر شیء اصلی، باید توسط یکی از روش‌های Object.assign و یا spread operator، یک clone از state اصلی را ایجاد کرده و آن را به عنوان state نهایی لحاظ کنیم. اما در حین کار با اشیاء nested، انجام اینکار سخت خواهد شد و همچنین خوانایی کد را نیز کاهش می‌دهد:
return {
    ...state,
    models: state.models.map(c =>
        c.model === action.payload.model
          ? {
              ...c,
              on: action.payload.toggle
            }
          : c
      )
  };
اما با کمک immer می‌توانیم به صورت مستقیم state را تغییر دهیم:
state.models.forEach(item => {
    if (item.model === action.payload.model) {
      item.on = action.payload.toggle;
    }
 });
کاری که immer انجام می‌دهد، تغییر یک شیء، به صورت مستقیم نیست؛ در واقع یک draftState را ایجاد خواهد کرد که در عمل یک proxy برای state فعلی می‌باشد. یعنی با mutate کردن state، یک شیء جدید را در نهایت clone خواهد کرد و به عنوان state نهایی برمی‌گرداند.
نظرات مطالب
متدی برای بررسی صحت کد ملی وارد شده
سلام
من از یک مبدل آنلاین استفاده کردم به این نتیجه رسیدم.
Namespace ConsoleApplicationTest
Class Program
Private Shared Sub Main(args As String())
Console.WriteLine("0172942284 => {0}", "0172942284".IsValidNationalCode())
Console.WriteLine("1000000001 => {0}", "1000000001".IsValidNationalCode())
End Sub
End Class

Public NotInheritable Class Helpers
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function IsValidNationalCode(nationalCode As [String]) As [Boolean]
If [String].IsNullOrEmpty(nationalCode) Then
Throw New Exception("لطفا کد ملی را صحیح وارد نمایید")
End If

If nationalCode.Length <> 10 Then
Throw New Exception("طول کد ملی باید ده کاراکتر باشد")
End If

Dim regex__1 = New Regex("[^0-9]<span> </span>")
If Not regex__1.IsMatch(nationalCode) Then
Throw New Exception("کد ملی تشکیل شده از ده رقم عددی می‌باشد؛ لطفا کد ملی را صحیح وارد نمایید")
End If

If Not Regex.IsMatch(nationalCode, "^(?!(\d)\1{9})\d{10}$") Then
Return False
End If

Dim check = Convert.ToInt32(nationalCode.Substring(9, 1))
Dim result = Enumerable.Range(0, 9).[Select](Function(x) Convert.ToInt32(nationalCode.Substring(x, 1)) * (10 - x)).Sum() Mod 11

Dim remainder As Integer = result Mod 11
Return check = (If(remainder < 2, remainder, 11 - remainder))

End Function
End Class
End Namespace
نظرات مطالب
غیر فعال کردن یک دکمه در حین انجام پردازش های سمت سرور
ممنون از کار شما
من این روش تست کردم اما زمانی که با یک تاییده همراه باشه کد تقریبا از کار می‌افته، صفحه به سرور پست میشه اما عملیاتی صورت نمی‌گیره.
کدی که نوشتم:
$("input[type=submit]").click(function () {
                if ((typeof (Page_ClientValidate) == 'function') && (Page_ClientValidate() == false)) {
                     return false;
                }
                if (!confirm("آیا مطمئن هستید؟"))
                    return false;
                this.disabled = true;
                this.value = 'در حال پردازش اطلاعات ...';
                return true;
            });

نظرات مطالب
ASP.NET MVC #18
        [HttpGet]
        public ActionResult LogOn(string returnUrl)
        {
            if (User.Identity.IsAuthenticated) //remember me
            {
                if (ShouldRedirect(returnUrl))
                {
                    return Redirect(returnUrl);
                }
                return Redirect(FormsAuthentication.DefaultUrl);
            }

            return View(); // show the login page
        }
بعد  از پیاده سازی CustomRoleProvider وقتی که از این فیلتر استفاده می‌کنم :
[Authorize(Roles = "Admin")]
می ره به اکشن Logon اونجا
IsAuthenticated=true
هست و همین طور متد
ShouldRedirect(returnUrl)
هم مقدار true رو بر می‌گردونه و نتیجه اینکه دوباره بر می‌گرده به /User/Create و اونجا هم دوباره برمی گرده به همین اکشن و این Loop تکرار می‌شه.

این کد برای من اینجوری کار می‌کنه

نظرات مطالب
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
با اجازه دوست عزیزم مهندس فتح الهی
من به نظرم Helpers رو اگه به شکل زیر Re factor کنیم بهتر باشه :)
اول یه کلاس تعریف می‌کنیم و اطلاعات لازم برای ترسیم پیش نمایش رو تو اون کلاس میزاریم
public class ShapeSpecification
    {
        public PointF StartPoint{get;set;}
        public PointF EndPoint{get;set;}
        public Color ForeColor{get;set;}
        public byte Thickness{get;set;}
        public bool IsFill{get;set;}
        public Brush BackgroundBrush{get;set;}
    }
یه کلاس دیگه هم نقاط ابتدا و انتها و طول و عرض رو تو خودش داره
public class StartPoints
    {
        public float XPoint { get; set; }
        public float YPoint { get; set; }
        public float Width { get; set; }
        public float Height { get; set; }
    }
حالا یه اینترفیس تعریف می‌کنیم که فقط یه متد داره به نام   Draw
 public interface IPeiview
    {
        void Draw(ShapeSpecification shapeScepification);
    }
حالا می‌رسیم به کلاس Helpers اصلیمون که میتونه هم استاتیک باشه و هم معمولی به دو شکل زیر
public class Helpers
    {
        private readonly IPeiview peiview;

        public Helpers(IPeiview peiview)
        {
            this.peiview = peiview;
        }

        public void Draw(ShapeSpecification shapeSpecification)
        {
            peiview.Draw(shapeSpecification);
        }
    }
 public static  class Helpers
    {

        public static void Draw(ShapeSpecification shapeSpecification, IPeiview peiview)
        {
            peiview.Draw(shapeSpecification);
        }
    }
که فقط یه متد ساده Draw داره و اونم تابع Draw اینترفیسی که بش دادیم رو صدا میزینه
یه کلاس دیگه هم تعریف میکنیم که مسئولیتش تشخیص بوم‌های چهارگانه است برای شروع نقطه‌ی ترسیم
public static class AreaParser
    {
        public static StartPoints Parse(PointF startPoint, PointF endPoint)
        {
            var startPoints = new StartPoints();

            startPoints.Width = Math.Abs(endPoint.X - startPoint.X);
            startPoints.Height = Math.Abs(endPoint.Y - startPoint.Y);

            if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                startPoints.XPoint = startPoint.X;
                startPoints.YPoint = startPoint.Y;
            }

            else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                startPoints.XPoint = endPoint.X;
                startPoints.YPoint = endPoint.Y;
            }

            else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                startPoints.XPoint = endPoint.X;
                startPoints.YPoint = startPoint.Y;
            }

            else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                startPoints.XPoint = startPoint.X;
                startPoints.YPoint = endPoint.Y;
            }
            return startPoints;
        }
    }
نکته: این کلاس رو اگه با Func ایجاد کنیم خیلی بهتر و تمیزتر  وقشنکتر هم میشد که من می‌گزرم فعلا ازش
حالا ما هر شکل جدید که اضافه کنیم به پروژه Paint خودمون و قصد پیش نمایش اونو داشته باشیم فقط کافیه یه کلاس برا پیش نمایشش ایجاد کنیم که کلاس Ipreview رو Implement کنه و متد Draw مخصوص به خودش را داشته باشه و از شر Swith‌های طولانی خلاص میشیم مثلا من برای دایره اینکارو کردم
public class CirclePreview:IPeiview
    {
        private readonly Graphics graphics;

        public CirclePreview(Graphics graphics)
        {
            this.graphics = graphics;
        }
        public void Draw(ShapeSpecification shapeScepification)
        {
            var startPoints = AreaParser.Parse(shapeScepification.StartPoint, shapeScepification.EndPoint);

            float raduis = Math.Max(startPoints.Width, startPoints.Height);

            if (shapeScepification.IsFill)
                this.graphics.FillEllipse(shapeScepification.BackgroundBrush, startPoints.XPoint, startPoints.YPoint, raduis, raduis);
            else
                this.graphics.DrawEllipse(new Pen(shapeScepification.ForeColor, shapeScepification.Thickness), startPoints.XPoint, startPoints.YPoint, raduis, raduis);
        }
    }


نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
امکان تهیه Custom Elements در NET 6 Blazor.

در آخرین نسخه Blazor این امکان فراهم شده است که بتوانیم از کامپوننت‌های Blazor درون پروژه‌های React/Vue, Angular, ... استفاده کنیم (+). البته این فیچر هنوز به صورت آزمایشی می‌باشد و ممکن است API آن تغییر کند. 
در ادامه یک مثال از این قابلیت را مشاهده خواهید کرد. 
ایجاد پروژه Blazor
یک دایرکتوری ایجاد کرده و درون آن یک پروژه blazorwasm با نام blazor_wasm ایجاد کنید:
dotnet new blazorwasm blazor_wasm
برای استفاده از این فیچر میبایست پکیج  Microsoft.AspNetCore.Components.CustomElements را نصب کنیم:
dotnet add package Microsoft.AspNetCore.Components.CustomElements --version 0.1.0-alpha.21466.1
در ادامه یک کامپوننت Todo ایجاد خواهیم کرد:
@page "/todo"

<PageTitle>Todo</PageTitle>

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" @bind="todo.IsDone" />
            <input @bind="todo.Title" />
        </li>
    }
</ul>

<input placeholder="Something todo" @bind="newTodo" />
<button @onclick="AddTodo">Add todo</button>

@code {

    public class TodoItem
    {
        public string? Title { get; set; }
        public bool IsDone { get; set; }
    }

    private List<TodoItem> todos = new();
    private string? newTodo;

    private async void AddTodo(MouseEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
}
برای تبدیل کامپوننت فوق به یک Custom Element درون فایل Program.cs خط زیر را اضافه میکنیم:
builder.RootComponents.RegisterAsCustomElement<Todo>("todo-element");

استفاده از کامپوننت فوق درون یک پروژه React
npx create-react-app blazor_react && cd blazor_react
برای استفاده از Custom Element موردنظر دو خط زیر را به فایل public/index.html اضافه میکنیم:
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
همچنین لازم است یک پراکسی نیز درون پروژه ایجاد کنیم (درون فایل package.json)؛ با اینکار اسکریپت‌های موردنیاز فوق از سمت سرور دریافت خواهند شد:
"proxy": "BLAZOR_APP_ADDRESS", // for example: http://localhost:5269 
در نهایت درون فایل App.js می‌توانیم از کامپوننت Todo استفاده کنیم:
function App() {
  return (
    <div className="App">
      <todo-element />
    </div>
  );
}

export default App;
 

نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
تغییرات مطابق موارد زیر داده شد ولی باز هم همان ارور 
public class CustomNormalizer : ILookupNormalizer
    {
        public string NormalizeName(string key)
        {
            key = Normalize(key);
            key = key.ApplyCorrectYeKe()
                     .RemoveDiacritics()
                     .CleanUnderLines()
                     .RemovePunctuation();
            key = key.Trim().Replace(" ", "");
            return key;
        }
        public string NormalizeEmail(string key)
        {
            key = Normalize(key);
            key = fixGmailDots(key);
            return key;
        }
        public string Normalize(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                return null;
            }

            key = key.Trim();
            key = key.ToUpperInvariant();
            return key;
        }

        private static string fixGmailDots(string email)
        {
            email = email.ToLowerInvariant().Trim();
            var emailParts = email.Split('@');
            var name = emailParts[0].Replace(".", string.Empty);

            var plusIndex = name.IndexOf("+", StringComparison.OrdinalIgnoreCase);
            if (plusIndex != -1)
            {
                name = name.Substring(0, plusIndex);
            }

            var emailDomain = emailParts[1];
            emailDomain = emailDomain.Replace("googlemail.com", "gmail.com");

            string[] domainsAllowedDots =
            {
                "gmail.com",
                "facebook.com"
            };

            var isFromDomainsAllowedDots = domainsAllowedDots.Any(domain => emailDomain.Equals(domain));
            return !isFromDomainsAllowedDots ? email : string.Format("{0}@{1}", name, emailDomain);
        }
    }

اینترفیس ILookupNormalizer   به شکل زیر است

#region Assembly Microsoft.Extensions.Identity.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// C:\Users\kazemi\.nuget\packages\microsoft.extensions.identity.core\3.0.0-preview-18579-0056\lib\netstandard2.0\Microsoft.Extensions.Identity.Core.dll
#endregion

namespace Microsoft.AspNetCore.Identity
{
    //
    // Summary:
    //     Provides an abstraction for normalizing keys for lookup purposes.
    public interface ILookupNormalizer
    {
        //
        // Summary:
        //     Returns a normalized representation of the specified key.
        //
        // Parameters:
        //   key:
        //     The key to normalize.
        //
        // Returns:
        //     A normalized representation of the specified key.
        string Normalize(string key);
    }
}

مطالب
نحوه ایجاد یک نقشه‌ی سایت پویا با استفاده از قابلیت Reflection
طبق این استاندارد قالب نقشه‌ی سایت به فرم زیر می‌باشد:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
   <url>
      <loc>http://www.example.com/</loc>
      <lastmod>2005-01-01</lastmod>
      <changefreq>monthly</changefreq>
      <priority>0.8</priority>
   </url>
</urlset>

که یک فایل XML متشکل از یک تگ urlset  است و این تگ نیز حاوی یک یا چند تگ url می‌باشد. با توجه به تعاریف بالا به یک چنین کلاسی خواهیم رسید: 

public enum ChangeFreq
        {
            Always,
            Hourly,
            Daily,
            Weekly,
            Monthly,
            Yearly,
            Never
        }

        [XmlElement("loc")]
        public string Url { get; set; }

        [XmlElement("lastmod")]
        public DateTime? LastModified { get; set; }
        public bool ShouldSerializeLastModified()
        {
            return LastModified.HasValue;
        }

        [XmlElement("changefreq")]
        public ChangeFreq? ChangeFrequency { get; set; }
        public bool ShouldSerializeChangeFrequency()
        {
            return ChangeFrequency.HasValue;
        }
        [XmlElement("priority")]
        public float? Priority { get; set; }
        public bool ShouldSerializePriority()
        {
            return Priority.HasValue;
        }
    }

دقت داشته باشید که چون پروپرتی‌های LastModified ، ChangeFrequency و Priority از نوع Nullable تعریف شده‌اند، پس باید کاری کنیم در صورتیکه این پروپرتی‌ها نال بودند سریالیز نشوند. بدین منظور از تابع ShouldSerialize[MemberName] استفاده می‌شود. این تابع  جزئی از دات نت است. کافی است بعد از ShouldSerialize نام پروپرتی را ذکر کنید. حال به کلاس دیگری نیاز داریم تا لیستی از کلاس فوق را دربر داشته باشد. 

[XmlRoot("urlset",Namespace = "http://www.sitemaps.org/schemas/sitemap/0.9")]
    public class SiteMp
    {
        private readonly List<Location> _locations;

        public SiteMp()
        {
            _locations = new List<Location>();
        }

        [XmlElement("url")]
        public List<Location> Locations
        {
            get { return _locations; }
            set
            {
                foreach (var location in value)
                {
                    Add(location);
                }
            } 
            
        }

        public void Add(Location location)
        {
            _locations.Add(location);
        }
    }

حال برای پردازش کلاس بالا لازم است ActionResultی را طراحی کنیم تا خروجی Response را به فرمت XML پردازش کند:

public class XmlResult : ActionResult
    {
        private readonly object _objectToSerialize;

        public XmlResult(object objectToSerialize)
        {
            _objectToSerialize = objectToSerialize;
        }
        public override void ExecuteResult(ControllerContext context)
        {
            if (_objectToSerialize == null)
               return;
             context.HttpContext.Response.Clear();
             var xmlSerializer = new XmlSerializer(_objectToSerialize.GetType());
             context.HttpContext.Response.ContentType = "text/xml";
             xmlSerializer.Serialize(context.HttpContext.Response.Output, _objectToSerialize);            
        }
    }

و در آخر یک کنترلر ساخته و به صورت زیر از آن استفاده می‌کنیم: 

public class SiteMapController : Controller
    {
        // GET: SiteMap
        public ActionResult Index()
        {
            SiteMp siteMap = new SiteMp();
            siteMap.Add(new Location
            {
                Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/Home/Index"
            });
            siteMap.Add(new Location
            {
                Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/Home/NewRequest",
                ChangeFrequency = Location.ChangeFreq.Always,
                LastModified = DateTime.UtcNow,
                Priority = 0.5f
            });
            siteMap.Add(new Location
            {
                Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/Home/FindRequest",
                ChangeFrequency = Location.ChangeFreq.Always,
                LastModified = DateTime.UtcNow,
                Priority = 0.5f
            });
            siteMap.Add(new Location
            {
                Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/ContactUs/Index",
                ChangeFrequency = Location.ChangeFreq.Daily,
                LastModified = DateTime.UtcNow,
                Priority = 0.5f
            });
            return new XmlResult(siteMap);
        }

اگر دقت کنید لینک‌های ثابت باید به صورت دستی اضافه شوند. سناریویی را تصور کنید که لینک‌ها زیاد باشند(جدای از لینک هایی که از دیتابیس لود می‌شوند) این کار کمی ناجور به نظر می‌رسد. در اینجا میخواهیم از طریق امکانات ،Reflection عمل اضافه کردن لینک به صورت خودکار انجام شود. 

public class ControllerScanner
    {
       public static List<string> ScanAllControllers(HttpRequestBase requestBase)
        {
            Assembly asm = Assembly.GetAssembly(typeof(MvcApplication));

            var controllerActionlist = asm.GetTypes()
                .Where(type => typeof (Controller).IsAssignableFrom(type))
                .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
                .Where((returnType => returnType.ReturnType == (typeof(ViewResult)) || returnType.ReturnType==(typeof(ActionResult))))
                .Select(
                    x =>
                        new
                        {
                            Controller = x.DeclaringType.Name,
                            Action = x.Name,
                            ReturnType = x.ReturnType.Name

                        })
                .OrderBy(x => x.Controller).ThenBy(x => x.Action).Distinct().ToList();

            if (requestBase.Url == null)
                return null;

            var url = requestBase.Url.GetLeftPart(UriPartial.Authority);
            return controllerActionlist.Select(controller => $"{url}/{controller.Controller}/{controller.Action}").ToList();
        }
    }

حال از کلاس بالا در کنترلر SiteMap به صورت زیر استفاده می‌کنیم :

public class SiteMapController : Controller
    {
        // GET: SiteMap
        public ActionResult Index()
        {
            var siteMap = new SiteMap();
            var controllers = ControllerScanner.ScanAllControllers(Request);
            foreach (var controller in controllers)
            {
                siteMap.Add(new Location
                {
                    Url = controller,
                    ChangeFrequency = Location.ChangeFreq.Always,
                    LastModified = DateTime.UtcNow,
                    Priority = 0.5f
                });
            }
            return new XmlResult(siteMap);
        }        
    }

در آخر نیز سطر زیر را به سیستم مسیریابی اضافه نمایید تا در صورت درخواست فایل sitemap.xml  اکشن Index از کنترلر SiteMap فراخوانی شود.

 routes.MapRoute(
                "SiteMap", // Route name
                "sitemap.xml", // URL with parameters
                new { controller = "Sitemap", action = "Index", name = UrlParameter.Optional, area = "" }
            );