مطالب
ایجاد توالی‌ها در Reactive extensions
در مطلب «معرفی Reactive extensions» با نحوه‌ی تبدیل IEnumerable‌ها به نمونه‌های Observable آشنا شدیم. اما سایر حالات چطور؟ آیا Rx صرفا محدود است به کار با IEnumerableها؟ در ادامه نگاهی خواهیم داشت به نحوه‌ی تبدیل بسیاری از منابع داده دیگر به توالی‌های Observable قابل استفاده در Rx.


روش‌های متفاوت ایجاد توالی (sequence) در Rx

الف) استفاده از متدهای Factory

1) Observable.Create
نمونه‌ای از استفاده از آن‌را در مطلب «معرفی Reactive extensions» مشاهده کردید.
 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);
کار آن، تدارک delegate ایی است که توسط متد Subscribe، به ازای هربار پردازش مقدار موجود در توالی معرفی شده به آن، فراخوانی می‌گردد و هدف اصلی از آن این است که به صورت دستی اینترفیس IObservable را پیاده سازی نکنید (امکان پیاده سازی inline یک اینترفیس توسط Actionها).
البته در این مثال فقط delegate مربوط به onNext را ملاحظه می‌کند. توسط سایر overloadهای آن امکان ذکر delegate‌های OnError/OnCompleted نیز وجود دارد.

2) Observable.Return
برای ایجاد یک خروجی Observable از یک مقدار مشخص، می‌توان از متد جنریک Observable.Return استفاده کرد. برای مثال:
 var observableValue1 = Observable.Return("Value");
var observableValue2 = Observable.Return(2);
در ادامه نحوه‌ی پیاده سازی این متد را توسط Observable.Create مشاهده می‌کنید:
        public static IObservable<T> Return<T>(T value)
        {
            return Observable.Create<T>(o =>
            {
                o.OnNext(value);
                o.OnCompleted();
                return Disposable.Empty;
            });
        }
البته دو سطر نوشته شده در اصل معادل هستند با سطرهای ذیل؛ که ذکر نوع جنریک آن‌ها ضروری نیست. زیرا به صورت خودکار از نوع آرگومان معرفی شده، تشخیص داده می‌شود:
 var observableValue1 = Observable.Return<string>("Value");
var observableValue2 = Observable.Return<int>(2);

3) Observable.Empty
برای بازگشت یک توالی خالی که تنها کار اطلاع رسانی onCompleted  را انجام می‌دهد.
 var emptyObservable = Observable.Empty<string>();
در کدهای ذیل، پیاده سازی این متد را توسط Observable.Create مشاهده می‌کنید:
        public static IObservable<T> Empty<T>()
        {
            return Observable.Create<T>(o =>
            {
                o.OnCompleted();
                return Disposable.Empty;
            });
        }

4) Observable.Never
برای بازگشت یک توالی بدون قابلیت اطلاع رسانی و notification
 var neverObservable = Observable.Never<string>();
این متد به نحو زیر توسط Observable.Create پیاده سازی شده‌است:
        public static IObservable<T> Never<T>()
        {
            return Observable.Create<T>(o =>
            {
                return Disposable.Empty;
            });
        }

5) Observable.Throw
برای ایجاد یک توالی که صرفا کار اطلاع رسانی OnError را توسط استثنای معرفی شده به آن انجام می‌دهد.
 var throwObservable = Observable.Throw<string>(new Exception());
در ادامه نحوه‌ی پیاده سازی این متد را توسط Observable.Create مشاهده می‌کنید:
        public static IObservable<T> Throws<T>(Exception exception)
        {
            return Observable.Create<T>(o =>
            {
                o.OnError(exception);
                return Disposable.Empty;
            });
        }

6) توسط Observable.Range
به سادگی می‌توان بازه‌ی Observable ایی را ایجاد کرد:
 var range = Observable.Range(10, 15);
range.Subscribe(Console.WriteLine, () => Console.WriteLine("Completed"));

7) Observable.Generate
اگر بخواهیم عملیات Observable.Range را پیاده سازی کنیم، می‌توان از متد Observable.Generate استفاده کرد:
        public static IObservable<int> Range(int start, int count)
        {
            var max = start + count;
            return Observable.Generate(
                initialState: start,
                condition: value => value < max,
                iterate: value => value + 1,
                resultSelector: value => value);
        }
توسط پارامتر initialState، مقدار آغازین را دریافت می‌کند. پارامتر condition، مشخص می‌کند که توالی چه زمانی باید خاتمه یابد. در پارامتر iterate، مقدار جاری دریافت شده و مقدار بعدی تولید می‌شود. resultSelector کار تبدیل و بازگشت مقدار خروجی را به عهده دارد.

8) Observable.Interval
عموما از انواع و اقسام تایمرهای موجود در دات نت مانند System.Timers.Timer ، System.Threading.Timer و System.Windows.Threading.DispatcherTimer برای ایجاد یک توالی از رخ‌دادها استفاده می‌شود. تمام این‌ها را به سادگی می‌توان توسط متد Observable.Interval‌، که قابل انتقال به تمام پلتفرم‌هایی است که Rx برای آن‌ها تهیه شده‌است، جایگزین کرد:
 var interval = Observable.Interval(period: TimeSpan.FromMilliseconds(250));
interval.Subscribe(Console.WriteLine, () => Console.WriteLine("completed"));
در اینجا تایمر تهیه شده، هر 450 میلی‌ثانیه یکبار اجرا می‌شود. برای خاتمه‌ی آن باید شیء interval را Dispose کنید.
Overload دوم این متد، امکان معرفی scheduler و اجرای بر روی تردی دیگر را نیز میسر می‌کند.

9) Observable.Timer
تفاوت Observable.Timer با Observable.Interval در مفهوم پارامتر ارسالی به آن‌ها است:
 var timer = Observable.Timer(dueTime: TimeSpan.FromSeconds(1));
 timer.Subscribe(Console.WriteLine, () => Console.WriteLine("completed"));
یکی due time دارد (مدت زمان صبر کردن تا تولید اولین خروجی) و دیگری period (به صورت متوالی تکرار می‌شود).  
خروجی Observable.Interval مثال زده شده به نحو زیر است و خاتمه‌‌ای ندارد:
0
1
2
3
4
5

اما خروجی Observable.Timer به نحو ذیل  بوده و پس از یک ثانیه، خاتمه می‌یابد:
0
completed

متد Observable.Timer دارای هفت overload متفاوت است که توسط آن‌ها dueTime (مدت زمان صبر کردن تا تولید اولین خروجی)، period (کار Observable.Timer را به صورت متوالی در بازه‌ی زمانی مشخص شده تکرار می‌کند) و scheduler (تعیین ترد اجرایی عملیات) قابل مقدار دهی هستند.
اگر می‌خواهید Observable.Timer بلافاصله شروع به کار کند، مقدار dueTime آن‌را مساوی TimeSpan.Zero قرار دهید. به این ترتیب یک Observable.Interval را به وجود آورده‌اید که بلافاصله شروع به کار کرده است و تا مدت زمان مشخص شده‌ای جهت اجرای اولین callback خود صبر نمی‌کند.



ب) تبدیلگرهایی که خروجی IObservable ایجاد می‌کنند

برای تبدیل مدل‌های برنامه نویسی Async قدیمی دات نت مانند APM، رخدادها و امثال آن به معادل‌های Rx، متدهای الحاقی خاصی تهیه شده‌اند.

1) تبدیل delegates به معادل Observable
متد Observable.Start، امکان تبدیل یک Func یا Action زمانبر را به یک توالی observable میسر می‌کند. در این حالت به صورت پیش فرض، پردازش عملیات بر روی یکی از تردهای ThreadPool انجام می‌شود.
        static void StartAction()
        {
            var start = Observable.Start(() =>
            {
                Console.Write("Observable.Start");
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(100);
                    Console.Write(".");
                }
            });
            start.Subscribe(
               onNext: unit => Console.WriteLine("published"),
               onCompleted: () => Console.WriteLine("completed"));
        }

        static void StartFunc()
        {
            var start = Observable.Start(() =>
            {
                Console.Write("Observable.Start");
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(100);
                    Console.Write(".");
                }
                return "value";
            });
            start.Subscribe(
               onNext: Console.WriteLine,
               onCompleted: () => Console.WriteLine("completed"));
        }
در اینجا دو مثال از بکارگیری Action و Func‌ها را توسط Observable.Start مشاهده می‌کنید.
زمانیکه از Func استفاده می‌شود، تابع یک خروجی را ارائه داده و سپس توالی خاتمه می‌یابد. اگر از Action استفاده شود، نوع Observable بازگشت داده شده از نوع Unit است که در برنامه نویسی functional معادل void است و هدف از آن مشخص سازی پایان عملیات Action می‌باشد. Unit دارای مقداری نبوده و صرفا سبب اجرای اطلاع رسانی OnNext می‌شود.
تفاوت مهم Observable.Start و Observable.Return در این است که Observable.Start مقدار تابع را به صورت تنبل (lazily) پردازش می‌کند، اما Observable.Return پردازش حریصانه‌ای (eagrly) را به همراه خواهد داشت. به این ترتیب Observable.Start بسیار شبیه به یک Task (پردازش‌های غیرهمزمان) عمل می‌کند.
در اینجا شاید این سؤال مطرح شود که استفاده از قابلیت‌های Async سی‌شارپ 5 برای اینگونه کارها مناسب است یا Rx؟ قابلیت‌های Async بیشتر به اعمال مخصوص IO bound مانند کار با شبکه، دریافت فایل از اینترنت، کار با یک بانک اطلاعاتی خارج از مرزهای سیستم، مرتبط می‌شوند؛ اما اعمال CPU bound مانند محاسبات سنگین حاصل از توالی‌های observable را به خوبی می‌توان توسط Rx مدیریت کرد.


2) تبدیل Events به معادل Observable

دات نت از روزهای اول خود به همراه یک event driven programming model بوده‌است. Rx متدهایی را برای دریافت یک رخداد و تبدیل آن به یک توالی Observable ارائه داده‌است. برای نمونه ObservableCollection زیر را درنظر بگیرید
 var items = new System.Collections.ObjectModel.ObservableCollection<string>
  {
          "Item1", "Item2", "Item3"
  };
اگر بخواهیم مانند روش‌های متداول، حذف شدن آیتم‌های آن‌را تحت نظر قرار دهیم، می‌توان نوشت:
            items.CollectionChanged += (sender, ea) =>
            {
                if (ea.Action == NotifyCollectionChangedAction.Remove)
                {
                    foreach (var oldItem in ea.OldItems.Cast<string>())
                    {
                        Console.WriteLine("Removed {0}", oldItem);
                    }
                }
            };
این نوع کدها در WPF زیاد کاربرد دارند. اکنون معادل کدهای فوق با Rx به صورت زیر هستند:
            var removals =
                Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>
                (
                    addHandler: handler => items.CollectionChanged += handler,
                    removeHandler: handler => items.CollectionChanged -= handler
                )
                .Where(e => e.EventArgs.Action == NotifyCollectionChangedAction.Remove)
                .SelectMany(c => c.EventArgs.OldItems.Cast<string>());

            var disposable = removals.Subscribe(onNext: item => Console.WriteLine("Removed {0}", item));
با استفاده از متد Observable.FromEventPattern می‌توان معادل Observable رخ‌داد CollectionChanged را تهیه کرد. پارامتر اول جنریک آن، نوع رخداد است و پارامتر اختیاری دوم آن، EventArgs این رخداد. همچنین با توجه به قسمت Where نوشته شده، در این بین مواردی را که Action مساوی حذف شدن را دارا هستند، فیلتر کرده و نهایتا لیست Observable آن‌ها بازگشت داده می‌شوند. اکنون می‌توان با استفاده از متد Subscribe، این تغییرات را دریافت کرد. برای مثال با فراخوانی
 items.Remove("Item1");
بلافاصله خروجی Removed item1 ظاهر می‌شود.


3) تبدیل Task به معادل Observable

متد ToObservable واقع در فضای نام System.Reactive.Threading.Tasks را بر روی یک Task نیز می‌توان فراخوانی کرد:
 var task = Task.Factory.StartNew(() => "Test");
var source = task.ToObservable();
source.Subscribe(Console.WriteLine, () => Console.WriteLine("completed"));
البته باید دقت داشت استفاده از Task دات نت 4.5 که بیشتر جهت پردازش‌های async اعمال I/O-bound طراحی شده‌است، بر IObservable مقدم است. صرفا اگر نیاز است این Task را با سایر observables ادغام کنید از متد ToObservable برای کار با آن استفاده نمائید.


4) تبدیل IEnumerable به معادل Observable
با این مورد تاکنون آشنا شده‌اید. فقط کافی است متد ToObservable را بر روی یک IEnumerable، جهت تهیه خروجی Observable فراخوانی کرد.


5) تبدیل APM به معادل Observable

APM یا Asynchronous programming model، همان روش کار با متدهای Async با نام‌های BeginXXX و EndXXX است که از نگارش‌های آغازین دات نت به همراه آن بوده‌اند. کار کردن با آن مشکل است و مدیریت آن به همراه پراکندگی‌های بسیاری جهت کار با callbacks آن است. برای تبدیل این نوع روش برنامه نویسی به روش Rx نیز متدهایی پیش بینی شده‌است؛ مانند Observable.FromAsyncPattern.

یک نکته
کتابخانه‌ای به نام Rxx بسیاری از این محصور کننده‌ها را تهیه کرده‌است:
http://Rxx.codeplex.com

ابتدا بسته‌ی نیوگت آن‌را نصب کنید:
 PM> Install-Package Rxx
سپس برای نمونه، برای کار با یک فایل استریم خواهیم داشت:
 using (new FileStream("file.txt", FileMode.Open)
                 .ReadToEndObservable()
                 .Subscribe(x => Console.WriteLine(x.Length)))
{
         Console.ReadKey();
}
متد ReadToEndObservable یکی از متدهای الحاقی کتابخانه‌ی Rxx است.
مطالب
معرفی Async Parallel.ForEach در دات نت 6
عموما زمانیکه می‌خواهیم تمام وظایف مدنظر، به صورت موازی اجرا شوند، آن‌ها را Task.WhenAll می‌کنیم. برای مثال 10 هزار درخواست HTTP را به صورت وظایفی، WhenAll می‌کنیم و ... در این حالت ... سرور ریموت، IP شما را خواهد بست! چون کنترلی بر روی تعداد وظیفه‌ی در حالت اجرای موازی وجود ندارد و یک چنین عملی، شبیه به یک حمله‌ی DDOS عمل می‌کند! برای مدیریت بهتر یک چنین مواردی، در دات نت 6 متدهای Parallel.ForEachAsync ارائه شده‌اند تا دیگر نیازی به استفاده از راه‌حل‌های ثالثی که عموما آنچنان بهینه هم نیستند، نباشد.
public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask> body)
public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, CancellationToken cancellationToken, Func<TSource, CancellationToken, ValueTask> body)
public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Func<TSource, CancellationToken, ValueTask> body)
public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask> body)
public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken, Func<TSource, CancellationToken, ValueTask> body)
public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, ParallelOptions parallelOptions, Func<TSource, CancellationToken, ValueTask> body)
این مجموعه متدها از ValueTaskها بجای Taskها استفاده می‌کند تا سربار ایجاد Taskها در حلقه‌ها کاهش یابد. همچنین در اینجا degree of parallelism به صورت پیش‌فرض به تعداد هسته‌های سی‌پی تنظیم شده‌است (Environment.ProcessorCount)؛ چون عموما توسعه دهنده‌ها نمی‌دانند که چه عددی را باید برای آن انتخاب کنند. هر چند امکان تنظیم دستی آن‌ها هم وجود دارد (یکی از مهم‌ترین مشکلات کار با WhenAll).

یک مثال: در اینجا می‌خواهیم به صورت موازی، مشخصات کاربرانی از Github را توسط HttpClient دریافت کنیم. هر بار هم فقط می‌خواهیم سه وظیفه اجرا شوند و نه بیشتر
using System.Net.Http.Headers;
using System.Net.Http.Json;
 
var userHandlers = new []  { "users/VahidN", "users/shanselman", "users/jaredpar", "users/davidfowl" };
 
using HttpClient client = new()
{
    BaseAddress = new Uri("https://api.github.com"),
};
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("DotNet", "6"));
 
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 3 };
 
await Parallel.ForEachAsync(userHandlers, parallelOptions, async (uri, token) =>
{
    var user = await client.GetFromJsonAsync<GitHubUser>(uri, token); 
    Console.WriteLine($"Name: {user.Name}\nBio: {user.Bio}\n");
});
 
public class GitHubUser
{
    public string Name { get; set; }
    public string  Bio { get; set; }
}
در این مثال، نمونه‌ای از کارکرد متد جدید Parallel.ForEachAsync را مشاهده می‌کنید که اینبار، MaxDegreeOfParallelism آن قابل تنظیم است. یعنی با تنظیم فوق، هربار فقط سه وظیفه به صورت موازی اجرا خواهند شد. البته تنظیم آن به منهای یک، همان حالت WhenAll را سبب خواهد شد؛ یعنی محدودیتی وجود نخواهد داشت.
متد Parallel.ForEachAsync، آرایه‌ای را که باید بر روی آن کار کند، دریافت می‌کند. سپس تنظیمات اجرای موازی آن‌ها را هم مشخص می‌کنیم. در ادامه آن‌ها را در دسته‌های مشخصی، به صورت موازی بر اساس منطقی که مشخص می‌کنیم، اجرا خواهد کرد.


وضعیت امکان اجرای موازی متدهای async همزمان، تا پیش از دات نت 6

<List<T به همراه متد الحاقی ForEach است که می‌تواند یک <Action<T را بر روی المان‌های این لیست، اجرا کند و ... عموما زمانیکه به وظایف async می‌رسیم، به اشتباه مورد استفاده قرار می‌گیرد:
customers.ForEach(c => SendEmailAsync(c));
مثال فوق، با اجرای حلقه‌ی زیر تفاوتی ندارد:
foreach(var c in customers)
{
    SendEmailAsync(c); // the return task is ignored
}
یعنی یک عملیات async، بدون await فراخوانی شده‌است و تا پایان عملیات مدنظر، صبر نخواهد شد. حداقل مشکل آن این است که اگر در این بین استثنایی رخ دهد، هیچگاه متوجه آن نخواهید شد و حتی می‌تواند کل پروسه‌ی برنامه را خاتمه دهد. شاید عنوان کنید که می‌شود این مشکل را به صورت زیر حل کرد:
customers.ForEach(async c => await SendEmailAsync(c));
اما ... این روش هم تفاوتی با قبل ندارد. از این لحاظ که متد ForEach یک <Action<T را دریافت می‌کند که خروجی آن void است. یعنی در نهایت با راه حل دوم، فقط یک async void ایجاد می‌شود که باز هم قابلیت صبر کردن تا پایان عملیات را ندارد. نکته‌ی مهم اینجا است که اجرای موازی آن‌ها توسط متد Parallel.ForEach نیز دقیقا همین مشکل را دارد.
تنها راه حل پذیرفته‌ی شده‌ی چنین عمل async ای، فراخوانی آن‌ها به صورت متداول زیر و بدون استفاده از متد ForEach است:
foreach(var c in customers)
{
   await SendEmailAsync(c);
}
و یا Task.WhenAll کردن آن‌ها، با علم به این موضوع که MaxDegreeOfParallelism آن قابل کنترل نیست (حداقل به صورت استاندارد و بدون نیاز به کتابخانه‌های جانبی). برای مثال بجای نوشتن:
foreach(var o in orders)
{
    await ProcessOrderAsync(o);
}
می‌توان آن‌را به صورت زیر درآورد:
var tasks = orders.Select(o => ProcessOrderAsync(o)).ToList();
await Task.WhenAll(tasks);
در این حالت عملیات ProcessOrderAsync را تبدیل به لیستی از وظایف مدنظر کرده و به متد Task.WhenAll ارسال می‌کنیم تا به صورت موازی اجرا شوند. اما ... اگر 10 هزار Task وجود داشته باشند، کنترلی بر روی تعداد وظایف در حال اجرای موازی وجود نخواهد داشت و این مورد نه تنها سبب بالا رفتن کارآیی نخواهد شد، بلکه می‌تواند سرور را هم با اخلال پردازشی، به علت کمبود منابع در دسترس مواجه کند.

دات نت 6، هم کنترل MaxDegreeOfParallelism را میسر کرده‌است و هم اینکه اینبار نگارش async واقعی Parallel.ForEachAsync را ارائه داده‌است تا دیگر همانند حالت قبلی Parallel.ForEach، به async void‌ها و مشکلات مرتبط با آن‌ها نرسیم.
مطالب
پیشنهاد یک دیکشنری کم دردسرتر!
نگارش ابتدایی «iTextSharp.LGPLv2.Core » بر اساس کدهای اولیه‌ی iTextSharp بود که مستقیما از جاوا به سی‌شارپ ترجمه شده بود. این کدها پر بودند از ساختارهای داده‌ای مانند Hashtable و ArrayList که مرتبط هستند با روزهای آغازین ارائه‌ی دات نت 1؛ پیش از ارائه‌ی Generics. برای مثال نوع Hashtable، همانند ساختار داده‌ی Dictionary عمل می‌کند، اما جنریک نیست؛ یعنی شبیه به <Dictionary<object, object عمل می‌کند و برای کار با آن، باید مدام از تبدیل نوع‌های داده‌ها (یا همان boxing) از نوع object‌، به نوع داده‌ی مدنظر، استفاده کرد که این تبدیل نوع‌ها، همیشه به همراه کاهش کارآیی هم هستند. به علاوه در حین کار با Hashtable، اگر کلیدی در مجموعه‌ی آن وجود نداشته باشد، فقط نال را بازگشت می‌دهد، اما Dictionary، یک استثنای یافت نشدن کلید را صادر می‌کند. بنابراین فرض کنید که با هزاران سطر کد استفاده کننده‌ی از Hashtable طرف هستید که اگر آن‌ها را تبدیل به Dictionary‌های جنریک متناسبی کنید تا کارآیی برنامه بهبود یابد، تمام موارد استفاده‌ی از آن‌ها‌را نیز باید به همراه TryGetValue‌ها کنید تا از شر استثنای یافت نشدن کلید درخواستی، در امان باشید. در این مطلب روش مواجه شدن با یک چنین حالتی را با حداقل تغییر در کدها بررسی خواهیم کرد.


ممنوع کردن استفاده‌ی از ساختارهای داده‌ی غیرجنریک

قدم اول مواجه شدن با یک چنین کدهای قدیمی، ممنوع کردن استفاده‌ی از ساختارهای داده‌ی غیرجنریک و الزام به تبدیل آن‌ها به نوع‌های جدید است. برای این منظور می‌توان از Microsoft.CodeAnalysis.BannedApiAnalyzers استفاده کرد که توضیحات بیشتر آن‌را در مطلب «غنی سازی کامپایلر C# 9.0 با افزونه‌ها» پیشتر بررسی کرده‌ایم. به صورت خلاصه، ابتدا بسته‌ی نیوگت آن‌را به صورت یک آنالایزر جدید به فایل csproj. برنامه معرفی می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>
    <ItemGroup>
        <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt"/>
    </ItemGroup>
</Project>
همچنین در اینجا نیاز است یک فایل متنی BannedSymbols.txt را نیز به آن معرفی کرد؛ برای مثال با این محتوا:
# https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md
T:System.Collections.ICollection;Don't use a non-generic data structure.
T:System.Collections.Hashtable;Don't use a non-generic data structure.
T:System.Collections.ArrayList;Don't use a non-generic data structure.
T:System.Collections.SortedList;Don't use a non-generic data structure.
T:System.Collections.Stack;Don't use a non-generic data structure.
T:System.Collections.Queue;Don't use a non-generic data structure.
این تنظیمات سبب خواهند شد تا اگر در کدهای ما، ساختارهای داده‌ی غیرجنریکی در حال استفاده بودند، با یک اخطار ظاهر شوند و جهت سخت‌گیری بیشتر، روش تبدیل اخطارها به خطاها را نیز در مطلب «غنی سازی کامپایلر C# 9.0 با افزونه‌ها» بررسی کرده‌ایم تا مجبور به اصلاح آن‌ها شویم.


پیشنهاد یک دیکشنری کم دردسرتر!

برای نمونه پس از تنظیمات فوق، مجبور به تغییر تمام hash tableها به دیکشنری‌های جدید جنریک خواهیم شد؛ اما ... اگر اینکار را انجام دهیم، برنامه‌ای که تا پیش از این بدون مشکل کار می‌کرد، اکنون با استثناهای متعدد یافت نشدن کلیدها، خاتمه پیدا می‌کند! چون دیگر دیکشنری‌های جدید، همانند hash tableهای قدیمی، در صورت عدم وجود کلیدی، نال را بازگشت نمی‌دهند.
برای رفع این مشکل و اصلاح انبوهی از کدها با حداقل تغییرات و عدم تکرار TryGetValueها در همه‌جا، می‌توان دسترسی به ایندکس‌های یک دیکشنری استاندارد دات نت را به صورت زیر با ارث‌بری از آن، بازنویسی کرد:
/// <summary>
///     This custom IDictionary doesn't throw a KeyNotFoundException while accessing its value by a given key
/// </summary>
public interface INullValueDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    new TValue this[TKey key] { get; set; }
}

/// <summary>
///     This custom IDictionary doesn't throw a KeyNotFoundException while accessing its value by a given key
/// </summary>
public class NullValueDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INullValueDictionary<TKey, TValue>
{
    public new TValue this[TKey key]
    {
        get => TryGetValue(key, out var val) ? val : default;
        set => base[key] = value;
    }
}
همانطور که مشاهده می‌کنید، اگر بجای Dictionary، از NullValueDictionary پیشنهادی استفاده کنیم، دیگر نیازی نیست تا هزاران TryGetValue را در سراسر کدهای برنامه، تکرار و پراکنده کنیم و با حداقل تغییرات می‌توان معادل بهتری را بجای Hashtable قدیمی داشت.
مطالب
تنظیمات امنیتی Glimpse
در مورد glimpse پیشتر مطالبی در سایت منتشر شده است :
 آشنایی و بررسی ابزار Glimpse 
بعد از آپلود سایت ما می‌توانیم دسترسی به تنظیمات خاص glimpse را تنها به کاربران عضو محدود کنیم:
<location path="Glimpse.axd" >
    <system.web>
        <authorization>
            <allow users="Administrator" />
            <deny users="*" />
        </authorization>
    </system.web>
</location>

یا می‌توانیم آنرا غیرفعال کنیم :
<glimpse defaultRuntimePolicy="Off" xdt:Transform="SetAttributes">
</glimpse>

همچنین می‌توانیم با پیاده سازی اینترفیس IRuntimePolicy سیاست‌های مختلف نمایش تب‌های glimpse را تعیین کنیم :
using Glimpse.AspNet.Extensions;
using Glimpse.Core.Extensibility;

namespace Test
{
    public class GlimpseSecurityPolicy:IRuntimePolicy
    {
        public RuntimePolicy Execute(IRuntimePolicyContext policyContext)
        {
            // You can perform a check like the one below to control Glimpse's permissions within your application.
// More information about RuntimePolicies can be found at http://getglimpse.com/Help/Custom-Runtime-Policy
var httpContext = policyContext.GetHttpContext();
            if (!httpContext.User.IsInRole("Administrator "))
            {
                return RuntimePolicy.Off;
            }

            return RuntimePolicy.On;
        }

        public RuntimeEvent ExecuteOn
        {
            get { return RuntimeEvent.EndRequest; }
        }
    }
}

زمانیکه glimpse را از طریق Nuget نصب می‌کنید کلاس فوق به صورت اتوماتیک به پروژه اضافه می‌شود با این تفاوت که به صورت کامنت شده است تنها کاری شما باید انجام بدید کدهای فوق را از حالت کامنت خارج کنید و Role مربوطه را جایگزین کنید. 

نکته : کلاس فوق نیاز به رجیستر شدن ندارد و تشخیص آن توسط Glimpse به صورت خودکار انجام می‌شود.  
مطالب
آشنایی با Refactoring - قسمت 9

این قسمت از آشنایی با Refactoring به کاهش cyclomatic complexity اختصاص دارد و خلاصه آن این است: «استفاده از if های تو در تو بیش از سه سطح، مذموم است» به این علت که پیچیدگی کدهای نوشته شده را بالا برده و نگهداری آن‌ها را مشکل می‌کند. برای مثال به شبه کد زیر دقت کنید:

if
if
if
if
do something
endif
endif
endif
endif


که حاصل آن شبیه به نوک یک پیکان (Arrow head) شده است. یک مثال بهتر:


namespace Refactoring.Day9.RemoveArrowhead.Before
{
public class Role
{
public string RoleName { set; get; }
public string UserName { set; get; }
}
}

using System;
using System.Collections.Generic;
using System.Linq;

namespace Refactoring.Day9.RemoveArrowhead.Before
{
public class RoleRepository
{
private IList<Role> _rolesList = new List<Role>();

public IEnumerable<Role> Roles { get { return _rolesList; } }

public void AddRole(string username, string roleName)
{
if (!string.IsNullOrWhiteSpace(roleName))
{
if (!string.IsNullOrWhiteSpace(username))
{
if (!IsInRole(username, roleName))
{
_rolesList.Add(new Role
{
UserName=username,
RoleName=roleName
});
}
else
{
throw new InvalidOperationException("User is already in this role.");
}
}
else
{
throw new ArgumentNullException("username");
}
}
else
{
throw new ArgumentNullException("roleName");
}
}

public bool IsInRole(string username, string roleName)
{
return _rolesList.Any(x => x.RoleName == roleName && x.UserName == username);
}
}
}

متد AddRole فوق، نمونه‌ی بارز پیچیدگی بیش از حد حاصل از اعمال if های تو در تو است و ... بسیار متداول. برای حذف این نوک پیکان حاصل از if های تو در تو، از بالاترین سطح شروع کرده و شرط‌ها را برعکس می‌کنیم؛ با این هدف که هر چه سریعتر متد را ترک کرده و خاتمه دهیم:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Refactoring.Day9.RemoveArrowhead.After
{
public class RoleRepository
{
private IList<Role> _rolesList = new List<Role>();

public IEnumerable<Role> Roles { get { return _rolesList; } }

public void AddRole(string username, string roleName)
{
if (string.IsNullOrWhiteSpace(roleName))
throw new ArgumentNullException("roleName");

if (string.IsNullOrWhiteSpace(username))
throw new ArgumentNullException("username");

if (IsInRole(username, roleName))
throw new InvalidOperationException("User is already in this role.");

_rolesList.Add(new Role
{
UserName = username,
RoleName = roleName
});
}

public bool IsInRole(string username, string roleName)
{
return _rolesList.Any(x => x.RoleName == roleName && x.UserName == username);
}
}
}

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


پاسخ به بازخورد‌های پروژه‌ها
استفاده از اشیاء پیچیده در حالت StronglyTypedList
هر دوی این حالت‌ها کار خواهند کرد:
using PdfRpt.Core.Helper;

var data = list.GetSafeStringValueOf("Product.Category.Name");
or
var data = list.GetSafeStringValueOf<Entity>(x=>x.Product.Category.Name);
مطالب
تولید اعداد تصادفی (Random Numbers) در #C
کلاس Random در NET.  ابزارهایی را فراهم می‌کند که بتوانیم توسط آن‌ها اعداد تصادفی تولید کنیم. سازنده این کلاس دو Overload مختلف دارد. یکی از Overload‌ها بدون پارامتر است و دیگری مقداری را بعنوان Seed دریافت می‌کند. کلاس Random سه متد عمومی دارد:
 •  Next : یک عدد تصادفی را برای ما تولید می‌کند.
 • NextByte : آرایه‌ای از بایت‌ها را که با اعداد تصادفی پر شده‌اند تولید می‌کند.
 • NextDouble : یک عدد تصادفی را بین 0.0 و  1.0 باز می‌گرداند.

بررسی متد Next

متد Next سه Overload مختلف دارد و این امکان را برای شما مهیا می‌کند تا 2 عدد را به عنوان بازه تولید اعداد تصادفی انتخاب کنید (حد پایین و بالای بازه).
تولید یک عدد تصادفی:
 var rand = new Random().Next();
تولید یک عدد تصادفی کوچکتر از 1000:
 var rand = new Random().Next(1000);
در واقع پارامتر ارسالی حد بالای بازه می‌باشد و حد پایین بصورت پیش فرض عدد 0 در نظر گرفته می‌شود.
کد زیر عددی تصادفی را بین محدوده تعیین شده تولید می‌کند:
public static int RandomNumber(int min, int max)
{
     var rand = new Random();
     return rand.Next(min, max);
}

بررسی متد NextDouble

قطعه کد زیر (به کمک توابع کلاس Random) رشته‌ای تصادفی را با طول مشخصی برای ما تولید می‌کند؛ با قابلیت تعیین بزرگ و یا کوچک بودن کاراکتر‌های رشته تصادفی:
        public static string RandomString(int size, bool lowerCase)
        {
            StringBuilder builder = new StringBuilder();
            Random random = new Random();
            char ch;
            for (int i = 0; i < size; i++)
            {
                //تولید عدد و تبدیل آن به کاراکتر 
                ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
                builder.Append(ch);
            }
            if (lowerCase)
                return builder.ToString().ToLower();
            return builder.ToString();
        }
کد زیر پسوردی را به طول 10، که 4 حرف اول آن حروف کوچک و 4 حرف دوم آن عدد و 2 حرف آخر آن حروف بزرگ باشند، تولید می‌کند:
public string RandomPassword()
{
   StringBuilder builder = new StringBuilder();
   builder.Append(RandomString(4, true));
   builder.Append(RandomNumber(1000, 9999));
   builder.Append(RandomString(2, false));
   return builder.ToString();
}

بررسی متد NextByte
 همانطور که در ابتدای مطلب اشاره شد، خروجی این تابع آرایه‌ای از بایت‌ها می‌باشد و هر خانه‌ی آن عددی است تصادفی که بزرگتر و یا مساوی 0  و کوچکتر از مقدار Maximum نوع داده Byte است. کد زیر نحوه استفاده از این تابع را نشان می‌دهد:
byte[] b = new byte[10];
Random rnd = new Random();
rnd.NextBytes(b);
for (int i = 0; i < b.Length; i++)
{
   Console.WriteLine(b[i]);
}
خروجی مثال فوق :
153
115
86
5
161
190
249
228
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
جهت جلوگیری از Encoding  مدل ارسالی به ویو در View page source 

کانفیگ زیر را به کلاس استارتاپ اضافه کنید
فضا‌های نامی : 
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Microsoft.Extensions.WebEncoders;

services.Configure<WebEncoderOptions> (options => {
    options.TextEncoderSettings = new TextEncoderSettings (UnicodeRanges.All);
});

مطالب
افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET
مرورگرهای جدید تحت زیر مجموعه‌ای به نام Content Security Policy، قابلیت‌های توکاری را اضافه کرده‌اند تا حملاتی مانند XSS را حتی در برنامه‌ی وبی که برای این نوع حملات تمهیداتی را درنظر نگرفته‌است، خنثی کنند. این قابلیت‌ها به صورت پیش فرض فعال نبوده و نیاز است برنامه نویس صراحتا درخواست فعال شدن آن‌ها را از طریق افزودن تعدادی هدر مشخص به Response، ارائه دهد. در ادامه این هدرها را بررسی خواهیم کرد.


غیرفعال کردن اجرای اسکریپت‌های inline

عمده‌ی حملات XSS زمانی قابلیت اجرا پیدا می‌کنند که مهاجم بتواند به طریقی (ورودی‌های اعتبارسنجی نشده)، اسکریپتی را به درون صفحه‌ی جاری تزریق کند. بنابراین اگر ما به مرورگر اعلام کنیم که دیگر اسکریپت‌های inline را پردازش نکن، سایت را تا حد زیادی در مقابل حملات XSS مقاوم کرده‌ایم. این قابلیت به صورت پیش فرض خاموش است؛ چون به طور قطع فعال سازی آن بسیاری از سایت‌هایی را که عادت کرده‌اند اسکریپت‌های خود را داخل صفحات وب مدفون کنند، از کار می‌اندازد. این نوع سایت‌ها باید به روز شده و اسکریپت‌ها را از طریق فایل‌های خارجی js، به سایت و صفحات خود الحاق کنند.
برای فعال سازی این قابلیت، فقط کافی است هدرهای زیر به Response اضافه شوند:
 Content-Security-Policy: script-src 'self'
X-WebKit-CSP: script-src 'self'
X-Content-Security-Policy: script-src 'self'
سطر اول به زودی تبدیل به یک استاندارد W3 خواهد شد؛ اما فعلا فقط توسط کروم 25 به بعد پشتیبانی می‌شود. سطر دوم توسط مرورگرهایی که از موتور WebKit استفاده می‌کنند، پشتیبانی می‌شود و سطر سوم مخصوص فایرفاکس است و IE 10 به بعد.
بعد از فعال شدن این قابلیت، فقط اسکریپت‌هایی که از طریق دومین شما به صفحه الحاق شده‌اند، قابلیت اجرا را خواهند یافت و کلیه اسکریپت‌های مدفون شده داخل صفحات، دیگر اجرا نخواهد شد. در این حالت اگر از CDN برای الحاق اسکریپتی استفاده می‌کنید، مثلا مانند الحاق jQuery به صفحه، نیاز است مسیر آن‌را صراحتا در این هدر ذکر کنید:
 Content-Security-Policy: script-src 'self' https://youcdn.com
X-WebKit-CSP: script-src 'self' https://yourcdn.com
X-Content-Security-Policy: script-src 'self' https://yourcdn.com
علاوه بر آن حتی می‌شود پردازش تمام منابع مورد استفاده را نیز مانند تصاویر، شیوه‌نامه‌ها، فایل‌های فلش و غیره، به دومین جاری محدود کرد:
 Content-Security-Policy: default-src 'self' https://youcdn.com
X-WebKit-CSP: default-src 'self' https://yourcdn.com
X-Content-Security-Policy: default-src 'self' https://yourcdn.com
بدیهی است پس از آشنایی با این مورد، احتمالا در پروژه‌های جدید خود از آن استفاده کنید (چون inline scriptهای فعلی شما را کاملا از کار می‌اندازد).


نحوه‌ی اضافه کردن هدرهای Content Security Policy به برنامه‌های ASP.NET

روشی که با هر دو برنامه‌های وب فرم و MVC کار می‌کند، تهیه یک HTTP module است؛ به شرح ذیل:
using System;
using System.Web;

namespace AntiXssHeaders
{
    public class SecurityHeadersConstants
    {
        public static readonly string XXssProtectionHeader = "X-XSS-Protection";
        public static readonly string XFrameOptionsHeader = "X-Frame-Options";
        public static readonly string XWebKitCspHeader = "X-WebKit-CSP";
        public static readonly string XContentSecurityPolicyHeader = "X-Content-Security-Policy";
        public static readonly string ContentSecurityPolicyHeader = "Content-Security-Policy";
        public static readonly string XContentTypeOptionsHeader = "X-Content-Type-Options";
    }

    public class ContentSecurityPolicyModule : IHttpModule
    {
        public void Dispose()
        { }

        public void Init(HttpApplication app)
        {
            app.BeginRequest += AppBeginRequest;
        }

        void AppBeginRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            var response = app.Context.Response;
            setHeaders(response);
        }

        private static void setHeaders(HttpResponse response)
        {
            response.Headers.Set(SecurityHeadersConstants.XFrameOptionsHeader, "SameOrigin");

            // For IE 8+
            response.Headers.Set(SecurityHeadersConstants.XXssProtectionHeader, "1; mode=block");
            response.Headers.Set(SecurityHeadersConstants.XContentTypeOptionsHeader, "nosniff");

            //todo: Add /Home/Report --> public JsonResult Report() { return Json(true); }

            const string cspValue = "default-src 'self';";
            // For Chrome 16+
            response.Headers.Set(SecurityHeadersConstants.XWebKitCspHeader, cspValue);

            // For Firefox 4+
            response.Headers.Set(SecurityHeadersConstants.XContentSecurityPolicyHeader, cspValue);
            response.Headers.Set(SecurityHeadersConstants.ContentSecurityPolicyHeader, cspValue);
        }
    }
}
و یا در برنامه‌های ASP.NET MVC می‌توان یک فیلتر جدید را تعریف کرد و سپس آن‌را به صورت عمومی معرفی نمود:
//// RegisterGlobalFilters -> filters.Add(new ContentSecurityPolicyFilterAttribute());
public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var response = filterContext.HttpContext.Response;
        response.AddHeader("Content-Security-Policy", "script-src 'self'");
        // the rest ...
        base.OnActionExecuting(filterContext);
    }
}
در ماژول تهیه شده چند مورد دیگر را نیز مشاهده می‌کنید:
الف) X-XSS-Protection مربوط است به IE 8 به بعد
ب)  تنظیم هدر X-Frame-Options به SameOrigin سبب می‌شود تا صفحات سایت شما دیگر توسط Iframeها در سایت‌های دیگر قابل نمایش نباشد و فقط در سایت جاری بتوان صفحه‌ای را از همان دومین در صورت نیاز توسط Iframeها نمایش داد.
ج) تنظیم X-Content-Type-Options به nosniff سبب می‌شود تا IE سعی نکند با اجرای یک محتوا سعی در تشخیص mime-type آن کند و به این ترتیب امنیت دسترسی و مشاهده اشیاء قرار گرفته در صفحه (و یا تزریق شده توسط مهاجمین) به شدت بالا خواهد رفت.


برای مطالعه بیشتر
Security through HTTP response headers


پروژه‌ی کاملی مخصوص افزودن هدرهای یاد شده
https://nwebsec.codeplex.com/


یک نکته تکمیلی
توصیه شده‌است تا دیگر از روال رویدادگردان PreSendRequestHeaders برای ارسال هدرها استفاده نکنید؛ چون با پردازش‌های غیرهمزمان تداخل ایجاد می‌کند.

 
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت نهم - مسیریابی
نکات ارتقاء به نگارش RTM
همان «نکات ارتقاء به نگارش RC5 » در اینجا هم برقرار هستند. فقط نام فایل app.routes.ts به app.routing.ts تغییر یافته‌است.
تغییرات پروژه را در اینجا می‌توانید دنبال کنید.

چند مطلب تکمیلی
Using the New Release of Angular 2’s Router 3.0.0 
Angular 2 Routing With Modules