مطالب
معرفی 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‌ها و مشکلات مرتبط با آن‌ها نرسیم.
مطالب
کلاس‌ها در ES 6
رسمی‌ترین زبان‌های شیء گرا از کلاس‌ها و وراثت مربوط به آنها پشتیبانی می‌کنند؛ ولی از زمانی که JavaScript ساخته شد، به دلیل نداشتن کلاس‌ها باعث سردرگمی بیشتر توسعه دهنده‌ها شد. برای آشنایی با مباحث شیء گرایی در جاوااسکریپت  ^ و را مطالعه کنید.
در واقع کلاس‌ها در ES 6 هم واقعا مانند کلاس‌ها در سایر زبان‌ها نبوده و صرفا یک syntax آسان بر فراز روش‌های پیاده سازی انواع داده‌های شخصی در ورژن‌ها قبلی می‌باشند. این syntax به معنای تولید مدل جدید شیء گرایی در JavaScript نمی‌باشد و در ادامه خواهیم دید که این کلاس‌ها چیزی بجز یک function نیستند. در ورژن‌های قبل ES، تعریف نوع داده جدید به عنوان مثال به شکل زیر بود:
function PersonType(name) {
    this.name = name;
}

PersonType.prototype.sayName = function() {
    console.log(this.name);
};

let person = new PersonType("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonType);  // true
console.log(person instanceof Object);      // true
‫در کد بالا که مربوط است به ورژن 5 اکما اسکریپت، PersonType یک تابع سازنده است که دارای یک پراپرتی به نام name و یک متد در سطح آبجکت به نام sayName میباشد.
Class declarations یکی از روش‌های تعریف کلاس در ES 6 میباشد. به عنوان مثال در ورژن جدید، تعریف کلاس مثال فوق به شکل زیر خواهد بود:
class PersonClass {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
}

let person = new PersonClass("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"
در کد بالا این بار به جای تعریف یک  تابع (function) به عنوان سازنده، برای ساخت نوع داده‌ی شخصی، خواهید توانست به صورت مستقیم این سازنده را درون کلاس خود با نام constructor که مشخصا برای این منظور در نظر گرفته شده است، تعریف کنید. همانطور که در خطوط آخر کد بالا مشخص است، کلاس PersonClass چیزی بجز یک function نیست و همین مورد گفته‌های ابتدایی مطلب را تأیید می‌کند.  باید توجه داشت که در تعریف هر کلاسی فقط یک تابع سازنده با نام constructor می‌تواند وجود داشته باشد؛ در غیر این صورت خطای syntax error را دریافت خواهیم کرد.
شباهت‌هایی و معادل‌هایی که در پیاده سازی مثال بالا در دو ورژن مختلف وجود دارد باعث خواهد شد که بدون نگرانی از اینکه با کدام ورژن کار می‌کنید، به صورت ترکیبی از آنها استفاده کنید.
Class Expressions روش دوم پیاده سازی کلاس‌ها در ES 6 می‌باشد؛ به دو صورت named و unnamed که به صورتیکه در زیر مشاهده می‌کنید، قابل تعریف خواهد بود:
//unnamed class expressions do not require identifiers after "class"
let PersonClass = class {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

let person = new PersonClass("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"


//named
let PersonClass = class PersonClass2 {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

console.log(PersonClass === PersonClass2);  // true
همانطور که متوجه شدید، class‌ها به همانند function‌ها به دو شکل declarations و expressions قابل تعریف هستند (یکی دیگر از شباهت ها). یک نکته در حالت تعریف به صورت named این است که میتوان PerssonClass2 و PerssonClass را به دلیل اینکه هر دوی آنها اشاره‌گر به یک کلاس هستند، به جای هم استفاده کنید.
نکته جالب این که class expressions‌ها را می‌توان به عنوان آرگومان توابع دیگر هم ارسال کرد؛ برای مثال :
function createObject(classDef) {
    return new classDef();
}

let obj = createObject(class {
    sayHi() {
        console.log("Hi!");
    }
});

obj.sayHi();        // "Hi!"
در کد بالا createObject، متدی است که class expression ما به عنوان آرگومان آن پاس داده شده است و در نهایت توانسته‌ایم از این کلاس پاس داده شده در داخل متد نمونه سازی کرده و آن را به عنوان نتیجه‌ی برگشتی return کنیم. 
نکته جالب دیگر این که با استفاده از class expressions‌ها خواهیم توانست singleton‌ها را با فراخوانی بلافاصله‌ی سازنده کلاس، پیاده سازی کنیم. برای این منظور باید کلمه‌ی کلیدی new را قبل از کلمه‌ی کلیدی class نوشته و در پایان هم از دو پرانتز باز و بسته استفاده کنید که معادل فراخوانی سازنده‌ی کلاس خواهد بود.
let person = new class {
    constructor(name) {
        this.name = name;
    }

    sayName() {
        console.log(this.name);
    }
}("Nicholas");

person.sayName();       // "Nicholas  

در کد بالا ، "Nicholas" به عنوان آرگومان سازنده کلاس بی نام در هنگام ساخت نمونه از طریق پرانتز‌های باز و بسته انتهایی، پاس داده شده است. استفاده از class declarations یا class expressions برای کار با کلاس‌ها به سبک کاری شما مربوط خواهد شد و بس. ولی نکته این است که هر دو شکل پیاده سازی کلاس‌ها بر خلاف function declarations و function expressions ، قابلیت  hoisting  را نخواهند داشت و به صورت پیش فرض در حالت strict mode اجرا خواهند شد.

Accessor Properties

کلاس‌ها این امکان را دارند تا بتوان برای پراپرتی‌هایی که در سازنده‌ی کلاس تعریف شده‌اند، accessor property تعریف کرد. سینتکس استفاده شده‌ی برای این منظور، شبیه به ساخت object literal accessor‌ها در ES 5 میباشد.برای مثال:

class CustomHTMLElement {

    constructor(element) {
        this.element = element;
    }

    get html() {
        return this.element.innerHTML;
    }

    set html(value) {
        this.element.innerHTML = value;
    }
}

var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype,\
 "html");

console.log("get" in descriptor);   // true
console.log("set" in descriptor);   // true

در کد بالا ، getter و setter برای محتوای html مربوط به پراپرتی element در نظر گرفته شده است که در واقعا نمایندگان (delegates) مربوط به متد innterHTML خود element می‌باشند. معادل همین پیاده سازی بدون استفاده از سینتکس کلاس، به شکل زیر خواهد بود:

// direct equivalent to previous example
let CustomHTMLElement = (function() {
    "use strict";

    const CustomHTMLElement = function(element) {
        // make sure the function was called with new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.element = element;
    }

    Object.defineProperty(CustomHTMLElement.prototype, "html", {
        enumerable: false,
        configurable: true,
        get: function() {
            return this.element.innerHTML;
        },
        set: function(value) {
            this.element.innerHTML = value;
        }
    });
    return CustomHTMLElement;
}());

حتما متوجه شدید که با استفاده از سینتکس کلاس برای تعریف accessor property‌ها حجم کد نویسی شما خیلی کاهش خواهد یافت و این تنها تفاوت بین دو شکل پیاده سازی فوق میباشد.

Static Members

ساخت اعضای استاتیک در ورژن قبل برای مثال به شکل زیر بود:

function PersonType(name) {
    this.name = name;
}

// static method
PersonType.create = function(name) {
    return new PersonType(name);
};

// instance method
PersonType.prototype.sayName = function() {
    console.log(this.name);
};

var person = PersonType.create("Nicholas");

 در کد بالا یک متد استاتیک برای نوع داده شخصی PersonType در نظر گرفته شده است. این مورد در ES 6 بهبود یافته و فقط با قرار دادن کلمه‌ی کلیدی static قبل از نام متد و یا accessor property می‌توان به نتیجه‌ی مثال بالا دست یافت:

class PersonClass {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }

    // equivalent of PersonType.create
    static create(name) {
        return new PersonClass(name);
    }
}

let person = PersonClass.create("Nicholas");

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


Inheritance

مشکل دیگری که در ES 5 برای پیاده سازی انواع داده شخصی وجود داشت، حجم بالای کد و مراحلی بود که برای پیاده سازی وراثت می‌بایستی متحمل می‌شدیم. برای مثال در ورژن قبلی باید به شکل زیر عمل میکردیم:

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

function Square(length) {
    Rectangle.call(this, length, length);
}

Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value:Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});

var square = new Square(3);
console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

درکد بالا Square از Rectangle ارث بری کرده که برای این منظور Square.prototype را با ساخت نمونه‌ای از Rectangle.prototype بازنویسی کرده‌ایم. این سینتکس باعث سردرگمی اغلب تازه کاران خواهد شد. برای این منظور در ES 6 خیلی راحت با استفاده از کلمه‌ی کلیدی  extends بعد از نام کلاس و سپس نوشتن نام کلاس پایه خواهیم توانست به نتیجه‌ی بالا دست یابیم. به عنوان مثال:

class Rectangle {

    constructor(length, width) {
        this.length = length;
        this.width = width;
    }

    getArea() {
        return this.length * this.width;
    }
}

class Square extends Rectangle {
    constructor(length) {
        // same as Rectangle.call(this, length, length)
        super(length, length);
    }
}

var square = new Square(3);
console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

در کد بالا نیز کلاس Square از کلاس Rectangle ارث بری کرده و همانطور که مشخص است و انتظار داشتیم، متد getArea در یکی از اعضای به ارث برده شده از کلاس پایه، قابل دسترسی می‌باشد. در سازنده‌ی کلاس Square با استفاده از ()super توانسته‌ایم سازنده‌ی کلاس Rectangle را با آرگومان‌های مشخصی فراخوانی کنیم. 

اگر برای subclass، سازنده در نظر گرفته شود، باید سازنده‌ی کلاس پیاده سازی کننده حتما فراخوانی شود. در غیر این صورت با خطا روبرو خواهید شد. ولی در مقابل اگر هیچ سازنده‌ای برای subclass در نظر نگرفته باشید، به صورت خودکار سازنده‌ی کلاس پایه هنگام ساخت نمونه از این subclass فراخوانی خواهد شد:

class Square extends Rectangle {
    // no constructor
}

// Is equivalent to
class Square extends Rectangle {
    constructor(...args) {
        super(...args);
    }
}

همانطور که در کد بالا مشخص است اگر سازنده‌ای برای subclass در نظر گرفته نشود، تمام آرگومان‌های ارسالی، هنگام نمونه سازی از آن، به ترتیب به سازنده‌ی کلاس پایه نیز پاس داده خواهند شد.

 چند نکته

- فقط زمانی میتوان ()super را فراخوانی کرد که از بعد از نام کلاس از کلمه‌ی کلیدی extends استفاده شده باشد.
- باید قبل از دسترسی به کلمه‌ی کلیدی this در سازنده subclass، سازنده‌ی کلاس پایه را با استفاده از ()super فراخوانی کرد.
 

Class Methods 

 اگر در subclass متدی همنام متد کلاس پایه داشته باشید، به صورت خودکار متد کلاس پایه override خواهد شد. البته همیشه میتوان متد کلاس پایه را مستقیم هم فراخوانی کرد؛ به عنوان مثال:

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override, shadow, and call Rectangle.prototype.getArea()
    getArea() {
        return super.getArea();
    }
}

در کد بالا متد getArea کلاس پایه بازنویسی شده است. ولی با این حال با استفاده از کلمه‌ی super به متد اصلی در کلاس پایه دسترسی داریم. 

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

let methodName = "getArea";

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override, shadow, and call Rectangle.prototype.getArea()
    [methodName]() {
        return super.getArea();
    }
}

کد بالا دقیقا با مثال قبل یکسان است با این تفاوت که نام متد getArea را به صورت رشته‌ای با قابلیت محاسباتی در نظر گرفتیم.

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

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }

    getArea() {
        return this.length * this.width;
    }

    static create(length, width) {
        return new Rectangle(length, width);
    }
}

class Square extends Rectangle {
    constructor(length) {
        // same as Rectangle.call(this, length, length)
        super(length, length);
    }
}

var rect = Square.create(3, 4);
console.log(rect instanceof Rectangle);     // true
console.log(rect.getArea());                // 12
console.log(rect instanceof Square);        // false

در کد بالا متد استاتیک create یک متد استاتیک در کلاس پایه Rectangle می‌باشد که این بار در کلاس Square هم قابل دسترسی است.

قدرتمندترین جنبه‌ی کلاس‌های مشتق شده در ES 6 ، توانایی ارث بری از expression‌ها می‌باشد. شما می‌توانید کلمه‌ی کلیدی extends را با هر expression ای استفاده کنید. برای مثال:

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
}

var x = new Square(3);
console.log(x.getArea());               // 9
console.log(x instanceof Rectangle);    // true

در کد بالا Rectangle یک تابع سازنده برای تعریف نوع داده شخصی در ES 5 و Square، نوع داده با سینتکس کلاس در ES 6 می‌باشند. ولی با این حال کلاس Square توانسته است از Rectangle ارث بری کند.

یکی دیگر از امکانات فوق العاده‌ی آن، مشخص کردن داینامیک کلاس پایه است. برای مثال: 

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

function getBase() {
    return Rectangle;
}

class Square extends getBase() {
    constructor(length) {
        super(length, length);
    }
}

var x = new Square(3);
console.log(x.getArea());               // 9
console.log(x instanceof Rectangle);    // true

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

new.target

با استفاده از مقدار موجود در این شیء، در سازنده‌ی کلاس می‌توان مشخص کرد که به چه شکلی به  کلاس مورد نظر استناد شده‌است. برای مثال:

class Rectangle {
    constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
    }
}

// new.target is Rectangle
var obj = new Rectangle(3, 4);      // outputs true

در کد بالا با استفاده از new.target توانستیم که مشخص کنیم شیء ایجاد شده از نوع Rectangle می‌باشد. با استفاده از این امکان خوب می‌توان به ساخت کلاس‌های abstract رسید. برای مثال:

// abstract base class
class Shape {
    constructor() {
        if (new.target === Shape) {
            throw new Error("This class cannot be instantiated directly.")
        }
    }
}

class Rectangle extends Shape {
    constructor(length, width) {
        super();
        this.length = length;
        this.width = width;
    }
}

var x = new Shape();                // throws error
var y = new Rectangle(3, 4);        // no error
console.log(y instanceof Shape);    // true

در کد بالا که کاملا هم مشخص است؛ در سازنده‌ی کلاس Shape مشخص کرده‌ایم که اگر مستقیما از کلاس Shape نمونه سازی شد، یک exception را پرتاب کند. با این اوصاف ما توانسته‌ایم که کلاس Shape را به صورت Abstract معرفی کنیم.

مطالب
AOP با استفاده از Microsoft Unity
چند روز پیش فرصتی پیش آمد تا بتوانم مروری بر مطلب منتشر شده درباره AOP داشته باشم. به حق مطلب مورد نظر، بسیار خوب و مناسب شرح داده شده بود و همانند سایر مقالات جناب نصیری چیزی کم نداشت. اما امروز قصد پیاده سازی یک مثال AOP، با استفاده از Microsoft Unity Application Block را به عنوان IOC Container دارم. اگر شما هم، مانند من از UnityContainer به عنوان IOC Container در پروژه‌های خود استفاده می‌کنید نگران نباشید. این کتابخانه به خوبی از مباحث Interception پشتیبانی می‌کند. در ادامه طی یک مقاله این مورد را با هم بررسی می‌کنیم.
برای دوستانی که با AOP آشنایی ندارند پیشنهاد می‌شود ابتدا مطلب مورد نظر را یک بار مطالعه نمایند.
برای شروع یک پروژه در VS.Net بسازید و ارجاع به اسمبلی‌های زیر را در پروژه فراموش نکنید:
»Microsoft.Practices.EnterpriseLibrary.Common
»Microsoft.Practices.Unity
»Microsoft.Practices.Unity.Configuration
»Microsoft.Practices.Unity.Interception
»Microsoft.Practices.Unity.Interception.Configuration

یک اینترفیس به نام IMyOperation بسازید:
    public interface IMyOperation
    {      
        void DoIt();
    }

کلاسی می‌سازیم که اینترفیس بالا را پیاده سازی نماید:
 public void DoIt()
  {
     Console.WriteLine( "this is main block of code" );
  }
قصد داریم با استفاده از AOP یک سری کد مورد نظر خود(در این مثال کد لاگ کردن عملیات در یک فایل مد نظر است) را به کد‌های متد‌های مورد نظر تزریق کنیم. یعنی با فراخوانی این متد کد‌های لاگ عملیات در یک فایل ذخیره شود بدون تکرار یا فراخوانی دستی متد لاگ.
ابتدا یک کلاس برای لاگ عملیات می‌سازیم:
public class Logger
    {
        const string path = @"D:\Log.txt";

        public static void WriteToFile( string methodName )
        {
            object lockObject = new object();
            if ( !File.Exists( path ) )
            {
                File.Create( path );
            }
            lock ( lockObject )
            {
                using ( TextWriter writer = new StreamWriter( path , true ) )
                {
                    writer.WriteLine( string.Format( "{0} at {1}" , methodName , DateTime.Now ) );                
                }
            }
        }
    }
حال نیاز به یک Handler برای مدیریت فراخوانی کد‌های تزریق شده داریم. برای این کار یک کلاس می‌سازیم که اینترفیس ICallHandler را پیاده سازی نماید.
public class LogHandler : ICallHandler
    {
        public IMethodReturn Invoke( IMethodInvocation input , GetNextHandlerDelegate getNext )
        {
            Logger.WriteToFile( input.MethodBase.Name );

            var methodReturn = getNext()( input , getNext );         

            return methodReturn;
        }

        public int Order { get; set; }
    }
کلاس بالا یک متد به نام Invoke دارد که فراخوانی متد‌های مورد نظر برای تزریق کد را در دست خواهد گرفت. در این متد ابتدا عملیات لاگ در فایل مورد نظر ثبت می‌شود(با استفاده از Logger.WriteToFile). سپس با استفاده از getNext که از نوع GetNextHandlerDelegate است، اجرا را به کد‌های اصلی برنامه منتقل می‌کنیم.
 var methodReturn = getNext()( input , getNext );
برای مدیریت بهتر عملیات لاگ یک Attribute می‌سازیم که فقط متد هایی که نیاز به لاگ کردن دارند را مشخص کنیم. به صورت زیر:
 public class LogAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler( Microsoft.Practices.Unity.IUnityContainer container )
        {
            return new LogHandler();
        }
    }
فقط دقت داشته باشید که کلاس مورد نظر به جای ارث بری از کلاس Attribute باید از کلاس HandlerAttribute که در فضای نام Microsoft.Practices.Unity.InterceptionExtension  تعبیه شده است ارث ببرد(خود این کلاس از کلاس Attribute ارث برده است).  کافیست در متد CreateHandler آن که Override شده است یک نمونه از کلاس LogHandler را برگشت دهیم.
برای آماده سازی Ms Unity جهت عملیات Interception باید کد‌های زیر در ابتدا برنامه قرار داده شود:
var  unityContainer = new UnityContainer();

 unityContainer.AddNewExtension<Interception>();

  unityContainer.Configure<Interception>().SetDefaultInterceptorFor<IMyOperation>( new InterfaceInterceptor() );
            
  unityContainer.RegisterType<IMyOperation, MyOperation>();

توضیح چند مطلب:
بعد از نمونه سازی از کلاس UnityContainer باید Interception به عنوان یک Extension به این Container اضافه شود. سپس با استفاده از متد Configure برای اینترفیس IMyOperation یک Interceptor پیش فرض تعیین می‌کنیم. در پایان هم به وسیله متد RegisterType کلاس MyOperation  به اینترفیس IMyOperation رجیستر می‌شود. از این پس هر گاه درخواستی برای اینترفیس IMyOperation از unityContainer شود یک نمونه از کلاس MyOperation در اختیار خواهیم داشت.
به عنوان نکته آخر متد DoIt در اینترفیس بالا باید دارای LogAttribute باشد تا عملیات مزین سازی با کد‌های لاگ به درستی انجام شود.

یک نکته تکمیلی:
در هنگام مزین سازی متد  set خاصیت ها، به دلیل اینکه اینترفیسی برای این کار وجود ندارد باید مستقیما عملیات AOP به خود کلاس اعمال شود. برای این کار باید به صورت زیر عمل نمود:

var container = new UnityContainer();
container.RegisterType<Book , Book>();

container.AddNewExtension<Interception>();

 var policy = container.Configure<Interception>().SetDefaultInterceptorFor<Book>( new VirtualMethodInterceptor() ).AddPolicy( "MyPolicy" );

  policy.AddMatchingRule( new PropertyMatchingRule( "*" , PropertyMatchingOption.Set ) );
  policy.AddCallHandler<Handler.NotifyChangedHandler>();
همان طور که مشاهده می‌کنید عملیات Interception مستقیما برای کلاس پیکر بندی می‌شود و به جای InterfaceInterceptor از VirtualMethodInterceptor برای تزریق کد به بدنه متد‌ها استفاده شده است. در پایان نیز با تعریف یک Policy می‌توانیم به راحتی(با استفاده از "*") متد Set  تمام خواص کلاس را به NotifyChangedHandler مزین نماییم.

سورس کامل مثال بالا
نظرات مطالب
پردازش فایل‌های XML با استفاده از jQuery
function ReadFlagsOnXml(divControlName, XMLFile) {
    var div = $("#" + divControlName);
    console.log($(div).text());

    $.ajax({
        type: "GET",
        url: XMLFile,
        dataType: "xml",
        success: function(xml) {
            var xmlDoc = $.parseXML(xml),
                $xml = $(xmlDoc);
            console.log(xml);

            var countries = $(xml).find('Countries');
            $(countries).find('Country').each(function() {
                var countryName = $(this).find('Name').text();
                var flagImage = $(this).find('Image').text();
                $("<img>", {
                    src: flagImage,
                    title: countryName
                }).appendTo($(div));

            });
        }
    });
}
این کد روی IIS به خوبی اجرا میشه ولی داخل یک دایرکتوری، روی پروتکل file خیر
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت دهم - پیاده سازی الگوی Decorator
الگوی decorator، امکان محصور کردن یک شیء مفروض را با لایه‌ای بر فراز آن میسر می‌کند. برای مثال بجای اینکه در تمام متدهای سرویسی از try/catch استفاده کنیم، می‌توانیم این متدها را با یک ExceptionHandlingDecorator مزین کنیم و یا از این دست اعمال تکراری می‌توان به لاگ کردن ورودی و خروجی‌های یک متد و یا کش کردن اطلاعات آن‌ها نیز اشاره کرد. حتی عملیاتی مانند تشخیص خواص تغییر یافته‌ی یک شیء در Entity framework نیز به کمک همین مزین کننده‌ها که شیء اصلی در حال استفاده را با ایجاد لایه‌ای بر روی آن‌ها محصور می‌کنند، انجام می‌شود. به این عملیات Aspect oriented programming و یا AOP نیز می‌گویند؛ در اینجا واژه‌ی Aspect به اعمال مشترک و متداول موجود در برنامه اشاره می‌کند. در این مطلب قصد داریم نمونه‌ای از این تزئین کننده‌ها را به کمک سیستم تزریق وابستگی‌های NET Core. پیاده سازی کنیم.


پیاده سازی الگوی Decorator به کمک سیستم تزریق وابستگی‌های NET Core.

مثال زیر را در نظر بگیرید که در آن یک سرویس تعریف شده‌است و در این بین استثنائی رخ داده‌است.
    public interface ITaskService
    {
        void Run();
    }

    public class MyTaskService : ITaskService
    {
        public void Run()
        {
            throw new InvalidOperationException("An exception from the MyTaskService!");
        }
    }
می‌خواهیم بدون تغییری در کدهای این کلاس، به متدهای آن در حین اجرای نهایی، یک try/catch را به همراه logging، اضافه کنیم. به همین جهت نیاز خواهیم داشت تا یک محصور کننده (تزئین کننده یا decorator در اینجا) را برای آن طراحی کنیم:
using System;
using Microsoft.Extensions.Logging;
namespace CoreIocServices
{
    public class MyTaskServiceDecorator : ITaskService
    {
        private readonly ILogger<MyTaskServiceDecorator> _logger;
        private readonly ITaskService _decorated;

        public MyTaskServiceDecorator(
            ILogger<MyTaskServiceDecorator> logger,
            ITaskService decorated)
        {
            _logger = logger;
            _decorated = decorated;
        }

        public void Run()
        {
            try
            {
                _decorated.Run();
            }
            catch (Exception ex)
            {
                _logger.LogCritical(ex, "An unhandled exception has been occurred.");
            }
        }
    }
}
این محصور کننده نیز دقیقا همان ITaskService را پیاده سازی می‌کند؛ اما در سازنده‌ی آن یک ITaskService را نیز دریافت می‌کند. علت اینجا است که توسط آن بتوان متدهای ITaskService تزریقی را اجرا کرد و بر روی آن اعمالی مانند کش کردن، لاگ کردن و مدیریت استثناءها و غیره را انجام داد. برای مثال در متد Run آن مشاهده می‌کنید که متد Run همان وهله‌ی تزریقی اجرا شده‌است؛ اما درون یک try/catch به همراه لاگ کردن جزئیات استثنای رخ داده.
مزیت این‌کار، پیاده سازی اصل DRY یا Don't repeat yourself است. کاری که برای رفع این مشکل قرار است انجام دهیم، استفاده از یک تزئین کننده (محصور کننده)، کپسوله سازی اعمال تکراری و سپس اتصال آن به قسمت‌های مختلف برنامه است. همچنین در این حالت اصل open closed principle نیز بهتر رعایت خواهد شد. از این جهت که کدهای تکراری برنامه به یک لایه‌ی دیگر منتقل شده‌اند و دیگر نیازی نیست برای تغییر آن‌ها، کدهای قسمت‌های اصلی برنامه را تغییر داد (کدهای برنامه باز خواهند بود برای توسعه و بسته برای تغییر).

پس از طراحی این تزئین کننده، اکنون نوبت به معرفی آن به سیستم تزریق وابستگی‌های NET Core. است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<MyTaskService>();
            services.AddTransient<ITaskService>(serviceProvider =>
                new MyTaskServiceDecorator(
                     serviceProvider.GetService<ILogger<MyTaskServiceDecorator>>(),
                     serviceProvider.GetService<MyTaskService>())
            );
روش انجام اینکار را نیز در «قسمت ششم - دخالت در مراحل وهله سازی اشیاء توسط IoC Container» بیشتر بررسی کرده‌ایم.
در اینجا هم می‌توان در صورت نیاز اصل کلاس MyTaskService را بدون هیچ نوع تزئین کننده‌ای از سیستم تزریق وابستگی‌ها دریافت کرد و یا اگر وهله‌ای از سرویس ITaskService را از آن درخواست کردیم، ابتدا شیء MyTaskServiceDecorator وهله سازی شده و سپس توسط آن یک نمونه‌ی محصور شده و تزئین شده‌ی MyTaskService به فراخوان بازگشت داده خواهد شد.


ساده سازی معرفی تزئین کننده‌ها به سیستم تزریق وابستگی‌های NET Core. به کمک Scrutor

در «قسمت هشتم - ساده سازی معرفی سرویس‌ها توسط Scrutor» با کتابخانه‌ی Scrutor آشنا شدیم. یکی دیگر از قابلیت‌های آن، امکان ساده سازی تعریف تزئین کنند‌ها است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ITaskService, MyTaskService>();
            services.Decorate<ITaskService, MyTaskServiceDecorator>();
در اینجا معادل کدهایی را که با روش factory خود NET Core. نوشتیم، ملاحظه می‌کنید. ابتدا نیاز است خود سرویس اصلی غیر تزئین شده، به نحو متداولی به سیستم معرفی شود. سپس متد الحاقی جدید <,>Decorate را با همان اینترفیس و اینبار با Decorator مدنظر معرفی می‌کنیم. کاری که Scrutor در اینجا انجام می‌دهد، یافتن سرویس ITaskService معرفی شده‌ی پیشین و تعویض آن با MyTaskServiceDecorator می‌باشد. بنابراین نیاز است تعریف services.AddTransient پیش از تعریف services.Decorate انجام شده باشد. این روش تمیزتر از روش قبلی به نظر می‌رسد و شامل وهله سازی مستقیم MyTaskServiceDecorator به همراه فراهم آوردن تمام پارامترهای سازنده‌ی آن توسط ما نیست.
اشتراک‌ها
تاثیر بروز استثناءها بر روی کارآیی برنامه

In order to cleanse the data as we parse it, we thought using a try/catch would be ok. If we don’t catch the exceptions, we’re good, right?
Turns out it kills our performance when we throw a lot of exceptions, even if we don’t catch them. Each exception has some costs . We needed to find a way to handle this data without involving exceptions.
TryParse turns out to be a method designed to solve our problem. We ran some benchmarks to prove it. 

تاثیر بروز استثناءها بر روی کارآیی برنامه
اشتراک‌ها
آموزش JavaScript توسط Bob Tabor

NOTE: This popular course was recently updated with all new videos and content. Check it out!

01 | IntroductionGet an introduction from Bob, as he kicks off the course, sets expectations, and gives you a little guidance for the road ahead.

02 | Setting Up the Development Environment

03 | Basic JavaScript Syntax

04 | Variables

05 | Data Types

06 | Type Coercion and Conversion

07 | Expressions and Operators

08 | Arrays

09 | Function Declaration

10 | Function Expressions

11 | Decision Statements

12 | Iteration Statements

13 | Basics of Scope

14 | Returning Functions from Functions

15 | Object Literals

16 | Module Pattern and Revealing Module Pattern

17 | Closures

18 | this Keyword

19 | Destructuring

20 | String Template Literals

21 | Regular Expressions

22 | Built-In Natives

23 | Constructor Function Calls with the new Keyword

24 | Objects and the Prototype Chain

25 | JavaScript Classes

26 | Arrow Functions

27 | Truthy and Falsy Values

28 | null Type

29 | Date Objects

30 | String Methods

31 | Array Methods

32 | Error Handling with Try Catch

33 | Understanding the Document Object Model

34 | Working with DOM Nodes

35 | Course Conclusion 

آموزش JavaScript توسط Bob Tabor
نظرات مطالب
ایجاد ویژگی‌های اعتبارسنجی سفارشی در ASP.NET Core 3.1 به همراه اعتبارسنجی سمت کلاینت آن‌ها
کتابخانه FluentValidation ،  این قابلیت رو داره در سمت کلاینت اعتبار سنجی انجام بده و شما میتونید از تمامی شروطی که برای مدل خود در نظر گرفتید به همراه بخش کلاینتش رو باهم استفاده کنید. به عنوان مثال میخواهید کد ملی رو اعتبار سنجی کنید؛ مانند خالی نبودن فیلد. ابتدا از کلاس ClientValidationBase ارث بری می‌کنید
public class NationalCodeClientValidator : ClientValidatorBase
    {
        #region Fields

        private NationalCodeValidator NationalCodeValidator => (NationalCodeValidator)Validator;

        #endregion Fields

        #region Methods

        #region Constructors

        public NationalCodeClientValidator(PropertyRule rule, IPropertyValidator validator) : base(rule, validator)
        {
        }

        #endregion Constructors

        #region Override

        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-nationalcode", GetErrorMessage());
        }

        #endregion Override

        #region Utility

        private string GetErrorMessage()
        {
            var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName());
            string messageTemplate;
            try
            {
                messageTemplate = Validator.Options.ErrorMessageSource.GetString(null);
            }
            catch (FluentValidationMessageFormatException)
            {
                messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>();
            }
            var message = formatter.BuildMessage(messageTemplate);
            return message;
        }

        #endregion Utility

        #endregion Methods
    }

سپس کلاسی دیگر تعریف کرده و از PropertyValidator ارث بری می‌کنید:
public class NationalCodeValidator : PropertyValidator
    {
        #region Methods

        #region Constructors

        public NationalCodeValidator() : base(new LanguageStringSource(nameof(NationalCodeValidator)))
        {
        }

        #endregion Constructors

        #region Override

        protected override bool IsValid(PropertyValidatorContext context)
        {
            return true;
        }

        #endregion Override

        #endregion Methods
    }

سپس باید کلاس‌های تعریف شده رو به FluentValidation معرفی کرد:
.AddFluentValidation(option =>
            {
                option.RegisterValidatorsFromAssemblyContaining<CreateBankValidation>();
                option.ConfigureClientsideValidation(AddFluentValidationClientModelValidatorProvider());
            })

 private static Action<FluentValidationClientModelValidatorProvider> AddFluentValidationClientModelValidatorProvider()
        {
            return clientSideValidation =>
            {
                clientSideValidation.Add(typeof(NationalCodeValidator), (context, rule, validator) => new NationalCodeClientValidator(rule, validator));
            };
        }

حال فقط مانده استفاده ساختار Validation در سمت کلاینت:
// بررسی تایید کد ملی

function setCustomValidator() {
    $.validator.unobtrusive.adapters.add('nationalcode', [], function (options) {
        options.rules['nationalcode'] = {};
        options.messages['nationalcode'] = options.message;
    });
    $.validator.addMethod('nationalcode', function (value, element, parameters) {
        if (isValidIranianNationalCode(value)) return true;
        return false;
    });
}
مطالب
PowerShell 7.x - قسمت سیزدهم - ساخت یک Static Site Generator ساده توسط PowerShell و GitHub Actions
در این مطلب میخواهیم یک مثال دیگر از PowerShell را به همراه GitHub Actions را بررسی کنیم. هدف ایجاد یک Static Site Generator و در نهایت پابلیش خروجی استاتیک بر روی GitHub Pages است. روالی که در ادامه بررسی میکنیم صرفاً یک مثال از ترکیب این تکنولوژی‌ها است و قاعدتاً روش‌های ساده‌تر و سرراست‌تری نیز برای اینکار وجود دارد. به عنوان مثال میتوانید از Jekyll که یک SSG مبتنی بر Ruby است نیز استفاده کنید که GitHub Pages، به صورت پیش‌فرض از آن پشتیبانی میکند. در اینحالت به محض پوش کردن سایت بر روی ریپوزیتوری (با فرض اینکه این امکان را فعال کرده باشید) به صورت خودکار سایت بیلد شده و خروجی بر روی یک برنچ دیگر قرار خواهد گرفت و در نهایت برنچ بیلد شده توسط GitHub Pages میزبانی خواهد شد (البته امکان تغییر برنچ پیش‌فرض را نیز دارید). اما اگر بخواهیم کل فرآیند بیلد را به صورت سفارشی انجام دهیم، میتوانیم از GitHub Actions استفاده کنیم؛ یعنی مشابه کاری که Jekyll انجام میدهد. به محض پوش کردن محتوا، یک اسکریپت PowerShell برای اینکار فراخوانی شود و خروجی نهایی بر روی یک برنچ دیگر منتشر شود. خروجی نهایی این چنین قالبی خواهد داشت:

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

ساختار پروژه
ساختاری که برای پروژه در نظر گرفته‌ام به صورت زیر است:
├── _layout
│   ├── _footer.html
│   ├── _header.html
│   ├── _nav.html
│   └── main.html
├── build
├── img
├── posts
└── set-posts.ps1
  • دایرکتوری layout_: درون این دایرکتوری، ساختار اصلی بلاگ را قرار داده‌ایم. در ادامه محتویات هر فایل را مشاهده خواهید کرد: 
<!--main.html-->
<!DOCTYPE html>
<html dir="rtl">

{{header}}

<body>
    {{nav}}
    <main>
        {{content}}
    </main>
    {{footer}}
</body>



<!--_header.html-->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{title}}</title>
    <link href="https://cdn.jsdelivr.net/gh/rastikerdar/samim-font@v4.0.5/dist/font-face.css" rel="stylesheet"
        type="text/css" />
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet">
    <style>
        * {
        }
    </style>
</head>

<!--_nav.html-->
<header>
    <nav>
        <div>
            <div>
                <a href="#">بلاگ من</a>
            </div>
            <div>
                <ul>
                    {{nav}}
                </ul>
            </div>
        </div>
    </nav>
</header>


<!--_footer.html-->
<footer>
    <div>
        <div>
            <p>
                تمامی حقوق محفوظ است
            </p>
        </div>
    </div>
</footer>
  • دایرکتوری build: درون این دایرکتوری، خروجی‌های HTML که قرار است توسط اسکریپت PowerShell جنریت شوند، قرار خواهند گرفت. این پوشه در واقع قرار است توسط GitHub Pages میزبانی شود.
  • دایرکتوری img: درون این دایرکتوری، تصاویر مربوط به هر بلاگ‌پست را قرار خواهیم داد.
  • دایرکتوری posts: درون این دایرکتوری، مطالب‌مان را با فرمت Markdown، قرار خواهیم داد. به عنوان مثال در ادامه یک نمونه از آن را مشاهده خواهید کرد (در کد زیر از Front Matter برای اضافه کردن یکسری متادیتای موردنیاز که حین بیلد شدن ضروری هستند استفاده شده‌است) 
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

# اولین پست من

## اولین پست من

### اولین پست من

#### اولین پست من

##### اولین پست من

###### اولین پست من

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

<img src="/img/graphql.jpg"/>
همانطور که مشاهده میکنید، مسیر تصویر استفاده شده در بلاگ‌پست، از دایرکتوری img خوانده شده‌است.
  • فایل set_post.ps1: موتور اصلی جنریت کردن صفحات HTML این فایل میباشد. در ادامه محتویات آن را مشاهده خواهید کرد. سپس هر کدام از توابع استفاده شده را یکی‌یکی توضیح خواهیم داد: 
Function Get-Layouts {
    $headerLayout = Get-Content -Path ./_layout/_header.html -Raw
    $homeLayout = Get-Content -Path ./_layout/main.html -Raw
    $footerLayout = Get-Content -Path ./_layout/_footer.html -Raw

    Return @{
        Header = $headerLayout
        Home   = $homeLayout
        Footer = $footerLayout
    }
}

Function Get-PostFrontMatter($postContent) {
    $frontMatter = [regex]::Match($postContent, "(---(?:\r?\n(?!--|\s*$).*)*)\s*((?:\r?\n(?!---).*)*\r?\n---)")
    Return $frontMatter
}

Function Set-Headings($postHtml) {
    Return $postHtml -Replace '<h(\d) id="(.*)">', {
        $level = $_.Groups[1].Value
        $id = $_.Groups[2].Value
        $class = Switch ($level) {
            '1' { 'text-4xl font-bold mb-2' }
            '2' { 'text-3xl font-bold mb-2' }
            '3' { 'text-2xl font-bold mb-2' }
            '4' { 'text-xl font-bold mb-2' }
            '5' { 'text-lg font-bold mb-2' }
            '6' { 'text-base font-bold mb-2' }
        }
        "<h$level class='$class' id='$id'>"
    }
}

Function ConvertTo-Slug {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$String
    )
    process {
        $slug = $String -replace '[^\w\s-]', '' # remove non-word characters except hyphens
        $slug = $slug -replace '\s+', '-' # replace whitespace with a single hyphen
        $slug = $slug -replace '^-+|-+$', '' # remove leading/trailing hyphens
        $slug = $slug.ToLower() # convert to lowercase
        Write-Output $slug
    }
}

Function Get-Posts {
    $markdownPosts = Get-ChildItem -Path ./posts -Filter *.md
    $posts = @()
    Foreach ($post in $markdownPosts) {
        $postContent = Get-Content -Path $post.FullName -Raw
        $frontMatter = Get-PostFrontMatter $postContent
        $frontMatterObject = $frontMatter | ConvertFrom-Yaml

        $slug = $frontMatterObject.slug ?? (ConvertTo-Slug "$($frontMatterObject.date)-$($frontMatterObject.title)")
        $body = $postContent.Replace($frontMatter.Value, "") | ConvertFrom-Markdown
        $postHtml = $layouts.Home -replace '{{header}}', $layouts.Header `
            -replace '{{title}}', $frontMatterObject.title `
            -replace '{{nav}}', (Set-Navs) `
            -replace '{{content}}', $body.Html `
            -replace '{{footer}}', $layouts.Footer

        $postHtml = Set-Headings $postHtml
        $postHtml | Out-File -FilePath ./build/$slug.html

        $posts += @{
            title   = $frontMatterObject.title
            slug    = $slug
            excerpt = $frontMatterObject.excerpt
            date    = $frontMatterObject.date
            author  = $frontMatterObject.author
            body    = $body.Html
        }
    }
    Return $posts
}

Function Set-Archive {
    $posts = Get-Posts
    $archive = @()
    $archive = @"
        <ul>
            $($posts | ForEach-Object { "<li><a href='$($_.slug).html'>$($_.title)</a></li>" })
        </ul>
"@
    Return $archive -join "`r`n"
}

Function Copy-ToBuild {
    $layouts = Get-Layouts
    $latestPosts = Get-Posts | ForEach-Object { @"
    <div>
        <img src="https://via.placeholder.com/300x200" alt="$($_.title)">
        <h2>$($_.title)</h2>
        <p>$($_.excerpt)</p>
            <a href="$($_.slug).html">ادامه مطلب</a>
        </div>
"@
    }

    $homeLayout = $layouts.Home -replace '{{header}}', $layouts.Header `
        -replace '{{nav}}', (Set-Navs) `
        -replace '{{title}}', 'بلاگ من' `
        -replace '{{content}}', ('<div>' + $latestPosts + '</div>') `
        -replace '{{footer}}', $layouts.Footer

    $homeLayout | Out-File -FilePath ./build/index.html

    Copy-Item -Path ./img -Destination ./build -Recurse -Force

}

Function Set-Navs {
    $navs = @(
        @{
            title = "صفحه اصلی"
            url   = "/sample"
        },
        @{
            title = "درباره ما"
            url   = "/sample/about.html"
        },
        @{
            title = "تماس با ما"
            url   = "/sample/contact.html"
        }
    )
    $navLayout = Get-Content -Path ./_layout/_nav.html -Raw
    $navLayout -replace '{{nav}}', ($navs | ForEach-Object { "<li><a href=""$($_.url)""text-gray-700 hover:text-gray-800 m-2"">$($_.title)</a></li>" })
}


Copy-ToBuild
همانطور که در کد فوق مشاهده میکنید، تابع Copy-ToBuild فراخوانی شده است. درون این تابع، ابتدا لی‌اوت‌های موردنیاز برای تولید صفحات HTML را درون یک متغییر با نام layouts قرار داده‌ایم. درون لی‌اوت‌ها یکسری placeholder برای قرارگیری قسمت‌های مختلف سایت تعریف کرده‌ایم که قرار است توسط تابع عنوان شده، جایگزین شوند. در ادامه توسط تابع Get-Posts، تمامی مطالب درون دایرکتوری posts را واکشی کرده و برای هر کدام، صفحه‌ی HTML معادل آن را تولید کرده‌ایم. برای پارز کردن قسمت Front Matter هر بلاگ‌پست نیز از پکیج powershell-yaml استفاده شده‌است. درون تابع Get-Posts باید هر مطلب را همراه با لی‌اوت اصلی سایت، به HTML تبدیل کنیم. بنابراین یکسری عملیات string replacement درون تابع عنوان شده انجام گرفته است. در نهایت، مطالب همراه با لی‌اوت مناسبی درون دایرکتوری build ذخیره خواهند شد و سپس یک لیست از مطالب پارز شده به عنوان خروجی تابع برگردانده خواهد شد. در نهایت درون تابع Copy-ToBuild، یکسری card برای نمایش آخرین مطالب بلاگ تهیه شده، سپس خروجی نهایی درون فایل index.html همراه با قالب اصلی ذخیره خواهد شد.

ایجاد GitHub Actions Workflow
در ادامه برای ساختن workflow نهایی باید با کمک GitHub Actions، اسکریپت PowerShellی را که ساختیم، اجرا کنیم. این اسکریپت ابتدا پروژه را clone کرده، سپس وابستگی موردنیاز را نصب کرده و در نهایت اسکریپت را اجرا خواهد کرد: 
name: Deploy static content to Pages

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Install powershell-yaml module
        shell: pwsh
        run: |
          Set-PSRepository PSGallery -InstallationPolicy Trusted
          Install-Module powershell-yaml -ErrorAction Stop

      - name: Setup Pages
        uses: actions/configure-pages@v3

      - name: Build Static Site
        shell: pwsh
        run: |
          . ./set-posts.ps1

      - name: Upload Static Site Artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: build

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2

نحوه انتشار یک مطلب جدید
  • درون دایرکتوری posts، مطلب موردنظر را به همراه Front Matter زیر ایجاد کرده و سپس محتویات مطلب را بعد از آن وارد کنید:
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

content
  • تغییرات ایجاد شده را کامیت و سپس پوش کنید. به محض پوش کردن تغییرات، GitHub Actions پروسه بیلد را انجام خواهد داد و بلافاصله میتوانید تغییرات را مشاهده نمائید.
نکته: قابلیت GitHub Pages به صورت پیش‌فرض فعال نیست. برای فعال کردن آن ابتدا باید به قسمت تنظیمات ریپوزیتوری GitHubتان مراجعه کرده و سپس از تب Pages، فیلد Source را بر روی GitHub Actions قرار دهید:


بنابراین بعد از پوش کردن تغییرات workflowایی که ایجاد کردیم توسط Source شناسایی خواهد شد و سپس وب‌سایت از طریق آدرس https://<username>.github.io/<repository> در دسترس قرار خواهد گرفت:

کدهای این مطلب را میتوانید از اینجا دریافت کنید.

مطالب
کار با اسناد در RavenDb 4، ثبت و ویرایش
اگر تا بحال با بانک‌های NoSql کار کرده و لذت برده‌اید، به شما پیشنهاد میکنم حتما RavenDb را هم امتحان کنید، تا لذت استفاده از NoSql را چندین برابر حس کنید! RavenDb یک بانک اطلاعاتی NoSql از نوع DocumentStore است که به‌صورت متن باز توسعه داده می‌شود و مخزن کد آن در Github موجود است. از ویژگی‌های بارز RavenDb نسبت به سایر DocumentStoreها، Transactional بودن میباشد و در نسخه‌ی 4 بصورت کامل از Net Core. پشتیبانی میکند. برای آشنایی بیشتر با NoSql میتوانید از مقالات موجود در گروه NoSql استفاده کنید و برای آشنایی با RavenDb از دوره ای که در سایت وجود دارد استفاده نمایید(دوره مربوط به نسخه‌ی 3.5 می‌باشد).
از بارز‌ترین ویژگی‌های NoSqlها توانایی آن‌ها در ذخیره‌ی اطلاعات، بدون توجه به اسکیمای آن هاست؛ پس هر نوع مدلی که ما برای ذخیره اطلاعات نرم افزار تعریف میکنیم، فقط برای درک بهتر ما هست و بس!

با این مقدمه مدل‌های زیر را برای شروع کار داریم:
Public Class User
{
        public string Id { get; set; }
        public string PhoneNumber { get; set; }
        public Dictionary<string, App> Apps { get; set; }
}
public class App
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public List<string> Roles { get; set; }
        public List<String> Messages { get; set; }
        public String AdressId { get; set; }
        public bool IsActive { get; set; } = true;
        [JsonIgnore]
        public string DisplayName => $"{FirstName} {LastName}";
}

در این مدل، هر کاربر با یک شماره تماس میتواند در چندین برنامه ثبت شود و اطلاعات او در هر برنامه هم میتواند متفاوت باشد.
برای اتصال به RavenDb، به DocumentStore و برای ارسال درخواست‌ها به سمت سرور، به DocumentSession نیاز داریم. نمونه سازی DocumentStore هزینه‌بر بوده و باید در طی اجرای نرم افزار فقط یکبار(Singleton) نمونه سازی شود. DocumentSession بسیار سبک بوده و باید به ازای هر درخواست که به سمت سرور RavenDb ارسال میگردد یک بار نمونه سازی شده و بعد از آن نابود شود. پس برای استفاده در ASP.NET Core به این پیاده سازی در Startup میرسیم:
services.AddSingleton<IDocumentStore>(serviceProvider =>
{
      var store = new DocumentStore()
      {
            Urls = new[] { "http://192.168.1.10:8080" },
            Database = "AccountingSystem",
      }.Initialize();
      return store;
});

services.AddScoped<IAsyncDocumentSession>(serviceProvider =>
{
      var store = serviceProvider.GetRequiredService<IDocumentStore>();
      return store.OpenAsyncSession();
});

حال در تمام بخش‌های نرم افزار می‌توانیم DocumentSession استفاده کنیم.
برای ذخیره سازی مدل در RavenDb از کد زیر استفاده می‌کنیم:
var user = new User
{
      PhoneNumber = user.PhoneNumber
};
user.Apps.Add(appCode, new ActiveApp
{
       FirstName = "عبدالصالح",
       LastName = "کاشانی",
       UserName = abdossaleh,
       IsActive = true,
       RolesId = new List<string>{"Admin"}
});
await _documentSession.StoreAsync(user);
await _documentSession.SaveChangesAsync()

این ساده‌ترین کاری هست که میتوانیم انجام دهیم. بلافاصله بعد از استفاده از متد StoreAsync و بدون رفت و برگشتی به سرور، ویژگی Id برای user مقداردهی می‌شود و توضیح این رفتار هم پیشتر گفته شده است. با فراخوانی متد SaveChangesAsync تغییرات اتفاق افتاده در DocumentSession برای ذخیره سازی به سمت سرور ارسال می‌شوند. بله! الگوی Repository و UnitOfWork.
حال برای دریافت همین مدل، در صورتیکه Id آن را در اختیار داشته باشیم، از متد LoadAsync استفاده میکنیم.
var user = await _documentSession.LoadAsync<User>("Users/131-A");
با لود شدن کاربر، این Entity تحت نظر قرار میگیرد و اگر تغییری در هر کدام از ویژگی‌های آن صورت گیرد و متد SaveChangesAsync فراخوانی شود، کل مدل برای به‌روزرسانی به سمت سرور ارسال میشود. کل مدل و این به معنای بار اضافی در شبکه هست. البته در مدل‌های کوچک بهتر است که همین کار را انجام دهیم. ولی در اینجا به عمد مدلی را انتخاب کرده‌ایم که اطلاعات زیادی را در خود نگهداری میکند و ارسال تمام آن به ازای یک تغییر کوچک به صرفه نیست! خوشبختانه RavenDb برای حل این مشکل امکانات جالبی را در اختیار ما قرار داده که در ادامه آن‌ها را بررسی می‌کنیم.

Patching
به معنای تغییر دادن قسمتی از سند که شامل تغییر مقادیر، اضافه یا حذف یک ویژگی، ایجاد تغییرات در لیست و ... می‌باشد. با استفاده از متدهای Patch سند، میتوانیم بدون نیاز به لود سند و تغییر و ذخیره آن، قسمتی از سند را ویرایش کنیم. عملیات Patch، سمت سرور اجرا می‌شوند. برای مثال برای تغییر شماره تماس، از متد زیر استفاده می‌کنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.PhoneNumber
      , "09131110000");
که مدلی را که میخواهیم تغییر دهیم، به همراه نوع ویژگی مورد نظر برای تغییر، دریافت میکند و بعد از آن، به ترتیب Id سند مورد نظر، ویژگی مورد نظر برای اعمال تغییر و مقدار را میگیرد و با فراخوانی SaveChangesAsync این تغییرات اعمال می‌شوند. نکته‌ای که باید توجه کنید این است که اگر مدلی را لود کردید و در فیلدهای آن تغییری ایجاد نموده‌اید، دیگر نمیتوانید از Patch یا Defer (توضیح داده میشود) استفاده کنید. به عبارت دیگر در هر درخواست یا باید از سیستم Tracking خود RavenDb استفاده کنید و یا از Patching!
برای اضافه کردن یک آیتم به لیست،  از Patch بصورت زیر استفاده میکنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.Apps["59"].RolesId
      , r => r.Add("Admin"));

برای اضافه کردن مقداری به یک مقدار عددی در RavenDb، از متد Increment بصورت زیر استفاده میکنیم:
 _documentSession.Advanced.Increment<User, int>("Users/131-A", x => x.TestProp, 10);
متد Patch برای کارهای ساده‌ی اینچنین بسیار کاربردی می‌باشد؛ ولی برای کارهای پیشرفته‌تر کارآیی ندارد. به همین دلیل متد Defer در کنار آن معرفی شده‌است که فوق العاده کاربردی ولی اصطلاحا non-typed است و تحت نظارت Compiler نیست. برای مثال اضافه کردن یک مقدار به Dictionary ما، از طریق Patch امکان ندارد. اما اینکار با استفاده از متد Defer و کدهای JavaScript به‌سادگی زیر می‌باشد:
_documentSession.Advanced.Defer(new PatchCommandData("Users/131-A", null,
                              new PatchRequest()
                              {
                                    Script = $@"this.Apps[args.appCode] = args.app",
                                    Values =
                                         {
                                              {"appCode", appCode},
                                              {"app", new ActiveApp
                                                   {
                                                        FirstName = "عبدالصالح",
                                                        LastName = "کاشانی",
                                                        UserName = abdossaleh,
                                                        RolesId = new List<string>{"Admin"}
                                                    }
                                              }
                                          }
                              }, null));
متد Defer شناسه‌ی سند مورد نظر را گرفته و اسکریپت ما را با آرگومان‌های ارسالی، بر روی سند اعمال میکند. Defer دسترسی کاملی را به ما برای تغییر در سند میدهد. برای نمونه میتوانیم آیتمی را به مکان خاصی از لیست اضافه کنیم (برای کوتاه‌تر شدن اسکریپت‌ها فقط بخش Script و Value را ذکر میکنم):
Script = "this.Apps[args.app].Roles.splice(args.index,0,args.role)",
Values =
        {
            {
                "index": 1 // مکانی که میخواهیم عملیات انجام شود
                "app", 59
                "role", "User"
            }
        }
this در اینجا به سند جاری اشاره میکند.
از همین روش میتوانیم برای ویرایش کردن یک آیتم هم استفاده کنیم. برای مثال اگر مقدار 0 را در متد splice به یک تغییر دهیم، عملیات ویرایش صورت می‌گیرد (در واقع حذف آیتم در مکان index و درج آیتم جدید در همان مکان):
splice(args.index,1,args.role)
و برای حذف تمام آیتم‌های لیست جز یک آیتم خاص، از کد زیر استفاده میکنیم:
Script = @"this.Roles= this.Apps[args.app].Roles.filter(role=> role != args.role);",
        Values =
        {
            {"app", 59}
            {"role", "User"}
        }
همانطور که مشاهده می‌کنید به راحتی می‌توانیم کدهای جاوا اسکریپتی خود را در Defer استفاده کنیم. اما این قدرت زیاد، امکان اشتباه در کدهای ما را زیاد میکند چرا که تحت کنترل کامپایلر نیست.