مطالب
Reflection در ES6
در زبان‌های برنامه‌نویسی مانند سی‌شارپ و یا جاوا می‌توانیم از Reflection جهت خواندن متادیتاها استفاده کنیم. به عنوان مثال امکان تعریف پراپرتی و یا متدها و حتی تایپ‌هایی در زمان اجرا را در اختیارمان قرار می‌دهد. اما از آنجائیکه جاوا اسکریپت یک زبان داینامیک است، این قابلیت کمتر مورد توجه قرار گرفته است. در جاوا اسکریپت حین کار با کلاس‌ها و اشیاء، ممکن است نیاز داشته باشید تا از اعضای یک کلاس کوئری بگیرید و یا اینکه یک سری پراپرتی و متدهایی را در زمان اجرا به اشیاء‌تان اضافه کنید و یا مواردی از این دست.
تا قبل از ES 6 می‌توانستیم به این صورت به اعضای یک کلاس دسترسی داشته باشیم:
var person = {
    name: 'Sirwan',
    family: 'Afifi',
    doWork: function () {
        //....
    }
};

for (var prop in person) {
    console.log(prop); 
}
همانطور که مشاهده می‌کنید، توسط سینتکس for...in می‌توانیم به اعضای person دسترسی داشته باشیم. همچنین برای دسترسی به مقادیر هر کدام از اعضای آن می‌توانستیم از bracket syntax استفاده کنیم:
for (var prop in person) {
    console.log(person[prop]); 
}
لازم به ذکر است می‌توانستیم از متدهایی همانند Array.isArray, Object.getOwnPropertyDescriptor و حتی Object.keys نیز جهت دسترسی به اعضای پنهان یک شیء استفاده کنیم. اکنون قابلیت توکاری با نام Reflect این امکان را به ES 6 اضافه کرده است تا تمام اعمال فوق را به راحتی انجام داد.

Reflect
همانطور که عنوان شد توسط API جدیدی با نام Reflect، می‌توانیم به سادگی اعمال مربوط به Reflection را انجام دهیم. به عنوان مثال برای دسترسی به اعضای شیء person با استفاده از این API می‌توانیم به این صورت عمل کنیم:
var person = {
    name: 'Sirwan',
    family: 'Afifi',
    doWork: function () {
        //....
    }
};

for (let prop of Reflect.enumerate(person)) {
    console.log(`the value for ${prop} is ${person[prop]}`); 
  
}
در کد فوق از متد enumerate استفاده شده است، این متد یک پارامتر target را از ورودی می‌پذیرد. در واقع target همان شیءایی است که می‌خواهید پراپرتی‌های آن را پیمایش کنید. همچنین مقدار بازگشتی این متد یک iterator می‌باشد. مفهوم iterator نیز خیلی ساده است. به زبان ساده، امکان پیمایش درون یک کالکشن را در اختیارمان قرار می‌دهد. نکته‌ی دیگری که در کد فوق وجود دارد، استفاده از سینتکس for..of است، این سینتکس شبیه به for...in عمل می‌کند، با این تفاوت که در اینجا به جای پیمایش درون یکسری keys، درون values پیمایش می‌کنیم. در نتیجه برای پیمایش iterators باید از این سینتکس استفاده شود.

متدهای شیء Reflect
در ادامه تعدادی از متدهای شیء Reflect را مشاهده می‌کنید:
Reflect.get(target, name, [receiver])

Reflect.set(target, name, value, [receiver])

Reflect.has(target, name)

Reflect.apply(target, receiver, args)

Reflect.construct(target, args)

Reflect.getOwnPropertyDescriptor(target, name)

Reflect.defineProperty(target, name, desc)

Reflect.getPrototypeOf(target)

Reflect.setPrototypeOf(target, newProto)

Reflect.deleteProperty(target, name)

Reflect.enumerate(target)

Reflect.preventExtensions(target)

Reflect.isExtensible(target)

Reflect.ownKeys(target)
تعدادی از متدهای فوق خیلی مشابه متدهای Object هستند؛ با این تفاوت که متدهای فوق اطلاعات بهتری را بر می‌گردانند. به عنوان مثال متد Reflect.defineProperty در صورت ایجاد شدن یک پراپرتی جدید، مقدار true را برمی‌گرداند:
var yay = Reflect.defineProperty(target, 'foo', { value: 'bar' })
if (yay) {
  // yay!
} else {
  // oops.
}
اگر همین کار را با استفاده از متد Object.defineProperty انجام می‌دادیم می‌بایست کد را درون بلاک try/catch می‌نوشتیم:
try {
  Object.defineProperty(target, 'foo', { value: 'bar' })
  // yay!
} catch (e) {
  // oops.
}
به عنوان یک مثال دیگر، قبلاً برای حذف یک پراپرتی از یک شیء از کلمه کلیدی delete به اینصورت استفاده می‌کردیم:
var target = { foo: 'bar', baz: 'wat' }
delete target.foo
console.log(target)
// <- { baz: 'wat' }
اما با کمک متد Reflect.deleteProperty به راحتی می‌توانیم یک پراپرتی را حذف کنیم:
var target = { foo: 'bar', baz: 'wat' }
let isDeleted = Reflect.deleteProperty(target, 'foo')
console.log(isDeleted );
// true
در این‌حالت همانند متد Reflect.defineProperty، در صورت موفقیت‌آمیز بودن عمل حذف، مقدار true برگردانده می‌شود.
برای مشاهده‌ی لیست کامل متدهای فوق می‌توانید به اینجا مراجعه کنید. همچنین می‌توان با مراجعه به قسمت Browser compatibility وضعیت پشتیبانی هر کدام را در مرورگرهای مختلف مشاهده کرد.

نکاتی که در حین کار کردن با Reflect باید در نظر بگیرید:
  • این شیء فاقد متد [[Construct]] می‌باشد. یعنی نمی‌تواند همراه با کلمه‌ی کلیدی new مورد استفاده قرار گیرد.
  • همچنین نمی‌توان آن را همانند یک تابع فراخوانی کرد.
  • تمام متدهای آن به صورت استاتیک تعریف شده‌اند (همانند شیء Math).
نظرات مطالب
ایجاد نصاب یک قالب پروژه جدید چند پروژه‌ای در ویژوال استودیو
فایل extension.vsixmanifest افزونه رو باید ویرایش کنید (فایل vsix در اصل یک فایل zip است). مثلا VisualStudio Version آن الان 10 است که باید بشود 11. بعد در SupportedFrameworkRuntimeEdition آن باید MaxVersion به 4.5 تنظیم شود.
نظرات مطالب
EF Code First #12
علت وقوع خطا، در استثنای صادره ذکر شده: Page not found. یعنی کاربر یا حتی مرورگر درخواست آدرسی رو کرده که در سایت شما موجود نیست (مثلا کروم اولین کاری که انجام می‌ده، وجود فایل استاندارد آیکن سایت رو به صورت خودکار بررسی می‌کنه). بنابراین امکان وهله سازی کنترلر معادل آن صفحه یا آدرس یافت نشده، وجود ندارد.
نظرات مطالب
jQuery Ajax و نحوه صحیح ارسال مقادیر به یک وب سرویس
من کد زیر رو ارسال می‌کنم ولی مشکلی که وجود داره این هست که نه پارمترها ارسال میشه و نه درخواست آژاکسی هست.صفحه رو واقعا هدایت میکنه به آدرس مورد نظر.
//new group
    $("#NewGroup").on('click', function () {
        var catName = 'new';// $("#appendNodeText").val();
        $.ajax({
            dataType: "json",
            type: "POST",
            url: "@Url.Action(MVC.Categories.NewCategory())",
            data: { name: catName }

        }).done(function (data) {
            alert(data);
        });
    });
مطالب
کلاس‌ها در 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 معرفی کنیم.

مطالب
MVC Scaffolding #1
پیشنیازها
کل سری ASP.NET MVC
به همراه کل سری EF Code First


MVC Scaffolding چیست؟

MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامه‌های ASP.NET MVC مبتنی بر EF Code First.


بررسی مقدماتی MVC Scaffolding

امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژه‌های ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندی‌ها و قابلیت‌های سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدل‌های برنامه اضافه کنید:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models
{
    public class Task
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        [DisplayName("Due Date")]
        public DateTime? DueDate { set; get; }

        [DisplayName("Is Complete")]
        public bool IsComplete { set; get; }

        [StringLength(450)]
        public string Description { set; get; }
    }
}
سپس بر روی پوشه Controllers کلیک راست کرده و گزینه Add controller را انتخاب کنید. تنظیمات صفحه ظاهر شده را مطابق شکل زیر تغییر دهید:


همانطور که ملاحظه می‌کنید در قسمت قالب‌ها، تولید کنترلرهایی با اکشن متد‌های ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آن‌را پذیرفته‌ایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:


الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدل‌های برنامه ایجاد می‌گردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه می‌کند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلی‌های دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)

معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شده‌ای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی می‌تواند شامل فیلدهای بیشتری باشد و نباید آن‌را مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).

مزیت‌ها:
قسمت عمده‌ای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شده‌اند. کارهای اولیه‌ای که با هر روش و الگوی شناخته شده‌ای قصد پیاده سازی آن‌ها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمده‌ای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرم‌های آن‌را بخواهید با jQuery Ajax پیاده سازی کنید یا کنترل‌های پیش فرض را با افزونه‌های jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.


نصب بسته اصلی MVC Scaffolding توسط NuGet

بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console می‌توان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
اگر به مراحل نصب آن دقت کنید یک سری وابستگی را نیز به صورت خودکار دریافت کرده و نصب می‌کند:
Attempting to resolve dependency 'T4Scaffolding'.
Attempting to resolve dependency 'T4Scaffolding.Core'.
Attempting to resolve dependency 'EntityFramework'.
Successfully installed 'T4Scaffolding.Core 1.0.0'.
Successfully installed 'T4Scaffolding 1.0.8'.
Successfully installed 'MvcScaffolding 1.0.9'.
Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1.
Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1.
Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
از مواردی که با T4 آغاز شده‌اند در قسمت‌های بعدی برای سفارشی سازی کدهای تولیدی استفاده خواهیم کرد.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز می‌توان پیاده سازی کرد:
Scaffold Controller Task
نوشتن این دستور نیز ساده است. حروف sca را تایپ کرده و دکمه tab را فشار دهید. منویی ظاهر خواهد شد که امکان انتخاب دستور Scaffold را می‌دهد. یا برای نوشتن Controller نیز به همین نحو می‌توان عمل کرد.
نکته و مزیت مهم دیگری که در اینجا در دسترس می‌باشد، سوئیچ‌های خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچ‌های قابل استفاده است.


برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
اگر این دستور را اجرا کنیم به همان نتیجه حاصل از مراحل توضیح داده شده قبل خواهیم رسید؛ البته یا یک تفاوت: یک Partial View اضافه‌تر نیز به نام CreateOrEdit در پوشه Views\Tasks ایجاد شده است. این Partial View بر اساس بازخورد برنامه نویس‌ها مبنی بر اینکه Viewهای Edit و Create بسیار شبیه به هم هستند، ایجاد شده است.


بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding

در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
البته اگر دستور فوق را به همین نحو اجرا کنید با یک سری خطای Skipping مواجه خواهید شد مبنی بر اینکه فایل‌های قبلی موجود هستند و این دستور قصد بازنویسی آن‌ها را ندارد. برای اجبار به تولید مجدد کدهای موجود می‌توان از سوئیچ Force استفاده کرد:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
اتفاقی که در اینجا رخ خواهد داد، بازنویسی کد بی‌کیفت ابتدایی همراه با وهله سازی مستقیم DbContext در کنترلر، به نمونه بهتری که از الگوی مخزن استفاده می‌کند می‌باشد:
    public class TasksController : Controller
    {
        private readonly ITaskRepository taskRepository;

        // If you are using Dependency Injection, you can delete the following constructor
        public TasksController()
            : this(new TaskRepository())
        {
        }

        public TasksController(ITaskRepository taskRepository)
        {
            this.taskRepository = taskRepository;
        }
کیفیت کد تولیدی جدید مبتنی بر الگوی مخزن بد نیست؛ دقیقا همانی است که در هزاران سایت اینترنتی تبلیغ می‌شود؛ اما ... آنچنان مناسب هم نیست و اشکالات زیر را به همراه دارد:
public interface ITaskRepository : IDisposable
{
  IQueryable<Task> All { get; }
  IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties);
  Task Find(int id);
  void InsertOrUpdate(Task task);
  void Delete(int id);
  void Save();
}
اگر به ITaskRepository تولیدی دقت کنیم دارای خروجی IQueryable است؛ به این حالت leaky abstraction گفته می‌شود. زیرا امکان تغییر کلی یک خروجی IQueryable در لایه‌های دیگر برنامه وجود دارد و حد و مرز سیستم توسط آن مشخص نخواهد شد. بهتر است خروجی‌های لایه سرویس یا لایه مخزن در اینجا از نوع‌های IList یا IEnumerable باشند که درون آن‌ها از IQueryable‌ها برای پیاده سازی منطق مورد نظر کمک گرفته شده است.
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزن‌های تعریف شده را تک موجودیتی می‌کند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیت‌ها به کلاس‌های مخزن واگذار شده‌اند و کنترلرها به این نحو خلوت‌تر گردیده‌اند، یک مرحله پیشرفت محسوب می‌شود.
مطالب
Best Practice هایی برای ASP.NET MVC - قسمت اول
در سایت جاری مطالب زیادی درباره ASP.NET MVC نوشته شده است. این مطلب و قسمت بعدی آن مروری خواهد داشت بر Best Practice‌ها در ASP.NET MVC.

استفاده از NuGet Package Manager برای مدیریت وابستگی‌ها

درباره اهمیت NuGet برای مصرف کنندگان قبلا این مطلب نوشته شده است.
بجای صرف وقت برای اینکه بررسی کنیم آیا این نسخه‌ی جدید کتابخانه‌ی X یا اسکریپت jQuery آمده است یا خیر، می‌توان این وظیفه را به NuGet سپرد. علاوه بر این NuGet مزیت‌های دیگری هم دارد؛ مثلا تیم‌های برنامه نویسی می‌توانند کتاب خانه‌های مشترک خودشان را در مخزن‌های سفارشی NuGet قرار دهند و توزیع و Versioning آن‌را به NuGet بسپارند.


تکیه بر Abstraction (انتزاع)

Abstraction در طراحی سیستم‌ها منجر به تولید نرم افزار هایی Loosely coupled با قابلیت نگهداری بالا و همچنین فراهم شدن زمینه برای نوشتن Unit Test می‌شود.
اگر به مطالب قبلی وب سایت برگردیم در مطلب چرا ASP.NET MVC گفته شد که :
2) دستیابی به کنترل بیشتر بر روی اجزای فریم ورک :
در طراحی ASP.NET MVC همه‌جا interface‌ها قابل مشاهد هستند. همین مساله به معنای افزونه پذیری اکثر قطعات تشکیل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. برای مثال تابحال چندین view engine، routing engine و غیره توسط برنامه نویس‌های مستقل برای ASP.NET MVC طراحی شده‌اند که هیچکدام با ASP.NET web forms میسر نیست. برای مثال از view engine پیش فرض آن خوشتان نمی‌آید؟
عوضش کنید! سیستم اعتبار سنجی توکار آن‌را دوست ندارید؟ آن‌را با یک نمونه بهتر تعویض کنید و الی آخر ...
به علاوه طراحی بر اساس interface‌ها یک مزیت دیگر را هم به همراه دارد و آن هم ساده سازی
mocking (تقلید) آن‌ها است جهت ساده سازی نوشتن آزمون‌های واحد.


از کلمه‌ی کلیدی New استفاده نکنید

هر جا ممکن است کار وهله سازی اشیاء را به لایه و حتی Framework دیگری بسپارید. هر زمان اشیاء نرم افزار خودتان را با کلمه‌ی new وهله سازی می‌کنید اصل Abstraction را فراموش کرده اید. هر زمان اشیاء نرم افزار را مستقیم وهله سازی می‌کنید در نظر داشته باشید می‌توانید آنها را به صورت وابستگی تزریق کنید.
در همین رابطه مطالب زیر را از دست ندهید :


از HttpContext مستقیما استفاده نکنید (از HttpContextBase استفاده کنید)

از .NET 4 به بعد فضای نامی تعریف شده که در بر دارنده‌ی کلاس‌های انتزاعی (Abstraction) خیلی از قسمت‌های اصلی ASP.NET است.  یکی از مواردی که در توسعه‌ی ASP.NET معمولا زیاد استفاده می‌شود، شیء HttpContext است . استفاده از HttpContextBase را به استفاده از HttpContext ترجیح دهید. اهمیت این موضوع در راستای اهمیت انتزاع (Abstraction) می‌باشد.


از "رشته‌های جادویی" اجتناب کنید

استفاده از رشته‌های جادویی در خیلی از جاها کار را ساده می‌کند؛ بعضی وقت‌ها هم به آنها نیاز است اما مشکلات زیادی دارند :
  1. رشته‌ها معنای باطنی ندارند (مثلا : دشوار است که از روی نام یک ID مشخص کنم این ID چطور به ID دیگری مرتبط است و یا اصلا ربط دارد یا خیر)
  2. با اشتباهات املایی یا عدم رعایت حروف بزرگ و کوچک ایجاد مشکل می‌کنند.
  3. به Refactoring واکنش خوبی نشان نمی‌دهند. (برای درک بهتر این مطلب را بخوانید.)
برای درک بهتر 2، یک مثال بررسی می‌شود؛ اولی از رشته‌های جادویی برای دسترسی به داده در ViewData استفاده می‌کند و دومی refactor شده‌ی مثال اول است که از strongly type مدل برای دسترسی به همان داده استفاده می‌کند.
<p>
    <label for="FirstName">First Name:</label>
    <span id="FirstName">@ViewData["FirstName"]</span>
</p>
مثال دوم :
<p>
    <label for="FirstName">First Name:</label>
    <span id="FirstName">@Model.FirstName</span>
</p>
مثلا مثال دوم مشکلات رشته‌های جادویی را ندارد.
در رابطه با Magic strings این مطلب را مطالعه بفرمایید.


از نوشتن HTML در کدهای "Backend" خودداری کنید

با توجه به اصل جداسازی وابستگی‌ها (Separation of Concerns) وظیفه‌ی کنترلر‌ها و دیگر کدهای backend رندر کردن HTML نیست. (ساده سازی کنترلر ها) البته در نظر داشته باشید که قطعا تولید HTML در متد‌های کمکی کلاس هایی که "تنها" وظیفه‌ی آنها کمک به View‌ها جهت تولید کد هست ایرادی ندارد. این کلاس‌ها بخشی از View در نظر گرفته می‌شوند نه کدهای "backend".


در View‌ها "Business logic" انجام ندهید

معکوس بند قبلی هم کاملا صدق می‌کند ، منظور این است که View‌ها تا جایی که ممکن است باید حاوی کمترین Business logic ممکن باشند. در واقع تمرکز View‌ها باید استفاده و نحوه‌ی نمایش داده ای که برای آن‌ها فراهم شده باشد نه انجام عملیات روی آن.


استفاده از Presentation Model را به استفاده مستقیم از Business Object‌ها ترجیح دهید

در مطالب مختلف وب سایت اشاره به اهمین ViewModel‌ها شده است. برای اطلاعات بیشتر بند ج آموزش 11 از سری آموزش‌های ASP.NET MVC را مطالعه بفرمایید.


If‌های شرطی را در View‌ها را در متد‌های کمکی کپسوله کنید

استفاده از شرط‌ها در View کار توسعه دهنده را برای یک سری اعمال ساده می‌کند اما می‌تواند باعث کمی کثیف کاری هم شود. مثلا:
@if(Model.IsAnonymousUser) {
    <img src="@Url.Content("~/content/images/anonymous.jpg")" />
} else if(Model.IsAdministrator) {
    <img src="@Url.Content("~/content/images/administrator.jpg")" />
} else if(Model.Membership == Membership.Standard) {
    <img src="@Url.Content("~/content/images/member.jpg")" />
} else if(Model.Membership == Membership.Preferred) {
    <img src="@Url.Content("~/content/images/preferred_member.jpg")" />
}
می‌توان این کد که تا حدودی شامل منطق تجاری هم هست را در یک متد کمکی کپسوله کرد :
public static string UserAvatar(this HtmlHelper<User> helper)
{
    var user = helper.ViewData.Model;
    string avatarFilename = "anonymous.jpg";
    if (user.IsAnonymousUser)
    {
        avatarFilename = "anonymous.jpg";
    }
    else if (user.IsAdministrator)
    {
        avatarFilename = "administrator.jpg";
    }
    else if (user.Membership == Membership.Standard)
    {
        avatarFilename = "member.jpg";
    }
    else if (user.Membership == Membership.Preferred)
    {
        avatarFilename = "preferred_member.jpg";
    }
    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
    var contentPath = string.Format("~/content/images/{0}", avatarFilename)
    string imageUrl = urlHelper.Content(contentPath);
    return string.Format("<img src='{0}' />", imageUrl);
}
اکنون برای نمایش آواتار کاربر به سادگی می‌توان  نوشت :
@Html.UserAvatar()
به این ترتیب کد ما تمیز‌تر شده ، قابلیت نگهداری آن بالاتر رفته ، منطق تجاری یک بار و در یک قسمت نوشته شده از این کد در جاهای مختلف سایت می‌توان استفاده کرد و اگر لازم به تغییر باشد با تغییر در یک قسمت همه جا اعمال می‌شود.

منتظر قسمت بعدی باشید.
 
نظرات مطالب
بهبود SEO در ASP.NET MVC
نکته‌ای مهم برای کاهش SEO جماعت کپی پیست کار
اگر محتوای سایت شما عینا در سایت‌های دیگر کپی پیست شده، چگونه می‌توانید به گوگل اعلام کنید که محتوای اصلی کدام است؟ کدام یکی کپی است و کدام یک متعلق به شما است؟ برای این منظور باید ذیل مطالب خودتان تنظیمات مربوط به گوگل پلاس خود را نیز قرار دهید:
 <a rel="author" href="https://plus.google.com/your_g_plus_id?rel=author">G+</a>
سپس در گوگل پلاس خود اعلام کنید که در این سایت مشارکت دارید (در قسمت Contributor to).
مطالب
React 16x - قسمت 24 - ارتباط با سرور - بخش 3 - نکات تکمیلی کار با Axios
پس از آشنایی با مقدمات کار با Axios، در این قسمت امکانات پیشرفته‌تر آن‌را مانند خطایابی سراسری، interceptors  و ... بررسی می‌کنیم.


به روز رسانی‌های خوشبینانه‌ی UI

پیاده سازی اعمال CRUD توسط Axios در قسمت قبل، به همراه یک مشکل مهم است: اعمال کار با شبکه و سرور، زمانبر هستند و مدتی طول می‌کشد تا پاسخ عملیات از سمت سرور دریافت شود. در این بین اگر خطایی رخ دهد، مابقی کدهای نوشته شده‌ی در متدهایی مانند Update و Delete، اجرا نمی‌شوند. به این حالت «به روز رسانی بدبینانه‌ی UI» گفته می‌شود. در حالت خوشبینانه، فرض بر این است که در اکثر موارد، فراخوانی سرور با موفقیت به پایان می‌رسد. در یک چنین حالتی، ابتدا UI به روز رسانی می‌شود و سپس فراخوانی‌های سمت سرور صورت می‌گیرند. اگر این فراخوانی با شکست مواجه شد، مجددا UI را به حالت قبلی آن باز می‌گردانیم:
  handleDelete = async post => {
    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts });

    await axios.delete(`${apiEndpoint}/${post.id}`);
  };
در کدهای فوق، ابتدا UI به روز رسانی می‌شود (که بسیار سریع است)، سپس حذف سمت سرور صورت می‌گیرد. یک چنین پیاده سازی، به کاربر حس کار با یک برنامه‌ی بسیار سریع را القاء می‌کند؛ هرچند فراخوانی سمت سرور انجام شده، ممکن است مدتی طول بکشد.
اما اگر در این بین خطایی رخ داد، چه باید کرد؟ باید آخرین تغییر انجام شده را به حالت اول باز گرداند. انجام یک چنین کاری در React ساده‌است. چون ما state را به صورت مستقیم ویرایش نمی‌کنیم، همیشه می‌توان ارجاعی را به state قبلی، ذخیره و سپس در صورت نیاز آن‌را بازیابی کرد:
  handleDelete = async post => {
    const originalPosts = this.state.posts;

    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts }); // Optimistic Update

    try {
      await axios.delete(`${apiEndpoint}/${post.id}`);
    } catch (ex) {
      alert("An error occurred when deleting a post!");
      this.setState({ posts: originalPosts }); // Undo changes
    }
  };
در اینجا در ابتدا توسط متغیر originalPosts، ارجاعی را به وضعیت قبلی آرایه‌ی posts موجود در state (وضعیت ابتدایی UI)، نگهداری می‌کنیم. سپس کار حذف بسیار سریع آیتم درخواستی را از UI انجام می‌دهیم. اکنون کار حذف اصلی رکورد را از سرور، درون یک try/catch انجام خواهیم داد. اگر خطایی رخ دهد، پیامی را به کاربر نمایش داده و سپس مجددا state را به همان originalPosts پیشین، باز خواهیم گرداند.


مدیریت خطاهای رخ داده‌ی در حین فراخوانی سرور

تا اینجا مشاهده کردیم که یک روش مدیریت خطاها در کدهای Axios، قرار دادن آن‌ها در یک قطعه کد try/catch است. در اینجا نیز باید بتوان بین خطاهای پیش بینی شده و نشده، تفاوت قائل شد.
- خطاهای پیش بینی شده: برای مثال اگر درخواست حذف رکوردی را دادیم که در بانک اطلاعاتی موجود نیست، انتظار داریم سرور، خطای 404 یا return NotFound را بازگشت دهد و یا 400 که معادل bad request است و در حالت ارسال داده‌هایی غیرمعتبر، رخ می‌دهد. در این موارد بهتر است خطاهایی خاص را به کاربران نمایش داد؛ برای مثال رکورد درخواستی وجود ندارد یا پیشتر حذف شده‌است.
- خطاهای پیش بینی نشده: این نوع خطاها نباید و یا قرار نیست در شرایط عادی رخ دهند. برای مثال اگر شبکه در دسترس نیست، امکان ارتباط با سرور نیز میسر نخواهد بود و یا حتی ممکن است خطایی در کدهای سمت سرور، سبب بروز خطایی شده باشد. این نوع خطاها ابتدا باید لاگ شوند تا با بررسی‌های آتی آن‌ها، بتوان مشکلات پیش بینی نشده را بهتر برطرف کرد. همچنین در یک چنین مواردی، باید یک پیام خطای خیلی عمومی را به کاربر نمایش داد؛ برای مثال «یک خطای پیش بینی نشده رخ داده‌است.».

برای مدیریت این دو حالت باید به جزئیات شیء ex، در بدنه‌ی catch، دقت کرد که دارای دو خاصیت request و response است. اگر ex.response تنظیم شده بود، یعنی دریافت خروجی از سرور موفقیت آمیز بوده‌است. اگر سرور در دسترس نباشد و یا برنامه‌ی سمت سرور کرش کرده باشد، ex.response نال خواهد بود. اگر ex.request نال نبود، یعنی ارسال درخواست به سمت سرور با موفقیت انجام شده‌است. برای مثال جهت بررسی خطای مورد انتظار 404، می‌توان در قسمت catch(ex) به صورت زیر عمل کرد:
try {
  await axios.delete(`${apiEndpoint}/${post.id}`);
} catch (ex) {
  if (ex.response && ex.response.status === 404) {
     alert("This post has already been deleted!");
  } else {
     console.log("Error", ex);
     alert("An unexpected error occurred when deleting a post!");
  }

  this.setState({ posts: originalPosts }); // Undo changes
}
در اینجا ابتدا بررسی می‌شود که آیا شیء response نال است یا خیر؟ سپس خاصیت status آن‌را برای بررسی خطاهای پیش بینی شده، بررسی می‌کنیم. خطایی که در اینجا نمایش داده می‌شود، اختصاصی‌تر است. در غیراینصورت، ابتدا باید این خطا لاگ شود و سپس یک اخطار عمومی نمایش داده می‌شود. پس از بررسی هر دو حالت، باید UI را مجددا به حالت اول آن بازگشت داد.
عموما خطاهای پیش‌بینی شده را لاگ نمی‌کنیم؛ چون ممکن است کاربر، یک صفحه را در چندین برگه باز کرده باشد و در یکی، رکوردی را حذف کند. در این حال، این رکورد هنوز در برگه‌های دیگر موجود است و اگر مجددا درخواست حذف آن‌را صادر کند، مشکل خاصی از دیدگاه برنامه رخ نداده‌است و نیازی به پیگیری‌های آتی را ندارد. یعنی صرفا یک client error است.


مدیریت سراسری خطاهای رخ داده‌ی در حین فراخوانی سرور

برای مدیریت خطاها، نیاز است یک چنین try/catchهایی را در تمام قسمت‌های برنامه که با سرور کار می‌کنند، قرار دهیم. برای کاهش این کدهای تکراری، از interceptors کتابخانه‌ی Axios استفاده می‌شود. در این کتابخانه می‌توان در جاهائیکه درخواستی به سمت سرور ارسال می‌شود و یا پاسخی از سمت سرور دریافت می‌شود، قطعه کدهایی سراسری را قرار داد و بر روی درخواست و یا پاسخ، تغییراتی را اعمال کرد و یا حتی اطلاعات مربوطه را لاگ کرد؛ به این نوع قطعه کدها، interceptor گفته می‌شود و برای تعریف آن‌ها می‌توان از axios.interceptors.request و یا axios.interceptors.response، خارج از کلاس جاری استفاده کرد. برای مثال بر روی شیء axios.interceptors.response، می‌توان متد use را فراخوانی کرد که دو پارامتر را که هر کدام یک callback function هستند، می‌پذیرد. اولی در صورت موفقیت آمیز بودن response فراخوانی می‌شود و دومی در صورت شکست آن. اگر نیازی به هر کدام نبود، می‌توان آن‌را به null مقدار دهی کرد. اگر مدیریت قسمت شکست علمیات مدنظر است، نیاز خواهد بود در پایان این callback function، یک Rejected Promise را بازگشت داد تا ادامه‌ی برنامه، به درستی مدیریت شود. در این حالت اگر خطایی رخ دهد، ابتدا این interceptor فراخوانی می‌شود و سپس کنترل به بدنه‌ی catch منتقل خواهد شد:
import "./App.css";

import axios from "axios";
import React, { Component } from "react";

axios.interceptors.response.use(null, error => {
  console.log("interceptor called.");
  return Promise.reject(error);
});

const apiEndpoint = "https://localhost:5001/api/posts";
class App extends Component {
اکنون می‌خواهیم قطعه کد نمایش خطاهای عمومی پیش بینی نشده را از تمام بدنه‌های catch حذف کرده و به یک interceptor منتقل کنیم:
axios.interceptors.response.use(null, error => {
  const expectedError =
    error.response &&
    error.response.status >= 400 &&
    error.response.status < 500;

  if (!expectedError) {
    console.log("Error", error);
    alert("An unexpected error occurred when deleting a post!");
  }

  return Promise.reject(error);
});
خطاهای پیش بینی شده عموما در بازه‌ی 400 تا 500 قرار دارند. به همین جهت اگر یک چنین خطاهایی را دریافت کردیم، اخطاری را نمایش نداده و صرفا کنترل را به catch block منتقل می‌کنیم. اما اگر خطا، پیش بینی نشده بود، کار لاگ کردن خطا و همچنین نمایش اخطار را در اینجا انجام خواهیم داد.

یک نکته: استفاده از try/catchها فقط برای بازگشت UI به حالت قبلی و یا نمایش خطایی خاص به کاربر توصیه می‌شوند. اگر از روش «به روز رسانی‌های خوشبینانه‌ی UI» استفاده نمی‌کنید و همچنین خطاهای ویژه‌ای بجز خطای عمومی لاگ شده‌ی در interceptor فوق مدنظر شما نیست، نیازی هم به try/catch نخواهد بود و پس از بروز خطا، قسمت‌های بعدی کد اجرا نمی‌شوند؛ اما خطای عمومی فوق نمایش داده خواهد شد.


ایجاد یک HTTP Service با قابلیت استفاده‌ی مجدد

تا اینجا تعریف interceptor را پیش از کلاس کامپوننت جاری قرار داده‌ایم که هم سبب شلوغی این ماژول شده‌است و هم در صورت نیاز به آن در سایر برنامه‌ها، باید همین قطعه کد را مجددا در آن‌ها کپی کرد. به همین جهت پوشه‌ی جدید src\services را ایجاد کرده و سپس فایل src\services\httpService.js را در آن با محتوای زیر ایجاد می‌کنیم:
import axios from "axios";

axios.interceptors.response.use(null, error => {
  const expectedError =
    error.response &&
    error.response.status >= 400 &&
    error.response.status < 500;

  if (!expectedError) {
    console.log("Error", error);
    alert("An unexpected error occurred when deleting a post!");
  }

  return Promise.reject(error);
});

export default {
  get: axios.get,
  post: axios.post,
  put: axios.put,
  delete: axios.delete
};
در اینجا علاوه بر انتقال interceptor تعریف شده، کار export متدهای axios نیز به صورت یک شیء جدید صورت گرفته‌است.
سپس به app.js مراجعه کرده و این ماژول را با یک نام دلخواه import می‌کنیم:
import http from "./services/httpService";
در ادامه هرجائیکه ارجاعی به axios وجود دارد، آن‌را با http فوق جایگزین می‌کنیم. در این حالت می‌توان "import axios from "axios را نیز از ابتدای app.js حذف کرد. مزیت اینکار، مخفی کردن Axios، در پشت صحنه‌ی ماژول جدیدی است که ایجاد کردیم. به این ترتیب اگر در آینده خواستیم، Axios را با کتابخانه‌ی دیگری جایگزین کنیم، در کل برنامه تنها نیاز است این httpService.js جدید را تغییر دهیم.


ایجاد یک ماژول Config

بهبود دیگری را که می‌توانیم اعمال کنیم، انتقال const apiEndpoint تعریف شده، به یک ماژول مجزا است؛ تا اگر نیاز به استفاده‌ی از آن در قسمت‌های دیگری نیز وجود داشت، به سادگی بتوان آن‌را مدیریت کرد. به همین جهت فایل جدید src\config.json را با محتوای زیر ایجاد می‌کنیم:
{
   "apiEndpoint" : "https://localhost:5001/api/posts"
}
سپس به فایل app.js بازگشته و ابتدا const apiEndpoint را حذف و سپس import زیر را به ابتدای فایل، اضافه می‌کنیم:
import config from "./config.json";
اکنون هر جائی در کدهای خود که apiEndpoint را داریم، تبدیل به config.apiEndpoint می‌کنیم.


نمایش بهتر خطاها به کاربر توسط کتابخانه‌ی react-toastify

بجای alert توکار مرورگرها، می‌توان یک صفحه‌ی دیالوگ زیباتر را برای نمایش خطاها درنظر گرفت. به همین جهت ابتدا کتابخانه‌ی react-toastify را نصب می‌کنیم:
> npm i react-toastify --save
سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />
اکنون به src\services\httpService.js مراجعه کرده و alert آن‌را به صورت زیر تغییر می‌دهیم:
import { toast } from "react-toastify";
// ...

axios.interceptors.response.use(null, error => {
  // ...
  if (!expectedError) {
    // ...
    toast.error("An unexpected error occurrred.");
  }
ابتدا، شیء toast آن import می‌شود و سپس توسط این شیء می‌توان از متد error آن، جهت نمایش خطاهایی شکیل‌تر استفاده کرد؛ با این خروجی:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-backend-part-03.zip و sample-22-frontend-part-03.zip