مطالب
MVVM و امکان استفاده از یک وهله از ViewModel جهت چند View مرتبط

عموما هنگام طراحی یک View، خیلی زود به حجم انبوهی از کدهای XAML خواهیم رسید. در ادامه بررسی خواهیم کرد که چطور می‌توان یک View را به چندین View خرد کرد، بدون اینکه نیازی باشد تا از چندین ViewModel (یا همان code behind عاری از ارجاعات بصری سابق قرار گرفته در یک پروژه جدای دیگر) استفاده شود و تمام این View های خرد شده هم تنها از یک وهله از ViewModel ایی خاص استفاده کنند و با اطلاعاتی یکپارچه سروکار داشته باشند؛ یا در عمل یکپارچه کار کنند.
این مشکل از جایی شروع می‌شود که مثلا خرد کردن یک user control به چند یوزر کنترل، یعنی کار کردن با چند وهله از اشیایی متفاوت. هر چند نهایتا تمام این‌ها قرار است در یک صفحه در کنار هم قرار گیرند اما در عمل از هم کاملا مجزا هستند و اگر به ازای هر کدام یکبار ViewModel را وهله سازی کنیم، به مشکل برخواهیم خورد؛ چون هر وهله نسبت به وهله‌ای دیگر ایزوله است. اگر در یکی Name مثلا Test بود در دیگری ممکن است مقدار پیش فرض نال را داشته باشد؛ چون با چند وهله از یک کلاس، در یک فرم نهایی سروکار خواهیم داشت.

ابتدا Model و ViewModel ساده زیر را در نظر بگیرید:
using System.ComponentModel;

namespace SplittingViewsInMvvm.Models
{
public class GuiModel : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
raisePropertyChanged("Name");
}
}

string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
raisePropertyChanged("LastName");
}
}

#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
void raisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

using SplittingViewsInMvvm.Models;

namespace SplittingViewsInMvvm.ViewModels
{
public class MainViewModel
{
public GuiModel GuiModelData { set; get; }

public MainViewModel()
{
GuiModelData = new GuiModel();
GuiModelData.Name = "Name";
GuiModelData.LastName = "LastName";
}
}
}

سپس View زیر هم از این اطلاعات استفاده خواهد کرد:

<UserControl x:Class="SplittingViewsInMvvm.Views.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:VM="clr-namespace:SplittingViewsInMvvm.ViewModels"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<VM:MainViewModel x:Key="vmMainViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource vmMainViewModel}}">
<GroupBox Margin="2" Header="Group 1">
<TextBlock Text="{Binding GuiModelData.Name}" />
</GroupBox>
<GroupBox Margin="2" Header="Group 2">
<TextBlock Text="{Binding GuiModelData.LastName}" />
</GroupBox>
</StackPanel>
</UserControl>

اکنون فرض کنید که می‌خواهیم Group 1 و Group 2 را جهت مدیریت ساده‌تر View اصلی در دو user control مجزا قرار دهیم؛ مثلا:

<UserControl x:Class="SplittingViewsInMvvm.Views.Group1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<GroupBox Margin="2" Header="Group 1">
<TextBlock Text="{Binding GuiModelData.Name}" />
</GroupBox>
</Grid>
</UserControl>
و
<UserControl x:Class="SplittingViewsInMvvm.Views.Group2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<GroupBox Margin="2" Header="Group 2">
<TextBlock Text="{Binding GuiModelData.LastName}" />
</GroupBox>
</Grid>
</UserControl>

اکنون اگر این دو را مجددا در همان View اصلی ساده شده قبلی قرار دهیم (بدون اینکه در هر user control به صورت جداگانه data context را تنظیم کنیم):
<UserControl x:Class="SplittingViewsInMvvm.Views.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:V="clr-namespace:SplittingViewsInMvvm.Views"
xmlns:VM="clr-namespace:SplittingViewsInMvvm.ViewModels"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<VM:MainViewModel x:Key="vmMainViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource vmMainViewModel}}">
<V:Group1 />
<V:Group2 />
</StackPanel>
</UserControl>

باز هم .... برنامه همانند سابق کار خواهد کرد و ViewModel وهله سازی شده در user control فوق به صورت یکسانی در اختیار هر دو View اضافه شده قرار می‌گیرد و نهایتا یک View یکپارچه را در زمان اجرا می‌توان مورد استفاده قرار داد. علت هم بر می‌گردد به مقدار دهی خودکار DataContext هر View اضافه شده به بالاترین DataContext موجود در Visual tree که ذکر آن الزامی نیست:

<UserControl x:Class="SplittingViewsInMvvm.Views.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:V="clr-namespace:SplittingViewsInMvvm.Views"
xmlns:VM="clr-namespace:SplittingViewsInMvvm.ViewModels"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<VM:MainViewModel x:Key="vmMainViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource vmMainViewModel}}">
<V:Group1 DataContext="{Binding}" />
<V:Group2 DataContext="{Binding}"/>
</StackPanel>
</UserControl>


بنابراین به صورت خلاصه زمانیکه از MVVM استفاده ‌می‌کنید لازم نیست کار خاصی را جهت خرد کردن یک View به چند Sub View انجام دهید! فقط این‌ها را در چند User control جدا کنید و بعد مجددا به کمک فضای نامی که تعریف خواهد (مثلا V در اینجا) در همان View اصلی تعریف کنید. بدون هیچ تغییر خاصی باز هم برنامه همانند سابق کار خواهد کرد.


اشتراک‌ها
روش فعالسازی IE Mode در Chromium Edge

As you may remember, Microsoft has removed the IE Mode feature from Microsoft Edge. THe company has limited it to enterprise customers and IT admins. They have also stated that the feature was released to the public for testing purposes only. Here is a bit of good news - you can re-enable IE Mode when needed with recent versions of Edge! 

روش فعالسازی IE Mode در Chromium Edge
نظرات مطالب
یافتن Contextهای Dispose نشده در Entity framework
سلام
اگر امکان دارد ارتباط این مطلب رو با Unit of work که در قسمت 12 آموزش Code First بیان نمودید ، توضیح دهید.
اگر درست فهمیده باشم بیان شد الگوی واحد کار برای جلوگیری وهله سازی در هر متود، به کار گرفته میشود در صورتی که هدف مقاله فعلی پیدا کردن وهله‌های dispose نشده درون متدهای برنامه است.
نظرات مطالب
راحت بگویید نه!
ممنون از نظرتون، در کل هدف من برنامه نویس‌ها بوده و منظورم از ما بیکار نشسته ایم هم به افرادی است که بطور حرفه ای کار میکنند ولی چون تحرک فیزیکی ندارند از دید دیگران بیکار بنظر میان.
منظور ازچرخ ساز هم کسی رو میگم که میخواد مسائل حل شده از قبل رو مثل ORM‌ها رو یک تنه حل کنه وبا نوشتن یک لایه پر از باگ تک نفره که خیلی از کارهای EF و یا NH را انجام نمیدهد خودش را در تثبیت کند و چگونه کار برنامه نویسی خود را حفظ کنیم؟! 
مطالب
معرفی Reactive extensions
Reactive extensions یا به صورت خلاصه Rx ،کتابخانه‌ی سورس باز تهیه شده‌ای توسط مایکروسافت است که اگر بخواهیم آن‌را به ساده‌ترین شکل ممکن تعریف کنیم، معنای Linq to events را می‌دهد و امکان مدیریت تعامل‌های پیچیده‌ی async را به صورت declaratively فراهم می‌کند. هدف آن بسط فضای نام System.Linq و تبدیل نتایج یک کوئری LINQ به یک مجموعه‌ی Observable است؛ به همراه مدیریت مسایل همزمانی آن.
این افزونه جزو موفق‌ترین کتابخانه‌های دات نتی مایکروسافت در سال‌های اخیر به شما می‌رود؛ تا حدی که معادل‌های بسیاری از آن برای زبان‌های دیگر مانند Java، JavaScript، Python، ‍CPP و غیره نیز تهیه شده‌اند.


استفاده از Rx به همراه یک کوئری LINQ

یک برنامه‌ی کنسول جدید را ایجاد کنید. سپس برای نصب کتابخانه‌ی Rx، دستور ذیل را در کنسول پاورشل نیوگت اجرا نمائید:
 PM> Install-Package Rx-Main
نصب آن از طریق نیوگت، به صورت خودکار کلیه وابستگی‌های مرتبط با آن‌را نیز به پروژه‌ی جاری اضافه می‌کند:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Rx-Core" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Interfaces" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Linq" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Main" version="2.2.4" targetFramework="net45" />
  <package id="Rx-PlatformServices" version="2.2.4" targetFramework="net45" />
</packages>
سپس متد Main این برنامه را به نحو ذیل تغییر دهید:
using System;
using System.Linq;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            var query = Enumerable.Range(1, 5).Select(number => number);
            foreach (var number in query)
            {
                Console.WriteLine(number);
            }
            finished();
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
در اینجا یک سری عملیات متداول را مشاهده می‌کنید. بازه‌ای از اعداد توسط متد Enumerable.Range ایجاد شده و سپس به کمک یک حلقه‌، تمام آیتم‌های آن نمایش داده می‌شوند. همچنین در پایان کار نیز یک متد دیگر فراخوانی شده‌است.
اکنون اگر بخواهیم همین عملیات را توسط Rx انجام دهیم، به شکل زیر خواهد بود:
using System;
using System.Linq;
using System.Reactive.Linq;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            var query = Enumerable.Range(1, 5).Select(number => number);
            var observableQuery = query.ToObservable();
            observableQuery.Subscribe(onNext: number => Console.WriteLine(number), onCompleted: () => finished());
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
ابتدا نیاز است تا کوئری متداول LINQ را تبدیل به نمونه‌ی Observable آن کرد. اینکار را توسط متد الحاقی ToObservable که در فضای نام System.Reactive.Linq تعریف شده‌است، انجام می‌دهیم. به این ترتیب، هر زمانیکه که عددی به query اضافه می‌شود، با استفاده از متد Subscribe می‌توان تغییرات آن‌را تحت کنترل قرار داد. برای مثال در اینجا هربار که عددی در بازه‌ی 1 تا 5 تولید می‌شود، یکبار پارامتر onNext اجرا خواهد شد. برای نمونه در مثال فوق، از نتیجه‌ی آن برای نمایش مقدار دریافتی، استفاده شده‌است. سپس توسط پارامتر اختیاری onCompleted، در پایان کار، یک متد خاص را می‌توان فراخوانی کرد. خروجی برنامه در این حالت نیز به صورت ذیل است:
1
2
3
4
5
Done!
البته اگر قصد خلاصه نویسی داشته باشیم، سطر آخر متد Main، با سطر ذیل یکی است:
 observableQuery.Subscribe(Console.WriteLine, finished);

در این مثال ساده صرفا یک Syntax دیگر را نسبت به حلقه‌ی foreach متداول مشاهده کردیم که اندکی فشرده‌تر است. در هر دو حالت نیز عملیات انجام شده در تردجاری صورت گرفته‌اند. اما قابلیت‌ها و ارزش‌های واقعی Rx زمانی آشکار خواهند شد که پردازش موازی و پردازش در تردهای دیگر را در آن فعال کنیم.


الگوی Observer

Rx پیاده سازی کننده‌ی الگوی طراحی شیءگرایی به نام Observer است. برای توضیح آن یک لامپ و سوئیچ برق را درنظر بگیرید. زمانیکه لامپ مشاهده می‌کند سوئیچ برق در حالت روشن قرار گرفته‌است، روشن خواهد شد و برعکس. در اینجا به سوئیچ، subject و به لامپ، observer گفته می‌شود. هر زمان که حالت سوئیچ تغییر می‌کند، از طریق یک callback، وضعیت خود را به observer اعلام خواهد کرد. علت استفاده از callbackها، ارائه راه‌حل‌های عمومی است تا بتواند با انواع و اقسام اشیاء کار کند. به این ترتیب هر بار که شیء observer از نوع متفاوتی تعریف می‌شود (مثلا بجای لامپ یک خودرو قرار گیرد)، نیازی نخواهد بود تا subject را تغییر داد.
در Rx دو اینترفیس معادل observer و subject تعریف شده‌اند. در اینجا اینترفیس IObserver معادل observer است و اینترفیس IObservable معادل subject می‌باشد:
    class Subject : IObservable<int>
    {
        public IDisposable Subscribe(IObserver<int> observer)
        {
        }
    }
کار متد Subscribe، اتصال به Observer است و برای این حالت نیاز به کلاسی دارد که اینترفیس IObserver را پیاده سازی کند.
    class Observer : IObserver<int>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(int value)
        {
        }
    }
در اینجا OnCompleted زمانی اجرا می‌شود که پردازش مجموعه‌ای از اعداد int پایان یافته باشد. OnError در زمان وقوع استثنایی اجرا می‌شود و OnNext به ازای هر عدد موجود در مجموعه‌ی در حال پردازش، یکبار اجرا می‌شود. البته نیازی به پیاده سازی صریح این اینترفیس نیست و توسط متد توکار Observer.Create می‌توان به همین نتیجه رسید.
مجموعه‌های Observable کلید کار با Rx هستند. در مثال قبل ملاحظه کردیم که با استفاده از متد الحاقی ToObservable بر روی یک کوئری LINQ و یا هر نوع IEnumerable ایی،  می‌توان یک مجموعه‌ی Observable را ایجاد کرد. خروجی کوئری حاصل از آن به صورت خودکار اینترفیس IObservable را پیاده سازی می‌کند که دارای یک متد به نام Subscribe است.
در متد Subscribe کاری که به صورت خودکار صورت خواهد گرفت، ایجاد یک حلقه‌ی foreach بر روی مجموعه‌ی مورد آنالیز و سپس فراخوانی متد OnNext کلاس پیاده سازی کننده‌ی IObserver به ازای هر آیتم موجود در مجموعه است (فراخوانی observer.OnNext). در پایان کار هم فقط return this در اینجا صورت خواهد گرفت. در حین پردازش حلقه، اگر خطایی رخ دهد، متد observer.OnError انجام می‌شود.

در مثال قبل،کوئری LINQ نوشته شده، خروجی از نوع IObservable ندارد. به کمک متد الحاقی ToObservable:
public static System.IObservable<TSource> ToObservable<TSource>(
    this System.Collections.Generic.IEnumerable<TSource> source,
    System.Reactive.Concurrency.IScheduler scheduler)
به صورت خودکار، IEnumerable حاصل از کوئری LINQ را تبدیل به یک IObservable کرده‌ایم. به این ترتیب اکنون کوئری LINQ ما همانند سوئیچ برق عمل می‌کند و با تغییر آیتم‌های موجود در آن، مشاهده‌گرهایی که به آن متصل شده‌اند (از طریق فراخوانی متد Subscribe)، امکان دریافت سیگنال‌های تغییر وضعیت آن‌را خواهند داشت.
البته استفاده از متد Subscribe به نحوی که در مثال قبل ذکر شد، خلاصه شده‌ی الگوی Observer است. اگر بخواهیم دقیقا مانند الگو عمل کنیم، چنین شکلی را خواهد داشت:
 var query = Enumerable.Range(1, 5).Select(number => number);
var observableQuery = query.ToObservable();
var observer = Observer.Create<int>(onNext: number => Console.WriteLine(number));
observableQuery.Subscribe(observer);
ابتدا توسط متد ToObservable یک IObservable (سوئیچ) را ایجاد کرده‌ایم. سپس توسط کلاس Observer موجود در فضای نام System.Reactive، یک IObserver (لامپ) را ایجاد کرده‌ایم. کار اتصال سوئیچ به لامپ در متد Subscribe انجام می‌شود. اکنون هر زمانیکه تغییری در وضعیت observableQuery حاصل شود، سیگنالی را به observer ارسال می‌کند. در اینجا callbacks کار مدیریت observer را انجام می‌دهند.


پردازش نتایج یک کوئری LINQ در تردی دیگر توسط Rx

برای اجرای نتایج متد Subscribe در یک ترد جدید، می‌توان پارامتر scheduler متد ToObservable را مقدار دهی کرد:
using System;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Thread-Id: {0}", Thread.CurrentThread.ManagedThreadId);
            var query = Enumerable.Range(1, 5).Select(number => number);
            var observableQuery = query.ToObservable(scheduler: NewThreadScheduler.Default);
            observableQuery.Subscribe(onNext: number =>
            {
                Console.WriteLine("number: {0}, on Thread-id: {1}", number, Thread.CurrentThread.ManagedThreadId);
            }, onCompleted: () => finished());
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
خروجی این مثال به نحو ذیل است:
 Thread-Id: 1
number: 1, on Thread-id: 3
number: 2, on Thread-id: 3
number: 3, on Thread-id: 3
number: 4, on Thread-id: 3
number: 5, on Thread-id: 3
Done!
پیش از آغاز کار و در متد Main، ترد آی دی ثبت شده مساوی 1 است. سپس هربار که callback متد Subscribe فراخوانی شده‌است، ملاحظه می‌کنید که ترد آی دی آن مساوی عدد 3 است. به این معنا که کلیه نتایج در یک ترد مشخص دیگر پردازش شده‌اند.
NewThreadScheduler.Default در فضای نام System.Reactive.Concurrency واقع شده‌است.


یک نکته
در نگارش‌های آغازین Rx، مقدار scheduler را می‌شد معادل Scheduler.NewThread نیز قرار داد که در نگارش‌های جدید منسوخ شده درنظر گرفته شده و به زودی حذف خواهد شد. معادل‌های جدید آن اکنون NewThreadScheduler.Default، ThreadPoolScheduler.Default و امثال آن هستند.


مدیریت خاتمه‌ی اعمال انجام شده‌ی در تردهای دیگر توسط Rx

یکی از مواردی که حین اجرای نتیجه‌ی callbackهای پردازش شده‌ی در تردهای دیگر نیاز است بدانیم، زمان خاتمه‌ی کار آن‌ها است. برای نمونه در مثال قبل، نمایش Done پس از پایان تمام callbacks انجام شده‌است. فرض کنید، callback پایان عملیات را حذف کرده و متد finished را پس از فراخوانی متد observableQuery.Subscribe قرار دهیم:
observableQuery.Subscribe(onNext: number =>
{
   Console.WriteLine("number: {0}, on Thread-id: {1}", number,     
                              Thread.CurrentThread.ManagedThreadId);
}/*, onCompleted: () => finished()*/);
finished();
اینبار اگر برنامه را اجرا کنیم به خروجی ذیل خواهیم رسید:
 Thread-Id: 1
number: 1, on Thread-id: 3
Done!
number: 2, on Thread-id: 3
number: 3, on Thread-id: 3
number: 4, on Thread-id: 3
number: 5, on Thread-id: 3
این خروجی بدین معنا است که متد  observableQuery.Subscribeدر حین اجرا شدن در تردی دیگر، صبر نخواهد کرد تا عملیات مرتبط با آن خاتمه یابد و سپس سطر بعدی را اجرا کند. بنابراین برای حل این مشکل، تنها کافی است به آن اعلام کنیم که پس از پایان عملیات، onCompleted را اجرا کن.


مدیریت استثناهای رخ داده در حین پردازش مجموعه‌های واکنشگرا

متد Subscribe دارای چندین overload است. تا اینجا نمونه‌ای که دارای پارامترهای onNext و onCompleted بودند را بررسی کردیم. اگر بخواهیم مدیریت استثناءها را نیز در اینجا اضافه کنیم، فقط کافی است از overload دیگر آن که دارای پارامتر onError است، استفاده نمائیم:
observableQuery.Subscribe(
  onNext: number => Console.WriteLine(number),
  onError: exception => Console.WriteLine(exception.Message),
  onCompleted: () => finished());
اگر callback پارامتر onError اجرا شود، دیگر به onCompleted نخواهیم رسید. همچنین دیگر onNext ایی نیز اجرا نخواهد شد.


مدیریت ترد اجرای نتایج حاصل از Rx در یک برنامه‌ی دسکتاپ WPF یا WinForms

تا اینجا مشاهده کردیم که اجرای callbackهای observer در یک ترد دیگر، به سادگی تنظیم پارامتر scheduler متد ToObservable است. اما در برنامه‌های دسکتاپ برای به روز رسانی عناصر رابط کاربری، حتما باید در تردی قرار داشته باشیم که آن رابط کاربری در آن ایجاد شده‌است یا به عبارتی در ترد اصلی برنامه؛ در غیر اینصورت برنامه کرش خواهد کرد. مدیریت این مساله نیز در Rx بسیار ساده‌است. ابتدا نیاز است بسته‌ی Rx-WPF را نصب کرد:
 PM> Install-Package Rx-WPF
سپس توسط متد ObserveOn می‌توان مشخص کرد که نتیجه‌ی عملیات باید بر روی کدام ترد اجرا شود:
 observableQuery.ObserveOn(DispatcherScheduler.Current).Subscribe(...)
روش دیگر آن استفاده از متد ObserveOnDispatcher می‌باشد:
 observableQuery.ObserveOnDispatcher().Subscribe(...)
بنابراین مشخص سازی پارامتر scheduler متد ToObservable، به معنای اجرای query آن در یک ترد دیگر و استفاده از متد ObserveOn، به معنای مشخص سازی ترد اجرای callbackهای مشاهده‌گر است.

و یا اگر از WinForms استفاده می‌کنید، ابتدا بسته‌ی Rx خاص آن‌را نصب کنید:
 PM> Install-Package Rx-WinForms
و سپس ترد اجرای callbackها را SynchronizationContext.Current مشخص نمائید:
 observableQuery.ObserveOn(SynchronizationContext.Current).Subscribe(...)

یک نکته‌
در Rx فرض می‌شود که کوئری شما زمانبر است و callbackهای مشاهده‌گر سریع عمل می‌کنند. بنابراین هدف از callbackهای آن، پردازش‌های سنگین نیست. جهت آزمایش این مساله، اینبار query ابتدایی برنامه را به شکل ذیل تغییر دهید که در آن بازگشت زمانبر یک سری داده شبیه سازی شده‌اند.
 var query = Enumerable.Range(1, 5).Select(number =>
{
   Thread.Sleep(250);
   return number;
});
سپس با استفاده از متد ToObservable، ترد دیگری را برای اجرای واقعی آن مشخص کنید تا در حین اجرای آن برنامه در حالت هنگ به نظر نرسد و سپس نمایش آن‌را به کمک متد ObserveOn، بر روی ترد اصلی برنامه انجام دهید.
مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
React یک کتابخانه‌ی جاوا اسکریپتی، برای ساخت رابط‌های کاربری سریع و تعاملی است. توسعه‌ی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوب‌ترین کتابخانه‌ی جاوا اسکریپتی در این رده‌است:


به همین جهت اگر می‌خواهید رزومه‌ی غنی‌تری را ارائه دهید، فراگیری React می‌تواند موقعیت‌های شغلی بیشتری را نصیب شما کند.


ساختار کلی یک برنامه‌ی React

کامپوننت‌ها (جزئی از یک رابط کاربری) قلب هر برنامه‌ی React ای را تشکیل می‌دهند. برای ساخت یک برنامه‌ی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب می‌کنیم تا به رابط کاربری نهایی برسیم.
هر برنامه‌ی React، حداقل از یک کامپوننت تشکیل می‌شود که به آن Root component هم می‌گویند. این کامپوننت بیانگر کل برنامه‌است و دربرگیرنده‌ی مابقی Child components برنامه است. بنابراین ساختار هر برنامه‌ی React، شبیه به درختی از کامپوننت‌ها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید می‌خواهیم UI برنامه‌ای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحه‌ی توئیتر، به کامپوننت‌هایی شکسته می‌شود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقه‌ی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامه‌ی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننت‌های برنامه مانند nav bar ، trends ، profile و feed را انجام می‌دهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز می‌تواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننت‌های tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل می‌دهد. هر کدام از آن‌ها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار می‌گیرند تا UI نهایی را شکل دهند:



هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل می‌شود:
class Tweet {
 state = {};
 
 render() {
 } 
}
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوه‌ی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المان‌های DOM، نگاشت می‌شود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظه‌است. بنابراین یک برنامه‌ی React تشکیل شده‌است از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته می‌شود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام می‌دهد. زمانیکه در state کامپوننتی تغییری رخ می‌دهد، یک React Element جدید تولید می‌شود. سپس React این شیء جدید را با نمونه‌ی قبلی آن مقایسه کرده و تغییرات رخ‌داده را محاسبه می‌کند. در آخر این تغییرات را به DOM واقعی اعمال می‌کند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمی‌دهیم. در اینجا فقط state یک کامپوننت را تغییر می‌دهیم و سپس React، کار ایجاد شیء UI درون حافظه‌ای متناظر با آن و سپس اعمال آن‌را به UI نهایی قابل مشاهده‌ی در مرورگر، انجام می‌دهد. به همین جهت به این کتابخانه React می‌گویند! چون به تغییرات state کامپوننت‌ها واکنش نشان می‌دهد و سپس DOM واقعی را به روز می‌کند.


Angular یا React؟!

هر دوی React و Angular از لحاظ طراحی کامپوننت‌ها بسیار شبیه به هم هستند؛ اما Angular یک فریم‌ورک است و React تنها یک کتابخانه. تنها کاری را که React انجام می‌دهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننت‌ها. این تمام کاری است که React انجام می‌دهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریع‌تر و ساده‌تر از Angular است. بدیهی است یک برنامه‌ی تک صفحه‌ای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویس‌های HTTP نیز تشکیل می‌شود. در React شما مختار هستید که کتابخانه‌های جانبی فراهم شده‌ی برای آن‌را خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریم‌ورک ارائه می‌شوند.


برپایی محیط توسعه‌ی React

اولین برنامه‌ای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانه‌ی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی می‌کند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آن‌را به روز رسانی کرده‌اید).

اگر هم خیلی پیشترها node.js را نصب کرده‌اید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشه‌های قدیمی آن‌را یکی یکی یافته و دستی حذف کنید . سپس مجددا آن‌را نصب کنید.

در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بسته‌ی create-react-app. نصب سراسری یک بسته یعنی در هر پوشه‌ای می‌توان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمی‌شد، این بسته تنها در پوشه‌ی جاری و با سطح دید مختص به آن، نصب و قابل استفاده می‌شد.

ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونه‌ی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آن‌ها، پنل extensions را در VSCode، از نوار ابزار کنار صفحه‌ی آن، انتخاب کرده و نام‌های فوق را در آن جستجو و سپس نصب کنید.

و یا می‌توانید این فایل را اجرا کرده و تعدادی از افزونه‌های مفید VSCode را یکجا نصب کنید: install-addons.zip

همچنین قابلیت فرمت‌کردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونه‌ها به صورت خودکار صورت گیرد. برای این منظور گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:


علاوه بر این‌ها، جهت کار بهتر با VSCode، بهتر است بررسی کننده‌های کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks

پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحه‌ی نمایش تنظیمات آن، گزینه‌ی Auto fix on save آن‌را انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینه‌ی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آن‌را از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده می‌شود) تا افزونه‌ی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).


ایجاد قالب اولین برنامه‌ی React

در ادامه برای ایجاد اولین برنامه‌ی React، از بسته‌ی create-react-app که پیشتر آن‌را نصب کردیم، استفاده می‌کنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگی‌های مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بسته‌های مرتبط را در npm-cache سیستم نیز ذخیره می‌کند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب می‌کند، بلکه یک development server را برای اجرا و مشاهده‌ی سریع برنامه، webpack را برای یکی کردن فایل‌ها (bundling & minification)، Babel را برای کامپایل کدهای فایل‌های JSX و ... نیز نصب می‌کند. بنابراین به این ترتیب، یک پروژه‌ی تنظیم شده و آماده‌ی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیه‌ی آن نیست.
پس ایجاد برنامه، وارد پوشه‌ی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01
> npm start
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده می‌شود:


development server آن، تغییرات فایل‌های برنامه را تحت نظر قرار می‌دهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.


بررسی ساختار اولین پروژه‌ی React ایجاد شده

ساختار پوشه‌ها و فایل‌های مثال اولیه‌ی ایجاد شده توسط قالب create-react-app به صورت زیر است:


البته شما در این تصویر پوشه‌ی node_modules را که در کنار این پوشه‌ها قرار دارد، مشاهده نمی‌کنید. وجود یک چنین پوشه‌ی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه می‌کند (مصرف حافظه‌ی بالا، به همراه کند شدن شدید آن). اگر نمی‌خواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:


 و سپس در فایل باز شده، یک چنین تنظیمی را می‌توانید اضافه و یا ویرایش و تکمیل کنید:
  "files.exclude": {
    "**/.git": true,
    "**/.svn": true,
    "**/.hg": true,
    "**/CVS": true,
    "**/.DS_Store": true,
    "**/node_modules": true,
    "**/wwwroot": true,
    "**/bower_components": true,
    "**/**/bin": true,
    "**/**/obj": true,
    "**/packages": true
  },

در ادامه پوشه‌ی public این پروژه را مشاهده می‌کنید. تمام فایل‌هایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار می‌گیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آن‌را بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آن‌ها ذکر manifest.json است، مشاهده می‌کنید. کار فایل manifest.json، ارائه‌ی یک سری متادیتای خاص مخصوص دستگاه‌های موبایل است که در آن‌ها بجای favicon.ico، می‌توان از تصاویر و یا آیکن‌های بزرگتری مانند فایل‌های png موجود در پوشه‌ی public، استفاده کرد. در ادامه‌ی این فایل، به تنظیم زیر می‌رسیم:
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
div با id مساوی root، محل ارائه‌ی کل برنامه‌ی React ما است.

در پوشه‌ی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحه‌ی مشکی پیش‌فرض این قالب را انجام می‌دهد. در این فایل، شاهد بازگشت یک چنین تگ‌هایی هستیم:
  return (
    <div className="App">
      <header className="App-header">
       ... 
      </header>
    </div>
  );
احتمالا تابحال چنین return ای را در برنامه‌های جاوا اسکریپتی مشاهده نکرده‌اید؛ چون درج آن‌ها در فایل‌های js به این نحو، غیرمجاز است. این تگ‌ها نه رشته‌ای هستند و نه HTML خالص. به آن jsx گفته می‌شود که مخفف JavaScript XML می‌باشد. کار آن ارائه‌ی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژه‌ای به نام Babel عبور می‌کند و تبدیل به کدهای جاوا اسکریپتی خالصی می‌شود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;


همانطور که مشاهده می‌کنید، این قطعه کد jsx (که یک رشته‌ی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شده‌است:
"use strict";

var element = React.createElement("h1", null, "Hello World!");

بدیهی است نوشتن کدهای jsx، ساده‌تر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسان‌تر است. به همین جهت در کدهای React، ما از jsx استفاده می‌کنیم و تفسیر آن‌را به Babel واگذار خواهیم کرد.

در پوشه‌ی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطه‌ی آغازین برنامه را مشخص می‌کند. در قسمت‌های بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده می‌کنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شده‌است و کار آن کمک به ارائه‌ی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.


نوشتن اولین برنامه‌ی React

به پوشه‌ی src ایجاد شده مراجعه کرده و تمام فایل‌های موجود و پیش‌فرض آن‌را حذف کنید. در ادامه خودمان آن‌ها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد می‌کنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react";

const element = <h1>Hello World!</h1>;
console.log(element);
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کرده‌ایم. چون از jsx استفاده می‌کنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که می‌تواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره می‌کنیم، خروجی نهایی را در مرورگر نمایش می‌دهد (اگر هم آن‌را بسته‌اید، یکبار از طریق خط فرمان، دستور npm start را در ریشه‌ی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته می‌شود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحه‌ی سفید را مشاهده خواهید کرد. اکنون دکمه‌ی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.


شیءای را که در اینجا مشاهده می‌کنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کرده‌است که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظه‌ای DOM مختص React، جهت محاسبه‌ی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آن‌ها به DOM اصلی در مرورگر).
اکنون می‌خواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react";
import ReactDOM from "react-dom";

const element = <h1>Hello World!</h1>;
console.log(element);

ReactDOM.render(element, document.getElementById("root"));
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شده‌است. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آن‌را مشخص می‌کنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره می‌کند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:


همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کرده‌است.

هدف از این مثال ساده، نمایش نحوه‌ی کارکرد React، در پشت صحنه بود. در یک برنامه‌ی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشه‌ای برنامه بوده و می‌تواند شامل درختی از کامپوننت‌ها که UI نهایی را تشکیل می‌دهند، شود.


نگاهی به تنظیمات پروژه‌ی ایجاد شده

اگر فایل package.json پروژه را باز کنید، یک چنین بسته‌هایی در آن درج شده‌است:
{
  "name": "sample-01",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-scripts": "3.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
در اینجا صرفا سه بسته‌ی react، react-dom و react-scripts را در قسمت dependencies مشاهده می‌کنید که کل Importهای ما را تشکیل می‌دهند.
بسته‌ی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام می‌دهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start می‌شود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بسته‌ی نهایی بهینه سازی شده را تولید می‌کند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنه‌ی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-01.zip

در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی می‌کنیم.
نظرات مطالب
مدیریت طول عمر DbContext در برنامه‌های Blazor SSR

اگر در یک صفحه، سه کامپوننت به این صورت وجود داشته باشند:

<ComponentA/>
<ComponentB/>
<ComponentC/>

پردازش این‌ها در Blazor SSR، ترتیبی نیست و موازی است (هر کدام، بر روی یک ترد مجزا پردازش می‌شوند). اما اگر کامپوننت B، داخل کامپوننت A باشد، این‌ها با هم و در طی یک ترد پردازش می‌شوند. هرچند این بحث پردازش موازی هم در صورت فراهم بودن منابع سیستمی و سخت‌افزاری، تردهای آزاد در thread-pool و موارد دیگر، ممکن است رخ‌دهد یا خیر. بنابراین گاهی از اوقات، در طول مدتی، خطایی را مشاهده نمی‌کنید؛ اما ... اگر به لاگ‌های سیستم در طول یک روز مراجعه کنید، وجود این خطاها کاملا مشخص است. بنابراین بهتر است بر اساس «شانس» کار نکنید. inject AppDbContext appDbContext@ به این معنا است که فقط یک نمونه از DbContext، در طول درخواست جاری (از این لحاظ، Blazor SSR با ASP.NET Core یکسان رفتار می‌کند) بین تمام اجزای در حال پردازش صفحه‌ی در حال رندر، به اشتراک گذاشته شود. «ممکن است» در صورت عدم وجود پردازش موازی، هیچ خطایی را دریافت نکنید و یا ... به صورت «اتفاقی» و با مهیا بودن شرایط سیستمی و سخت‌افزاری، خطای یاد شده را مشاهده کنید.

مطالب
در مورد ادامه ...

در حال تهیه یک سیستم blogging هستم که بشود آن‌را چند کاربره یا چند نویسنده‌ای کرد. این سیستم جدید تا یک ماه دیگر جایگزین سیستم وصله پینه‌ای جاری خواهد شد (متن یکجا، css لینک شده از یک سایت دیگر، syntax highlighting از یک سایت دیگر، عکس‌ها در یک سرور مجزا، کامنت‌ها کلا در یک سرور دیگر (دیسکاس) بجز هاست بلاگر، لینک‌های روزانه در یک سایت ثالث، فایل‌های مثال‌ها در سروری دیگر! هاست اصلی بلاگ (بلاگر) هم کلا در ایران فیلتر است!). البته همین سیستم وصله پینه‌ای 4 سال است که داره کار می‌کنه!
به همین دلیل برای پیدا کردن وقت جهت بازنویسی سیستم، این سایت تا یک ماه دیگر در حالت تعلیق خواهد بود.
در این بین اگر علاقمند به مشارکت در بلاگ جدید (با همین آدرس با همین عنوان) به عنوان نویسنده هستید، یک ایمیل به من ارسال کنید (vahid_nasiri در یاهو). موضوع سایت مشخص است. اهداف آن هم به همین ترتیب. فقط فنی است. تنها مطالب فنی قرار است ارسال شود. پس از آماده شدن سایت جدید، یک اکانت نویسنده مطلب برای شما ارسال خواهد شد. با تشکر از همکاری شما.

نظرات مطالب
Strong Name
یک نکته‌ی تکمیلی: روش امضاء کردن با نام قوی، در نگارش‌های جدیدتر دات‌نت

فرض کنید قصد دارید یک کتابخانه‌ی عمومی را منتشر کنید. مراحل امضاء کردن اسمبلی آن با نام قوی در نگارش‌های اخیر دات‌نت به صورت زیر است:
الف) نیاز به فایل sn.exe، هنوز هم وجود دارد که به همراه Microsoft SDKs نصب می‌شود. عموما اگر یکی از IDEهای معروف را نصب کرده باشید، این SDK هم نصب شده‌است. برای یافتن محل نصب فایل sn.exe فقط کافی است دستور dir /s sn.exe را در ریشه‌ی درایو C خود اجرا کنید. برای مثال به مسیری مانند مسیر زیر خواهید رسید:
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\sn.exe
ب) سپس سه دستور زیر را یکی پس از دیگری در ریشه‌ی پروژه‌ی خود اجرا کنید:
sn.exe -k MyProject.snk
sn.exe -p MyProject.snk public.snk
sn.exe -tp public.snk
دستور اول، فایل snk. جدیدی را تولید می‌کند. دستور دوم، کلید عمومی آن‌را استخراج می‌کند و دستور سوم، این کلید عمومی را نمایش می‌دهد که از آن در ادامه استفاده می‌کنیم.
ج) اطلاعات بدست آمده را به صورت زیر، به فایل csproj. خود اضافه کنید:
<PropertyGroup>
   <SignAssembly>true</SignAssembly>
   <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)MyProject.snk</AssemblyOriginatorKeyFile>
   <PublicKey>0024000004800000940000000602 ...</PublicKey>
</PropertyGroup>
در اینجا نام فایل snk و کلید عمومی استخراج شده، درج می‌شوند. پس از اینکار می‌توان فایل public.snk تولیدی را حذف کرد.

همین اندازه تنظیم در جهت اضافه شدن پروسه‌ی امضاء کردن اسمبلی برنامه با نام قوی، کفایت می‌کند و اکنون جزو پروسه‌ی Build شده‌است. پس از Build برنامه، برای آزمایش امضاء شدن آن‌، دستور زیر را اجرا کنید:
sn.exe -vf MyProj.dll
مطالب
Blazor 5x - قسمت 22 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 2 - ورود به سیستم و خروج از آن
در قسمت قبل، نحوه‌ی افزودن قالب ابتدایی ASP.NET Core Identity را به یک برنامه‌ی Blazor Server بررسی کردیم. در این مطلب، قسمت‌های ورود و خروج آن‌را به همراه نمایش قسمتی از صفحه، تنها به کاربران اعتبارسنجی شده، بررسی می‌کنیم تا روش دسترسی به اطلاعات ASP.NET Core Identity را در یک برنامه‌ی Blazor Server یکپارچه شده‌ی با آن، مطالعه کنیم.


نمایش قسمتی از صفحه بر اساس وضعیت اعتبارسنجی کاربر

فرض کنید می‌خواهیم در کامپوننت Shared\LoginDisplay.razor که در قسمت قبل آن‌را اضافه کردیم، لینک‌های ثبت نام و لاگین را به کاربران غیر اعتبارسنجی شده (هنوز لاگین نکرده) نمایش دهیم و اگر کاربر، اعتبارسنجی شده بود (لاگین کرده بود)، لینک خروج را به او نمایش دهیم. برای این منظور کامپوننت Shared\LoginDisplay.razor را به صورت زیر تغییر می‌دهیم:
<AuthorizeView>
    <Authorized>
        <a href="Identity/Account/Logout">Logout</a>
    </Authorized>
    <NotAuthorized>
        <a href="Identity/Account/Register">Register</a>
        <a href="Identity/Account/Login">Login</a>
    </NotAuthorized>
</AuthorizeView>
AuthorizeView، یکی از کامپوننت‌های استاندارد Blazor Server است. زمانیکه کاربری به سیستم لاگین کرده باشد، فرگمنت Authorized و در غیر اینصورت قسمت NotAuthorized آن‌را مشاهده خواهد کرد.
البته اگر برنامه را در همین حالت اجرا کنیم، به استثنای زیر خواهیم رسید:
InvalidOperationException: Authorization requires a cascading parameter of type Task<AuthenticationState>.
Consider using CascadingAuthenticationState to supply this.
Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync()
برای رفع این مشکل و ارائه‌ی AuthenticationState به تمام کامپوننت‌های یک برنامه‌ی Blazor Server، نیاز است از کامپوننت CascadingAuthenticationState استفاده کرد. در مورد پارامترهای آبشاری، در قسمت نهم این سری بیشتر بحث شد و هدف از آن، ارائه‌ی یکسری اطلاعات، به تمام زیر کامپوننت‌های یک کامپوننت والد است؛ بدون اینکه نیاز باشد مدام این پارامترها را در هر زیر کامپوننتی، تعریف و تنظیم کنیم. همینقدر که آن‌ها را در بالاترین سطح سلسله مراتب کامپوننت‌های تعریف شده تعریف کردیم، در تمام زیر کامپوننت‌های آن نیز در دسترس خواهند بود.
بنابراین به فایل BlazorServer.App\App.razor که محل تعریف ریشه‌ی مسیریابی برنامه‌است، مراجعه کرده و کامپوننت آن‌را با کامپوننت توکار CascadingAuthenticationState محصور می‌کنیم:
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
اینکار سبب می‌شود تا اطلاعات AuthenticationState، بین تمام کامپوننت‌های یک برنامه‌ی Blazor به اشتراک گذاشته شود.

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



آزمایش نمایش منوی خروج برنامه

برای آزمایش برنامه، نیاز است ابتدا یک کاربر جدید را ثبت کنیم؛ چون هنوز هیچ کاربری در آن ثبت نشده‌است و همچنین کاربر پیش‌فرضی را هم به همراه ندارد. در مورد روش ثبت کاربران پیش‌فرض ASP.NET Core Identity، می‌توانید به مطلب «بازنویسی متد مقدار دهی اولیه‌ی کاربر ادمین در ASP.NET Core Identity توسط متد HasData در EF Core» مراجعه کنید و تمام نکات آن، در اینجا هم صادق است (چون پایه‌ی سیستم Identity مورد استفاده، یکی است و هدف ما در اینجا بیشتر بررسی نکات یکپارچه سازی آن با Blazor Server است و نه مرور تمام نکات ریز Identity).
بنابراین ابتدا از منوی بالای صفحه، گزینه‌ی Register را انتخاب کرده و کاربری را ثبت می‌کنیم. پس از ثبت نام، بلافاصله به منوی جدید زیر می‌رسیم که در آن گزینه‌های ورود و ثبت نام، مخفی شده‌اند و اکنون گزینه‌ی خروج از سیستم را نمایش می‌دهد:



بهبود تجربه‌ی کاربری خروج از سیستم

در همین حال که گزینه‌ی خروج نمایش داده شده‌است، اگر بر روی لینک آن کلیک کنیم، ابتدا ما را به صفحه‌ی مجزای logout هدایت می‌کند. سپس باید در این صفحه، مجددا بر روی لینک logout بالای آن کلیک کنیم. زمانیکه اینکار را انجام دادیم، اکنون صفحه‌ی دیگری را نمایش می‌دهد که به همراه پیام «خروج موفقیت آمیز از سیستم» است! در این پروسه، کاربر احساس می‌کند که کاملا از برنامه‌ی اصلی خارج شده‌است و همچنین مراحل طولانی را نیز باید طی کند.
مدیریت این مراحل توسط دو فایل زیر انجام می‌شوند:
Areas\Identity\Pages\Account\Logout.cshtml
Areas\Identity\Pages\Account\Logout.cshtml.cs

می‌خواهیم کدهای این دو فایل را به نحوی تغییر دهیم که اگر کاربری بر روی لینک logout برنامه‌ی اصلی کلیک کرد، به صورت خودکار logout شده و سپس مجددا به صفحه‌ی اصلی برنامه‌ی Blazor Server هدایت شود و مجبور نباشد تا مراحل طولانی یاد شده را تکرار کند.
به همین جهت ابتدا فایل Logout.cshtml.cs را حذف می‌کنیم؛ چون نیازی به آن نداریم. سپس محتوای فایل Logout.cshtml را به صورت زیر تغییر می‌دهیم:
@page
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager

@functions
{
    public async Task<IActionResult> OnGet()
    {
        if (SignInManager.IsSignedIn(User))
        {
            <p>You have successfully logged out of the application.</p>
            await SignInManager.SignOutAsync();
        }
        return Redirect("~/");
    }
}
با استفاده از سرویس SignInManager در ASP.NET Core Identity می‌توان یک کاربر را logout کرد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید. در این حالت بررسی می‌شود که آیا کاربر جاری، به سیستم وارد شده‌است؟ اگر بله، کوکی‌های او حذف شده و سپس به صفحه‌ی اصلی برنامه، Redirect می‌شود. به این ترتیب به تجربه‌ی کاربری خروج بهتری خواهیم رسید.


نمایش User Claims، در یک برنامه‌ی Blazor Server

سیستم ASP.NET Core Identity، بر اساس User Claims کار می‌کند؛ اطلاعات بیشتر. پس از استفاده از CascadingAuthenticationState در بالاترین سطح برنامه، اطلاعات آن در سراسر برنامه‌ی Blazor Server هم قابل دسترسی است. برای مثال در کامپوننت Shared\LoginDisplay.razor، به نحو زیر می‌توان نام کاربر ثبت نام شده را که یکی از User Claims او است، نمایش داد:
<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name
        <a href="Identity/Account/Logout">Logout</a>
    </Authorized>



محدود کردن دسترسی به صفحات برنامه تنها برای کاربران اعتبارسنجی شده

پس از لاگین موفق به سیستم، اکنون می‌خواهیم دسترسی به صفحات تعریف اتاق‌ها و یا امکانات رفاهی هتل را تنها به کاربران لاگین شده، محدود کنیم. برای اینکار تنها کافی است از ویژگی Authorize استفاده کنیم. برای مثال به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و یک سطر زیر را به آن اضافه می‌کنیم:
@attribute [Authorize]
دسترسی به کامپوننتی که دارای دایرکتیو فوق باشد، تنها مختص به کاربران اعتبارسنجی شده‌ی سیستم است.

مشکل! با اینکه تمام کامپوننت‌های مثال جاری را به ویژگی Authorize مزین کرده‌ایم، اما ... کار نمی‌کند! و هنوز هم می‌توان بدون لاگین به سیستم، به محتوای آن‌ها دسترسی داشت.
برای رفع این مشکل، مجددا نیاز است کامپوننت BlazorServer.App\App.razor را ویرایش کرد:
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            @*<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />*@
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <p>Sorry, you do not have access to this page</p>
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
در اینجا RouteView پیشین را کامنت کرده و با AuthorizeRouteView، جایگزین کرده‌ایم. کار آن فعالسازی پردازش ویژگی Authorize افزوده شده‌ی به کامپوننت‌های برنامه‌است. همچنین در اینجا محتوای سفارشی را که در صورت درخواست یک چنین کامپوننت‌هایی نمایش داده می‌شود، در فرگمنت NotAuthorized مشاهده می‌کنید؛ که حتی می‌تواند یک کامپوننت مجزا هم باشد:



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-22.zip