مطالب
حذف جوین‌های اضافی به یک جدول در Entity framework
تذکر: این مطلب و نکته برای تا EF 6.1.3 تهیه شده‌است و ممکن است در نگارش‌های آتی آن وجود نداشته یا برطرف شده‌باشد.

کوئری ذیل را در نظر بگیرید:
var productsList1 = ctx.Products.Where(product => product.Id > 1)
    .Include(product => product.Category)
    .Include(product => product.User)
    .Where(
        product =>
            product.Category.Title.Contains("t") && product.Category.Id > 1 && product.Price > 100)
    .OrderBy(product => product.Price)
    .ToList();
به نظر شما این کوئری چند Join را ایجاد می‌کند؟
احتمالا شاید عنوان کنید که به ازای هر Include یک join خواهیم داشت. بنابراین دو جوین به جداول کاربران و گروه‌های محصول‌ها ایجاد می‌شود.

اما ... در واقعیت این کوئری را تولید می‌کند:
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Price] AS [Price],
    [Extent1].[CategoryId] AS [CategoryId],
    [Extent1].[UserId] AS [UserId],
    [Extent3].[Id] AS [Id1],
    [Extent3].[Name] AS [Name1],
    [Extent3].[Title] AS [Title],
    [Extent3].[UserId] AS [UserId1],
    [Extent4].[Id] AS [Id2],
    [Extent4].[Name] AS [Name2]
    FROM    [dbo].[Products] AS [Extent1]
    INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[Id]
    LEFT OUTER JOIN [dbo].[Categories] AS [Extent3] ON [Extent1].[CategoryId] = [Extent3].[Id]
    LEFT OUTER JOIN [dbo].[Users] AS [Extent4] ON [Extent1].[UserId] = [Extent4].[Id]
    WHERE ([Extent1].[Id] > 1) AND ([Extent2].[Title] LIKE N'%t%') AND ([Extent1].[CategoryId] > 1) AND ([Extent1].[Price] > 100)
    ORDER BY [Extent1].[Price] ASC
اگر به قسمت جوین‌های آن دقت کنید دوبار جوین به جدول Categories را می‌توانید مشاهده کنید.
این دو جوین حاصل یکبار Include جدول Categories و یکبار استفاده از navigation property آن در قسمت where است.

این باگ در اینجا گزارش شده، ولی به نظر هنوز برطرف نشده‌است یا مجددا ظاهر شده‌است.

برای رفع آن در حال حاضر بهترین راه حل استفاده از روش ذیل است:
var query2 = from product in ctx.Products
             let category = product.Category
             where product.Id > 1
             where category.Title.Contains("t") && category.Id > 1 && product.Price > 100
             select new { product, category }; 
var productsList2 = query2.ToList();
در اینجا قسمت Include کلا حذف شده و همچنین گروه‌ها توسط یک متغیر موقتی که با let ایجاد شده‌است، استفاده می‌شود. خروجی آن کوئری ذیل است:
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Price] AS [Price],
    [Extent1].[CategoryId] AS [CategoryId],
    [Extent1].[UserId] AS [UserId],
    [Extent2].[Id] AS [Id1],
    [Extent2].[Name] AS [Name1],
    [Extent2].[Title] AS [Title],
    [Extent2].[UserId] AS [UserId1]
    FROM  [dbo].[Products] AS [Extent1]
    INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[Id]
    WHERE ([Extent1].[Id] > 1) AND ([Extent2].[Title] LIKE N'%t%') AND ([Extent2].[Id] > 1) AND ([Extent1].[Price] > 100)
همانطور که مشاهده می‌کنید، اینبار فقط یکبار جوین به جدول گروه‌ها تشکیل شده‌است.

چند نکته:
-در کوئری let دار، اگر در قسمت select نهایی فقط product ذکر شود، هرچند جوین به جدول گروه‌ها تشکیل می‌شود اما فیلدهای این جدول انتخاب نخواهند شد.
-معادل کوئری LINQ نوشته شده را اگر بخواهیم توسط متدهای الحاقی بازنویسی کنیم، به کوئری ذیل خواهیم رسید:
var query2ChainedVersion = ctx.Products
     .Select(product => new { product, category = product.Category })
     .Where(@t => @t.product.Id > 1)
     .Where(@t => @t.category.Title.Contains("t") && @t.category.Id > 1 && @t.product.Price > 100)
     .Select(@t => new { @t.product , @t.category });

اگر علاقمند به آزمایش این باگ هستید، کدهای کامل آن‌را از اینجا می‌توانید دریافت کنید:
  Sample38.zip
مطالب
ساخت رابط کاربری برای الکترون
قدرت الکترون برگرفته از فناوری وب است و هر آنچه که در آنجا امکان پذیر باشد، در اینجا نیز امکان پذیر است و خصوصیت برنامه‌های دسکتاپ را نیز داراست. الکترون به دلیل بارگذاری فایل‌های html، به شما اجازه می‌دهد تا از ابزارهایی چون بوت استرپ و فریمورک‌ها و کیت‌های مشابهی چون جی‌کوئری و انگیولار، امبر Ember و ... در آن استفاده کنید. ولی با این حال، الکترون نوپا سعی دارد کیت‌های اختصاصی خودش را هم داشته باشد، که در این مقاله به آن‌ها اشاره می‌کنیم.
یکی از این کیت‌ها، فوتون نام دارد. فوتون شامل یک سری css ,sass، فونت و قالب‌های html است که به شما اجازه می‌دهد تا یک برنامه با ظاهری شبیه به برنامه‌های مک را داشته باشید. کامپوننت‌های فوتون شامل tab ها، لیست‌ها، منوی کناری، دکمه‌های معمولی یا جعبه ابزاری و کنترل‌های فرم‌ها می‌شود. با این حال اگر هم دوست ندارید که از این کامپوننت‌ها استفاده کنید، می‌توانید از layout ‌های آن استفاده کند که پنجره‌ی شما را تقسیم بندی می‌کنند.
برای استفاده از فوتون لازم است آن را دانلود کرده و فایل‌های آن را در پروژه‌ی خود کپی کنید. دایرکتوری dist آن شامل یک مثال می‌شود که می‌توانید آن را به داخل دایرکتوری پروژه خود کپی کنید؛ یا خود این دایرکتوری را به عنوان دایرکتوری پروژه تعیین کنید. بعد از آن، فایل‌های موجود در دایرکتوری template را به داخل دایرکتوری والد، یعنی dist انتقال دهید و داخل فایل html، مسیر فایل css را تصحیح نمایید. فقط می‌ماند که الکترون را بر روی این محل نصب کنید، یا اینکه الکترون نصب شده‌ی به صورت عمومی (Global) را در اختیار آن قرار دهید.
بعد از آن ممکن است با خطا مواجه شوید و وقتی فایل اصلی را که در اینجا نام آن app.js است، باز کنید، خطوط زیر را می‌بینید:
var app=require('app');
var BrowserWindow=require('browser-window');
این نوع استفاده از ماژول‌های داخلی، متعلق به نسخه‌های اولیه است و در نسخه‌های اخیر پشتیبانی نمی‌شود. پس بهتر است این خطوط را به صورت‌هایی که قبلا گفته‌ایم تغییر دهید.
سپس برنامه را اجرا کنید تا رابط جدید کاربری را ببینید.
فقط یک مشکلی هست و آن هم این است که باید فریم یا پنجره‌ای را که خود الکترون تولید می‌کند، حذف کنیم برای حذف آن می‌توانید از خصوصیت frame در شیء Browser Window استفاده کنید:
if(process.platform=='darwin')
{
  mainWindow= new BrowserWindow({
    width: 1000,
    height: 500,
    'min-width': 1000,
    'min-height': 500,
    'accept-first-mouse': true,
    'title-bar-style': 'hidden',
    titleBarStyle:'hidden'
  });
}
else {
  new BrowserWindow({
   width: 1000,
   height: 500,
   'min-width': 1000,
   'min-height': 500,
   frame:false
 });
}
در نسخه‌های 10 به بعد مک، از آنجاکه این خصوصیت، نه تنها فریم کرومیوم را حذف میکند، بلکه قابلیت‌هایی چون تغییر اندازه و ... را از آن نیز می‌گیرد، برای همین خصوصیت titleBarStyle را که به دو شکل هم می‌تواند نوشته شود، مورد استفاده قرار می‌دهیم.
حالا اگر برنامه را مجددا اجرا کنید، می‌بینید که قاب‌های دور آن حذف شده‌اند، ولی با چند ثانیه کار کردن متوجه این ایراد می‌شوید که پنجره قابل درگ کردن و جابجایی نمی‌باشد. برای حل آن باید از css کمک بگیریم:
-webkit-app-region: drag
دستور بالا را به هر المانی انتساب دهید، آن المان و فرزندانش قابل درگ خواهند بود، ولی اگر المانی را با این خصوصیت تنظیم کردید، ولی قصد دارید که یکی یا چند عدد از المان‌های فرزند این خاصیت را نداشته باشند، این دستور را به آنان انتساب دهید:
-webkit-app-region: no-drag;
از آنجاکه در این رابط کاربری، نوار عنوان تگ مشخصی دارد:
<header class="toolbar toolbar-header" >
با اضافه کردن این دستور css می‌توانید به آن قابلیت درگ را بدهید:
<header class="toolbar toolbar-header" style="-webkit-app-region: drag">
حالا مجددا برنامه را تست کنید تا نتیجه کار را ببینید.
همانطور که می‌بینید با کمترین زحمت، به چنین رابط کاربری رسیدید. تصویر زیر متعلق به برنامه‌ای است که در دو قسمت قبلی (+ + ) ساختیم و حالا با استفاده از این پکیج، ظاهر آن را تغییر داده‌ایم:



Electron UI Kit
دومین رابط کاربری که معرفی میکنیم در واقع یک کیت از یک سری کامپوننت است که بسیار شبیه به برنامه‌های دسکتاپ طراحی شده و شامل لیست‌ها، گریدها، کنترل‌ها و ... است که در دو فایل استایل، برای ویندوز و مک، مجزا شده‌اند.

Maverix

یک استایل تحت وب به نام Maverix است که البته در مورد برنامه‌های دسکتاپ و الکترون حرفی نزده و خود را فریمورکی برای استفاده در برنامه‌های تحت وب معرفی کرده است. ولی از آنجا که کنترل‌های موجود آن بر اساس سیستم عامل مک ایجاد شده‌اند، به راحتی می‌توانند خود را بجای برنامه‌های دسکتاپ جا بزنند. می‌توانید دموی آن را نیز ببینید.

مطالب
مقدمه‌ای بر داکر، قسمت سوم
در قسمت قبلی با Volume آشنا شدیم و نحوه‌ی اجرا کردن یک Source Code را درون Container یاد گرفتیم. در این قسمت میخواهیم یک Image شخصی ساخته، آن‌را اجرا و درون Docker hub ارسال نماییم.


Dockerfile چیست؟
Dockerfile عملا چیزی بیشتر از یک دستور العمل از نوع متنی برای build و ساخت یک docker image از آن نمیباشد. ضمن اینکه مراحل build شدن، cache شده و build‌های بعدی با سرعت خیلی بیشتری اجرا خواهند شد. بعد از نوشتن چند dockerfile متوجه خواهیم شد که مراحلش بسیار ساده است.
ساخت اولین Dockerfile
قبل از ساخت dockerfile، مثل جلسه‌ی قبل یک پروژه‌ی ساده‌ی nodejsی را با فایل index.js میسازیم:
const express = require('express')
const app = express()
const PORT = 3000;
app.get('/', (req, res) => {
  res.send('Hello World')
})
app.listen(PORT, () => {
  console.log(`listening on port ${PORT}!`)
})
درون  package.json هم این قسمت را تغییر میدهیم:
"scripts": {
    "start": "node index"
  },
حال فایل Dockerfile را ساخته و دستورالعمل‌های زیر را درون آن مینویسیم:
FROM node
ENV NODE_ENV=production
COPY . /var/www
WORKDIR /var/www
RUN npm i
EXPOSE 3000
ENTRYPOINT npm start
توضیحات دستورات فوق
1) FROM node یک imageی است که برنامه‌ی شما از آن استفاده میکند.
2) از environment variable استفاده کرده و نوع آن را روی production میگذاریم.
3) COPY کردن تمام فایل‌های دایرکتوری جاری پروژه درون فایل سیستم container به آدرس فوق.
4) عوض کردن work directory روی آدرسی که پروژه کپی شده است.
5) اجرا کردن دستور npm i برای نصب شدن Dependencies‌های پروژه.
6) EXPOSE کردن یک port برای ایجاد دسترسی.
7) نقطه‌ی شروع برنامه و دستور npm start که درون package.json قبل تعریف نموده بودیم.
 
بعد از ساخت Dockerfile فوق نوبت به build گرفتن از آن میرسد که با استفاده از دستور زیر میباشد:
docker build -f Dockerfile -t alikhll/testnode1 .
نکته: اگر image node را روی سیستم خود نداشته باشید ابتدا بصورت خودکار آن را pull مینماید.
1) پرچم f- که برای شناساندن فایل Dockefile میباشد، بدلیل این است که نام این فایل قابل تغییر میباشد.
2) پرچم t- برای نام image ساخته شده میباشد. همچنین . نیز به دایرکتوری جاری اشاره میکند.
بعد از ساخته شدن image با استفاده از دستور docker images میتوانید آن را مشاهده نمایید.
برای اجرای image نیز از دستور زیر استفاده میکنیم:
docker run -d -p 8080:3000 alikhll/testnode1
حال با استفاده از port 8080 میتوانید اپلیکیشن را اجرا نمایید.
از آنجایی که اکثر خوانندگان این مجموعه برنامه نویسان دات نت هستند یک Dockerfile دات نتی نیز برای تسلط بیشتر مینویسیم.
ابتدا دستورات زیر را درون ترمینال خود وارد کرده و یک پروژه‌ی وب از نوع Net Core. را میسازیم:
dotnet new web
dotnet restore
dotnet run
حال روی localhost قابلیت اجرا خواهد داشت؛ اما میخواهیم این app را بر روی container اجرا کنیم. بنابراین Dockerfile را اینگونه مینویسیم:
FROM microsoft/dotnet
ENV ASPNETCORE_URLS http://*:3000
COPY . /var/www
WORKDIR /var/www
RUN dotnet restore
EXPOSE 3000
ENTRYPOINT dotnet run
همه چیز خیلی شبیه به داکرفایل قبلی است، با این تفاوت که از ایمیج microsoft/dotnet استفاده کرده‌ایم (از imageهای سبکتری برای محیط production استفاده میکنیم! ضمن اینکه image فوق از Debian استفاده میکند. image جدیدی روی توزیع Alpine ایجاد شده است که حجم خیلی پایینی داشته و برای مطالعه بیشتر به اینجا رجوع کنید).
نکته‌ی مهم ASPNETCORE_URLS میباشد چون میخواهیم بتوانیم از خارج از محیط container با استفاده از IP، به آن دسترسی داشته باشیم (محیط local نیست).
 دستورات زیر را برای build و اجرا وارد مینماییم:
docker build -f Dockerfile -t alikhll/testasp1 .
docker run -d -p 8080:3000 alikhll/testasp1
اکنون app شما باید روی پورت خارجی 8080 قابل اجرا باشد.
نکته: من container قبلی nodejsی را stop کرده بودم وگرنه این پورت قابل استفاده‌ی مجدد نبود!
پابلیش کردن روی Docker Hub
انتشار دادن روی Docker hub ّبسیار ساده است. میتوانید یک اکانت به صورت رایگان ساخته و image‌های خود را بر روی آن انتشار دهید.
نکته: پروژه‌های تستی خود را میتوانید آنجا انتشار داده که البته قابلیت private بودن را ندارند. در صورتیکه برای یک پروژه‌ی واقعی که image‌های عمومی نیستند و فقط درون سازمان باید به آن دسترسی داشته باشند، میتوانید اکانت enterprise تهیه کرده و image‌های خود را درون آن مدیریت نمایید. همچنین از سرویس‌های ابری Azure, Amazon نیز برای انتشار دادن imageهای خصوصی میتوان استفاده نمود.
دستور زیر برای انتشار دادن imageی که ساختیم روی docker hub میباشد. ابتدا login کرده user/password را وارد کرده سپس push مینماییم:
docker login
docker push alikhll/testnode1
نکته: به جای alikhll باید username شخصی خود را وارد نمایید.
اکنون به راحتی با استفاده از دستور زیر روی یک ماشین دیگر که داکر روی آن نصب شده است، میتوانید image را pull کرده و اجرا نمایید:
docker pull alikhll/testnode1
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers
نکته تکمیلی
پر کردن مقدار SelectListItem سمت سرور با متود سفارشی :
public static class CommonExtensionMethods
{
    public static List<SelectListItem> CreateSelectListItem<T>(
        this List<T> items,
        object selectedItem = null,
        bool addChooseOneItem = true,
        string firstItemText = "انتخاب کنید",
        string firstItemValue = 0
    )
    {
        var modelType = items.First().GetType();

        var idProperty = modelType.GetProperty("Id");
        var titleProperty = modelType.GetProperty("Title");
        if (idProperty is null || titleProperty is null)
            throw new ArgumentNullException(
                $"{typeof(T).Name} must have ```Id``` and ```Title``` propeties");

        var result = new List<SelectListItem>();
        if (addChooseOneItem)
            result.Add(new SelectListItem(firstItemText, firstItemValue));
        foreach (var item in items)
        {
            var id = idProperty.GetValue(item)?.ToString();
            var text = titleProperty.GetValue(item)?.ToString();
            var selected = selectedItem?.ToString() == id;
            result.Add(new SelectListItem(text, id, selected));
        }

        return result;
    }
}  
نحوه استفاده :
مدلی که AllMainCategories برگشت میدهد:
public class ShowCategory
{
    public int Id { get; set; }

    public string Title { get; set; }
}
public async Task<IActionResult> Add()
{
    var categories = await _categoryService.AllMainCategories();
    ViewBag.MainCategories = categories.ToList().CreateSelectListItem(firstItemText: "خودش سر دسته باشد");
    return View();
}

[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Add(AddCategoryViewModel model)
{
    if (!ModelState.IsValid)
    {
        var categories = await _categoryService.AllMainCategories();
        ViewBag.MainCategories = categories.ToList()
            .CreateSelectListItem(model.ParentId, firstItemText: "خودش سر دسته باشد");
        ModelState.AddModelError(string.Empty, PublicConstantStrings.ModelStateErrorMessage);
        return View(model);
    }
    await _categoryService.AddAsync(new Category()
    {
        Title = model.Title,
        ParentId = model.ParentId == 0 ? null : model.ParentId
    });
    await _uow.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}
مطالب
معرفی فریم ورک Blueprint CSS
احتمالا با عباراتی مانند طراحی table less و مزیت‌های طراحی با CSS، همانند سرعت بالاتر بارگذاری سایت در مقایسه با نمایش یک جدول که نیازمند دریافت تمام جزئیات آن و سپس رندر نهایی اطلاعات آن توسط مرورگر است، بارها برخورد داشته‌اید. اما ... آیا یکبار سعی کرده‌اید که به صورت دستی همان کارهایی را که پیشتر با HTML table انجام می‌دادید، اینبار توسط CSS پیاده سازی کنید؟
در اکثر اوقات نتیجه کار مایوس کننده، بسیار سخت و نگهداری آن در طول زمان بسیار مشکل خواهد بود؛ به علاوه سازگاری با مرورگرهای مختلف و نکات ریز هر کدام را نیز لحاظ کنید. به همین جهت تعدادی فریم ورک CSS برای شبیه سازی گرید و جدول تهیه شده‌اند که کار طراحی table less را بسیار ساده و لذت بخش کرده‌اند. یکی از این موارد، فریم ورک Blueprint CSS نام دارد و در ادامه نحوه استفاده از آن‌را مرور خواهیم کرد. این مرور هم مستقل است از فناوری سمت سرور مورد استفاده و صرفا مباحث html و CSS آن بررسی خواهند شد.


دریافت Blueprint CSS

این فریم ورک سورس باز را از مخزن کدهای آن در GitHub می‌توانید دریافت کنید: (^)
البته نگران حجم نزدیک به 4 مگابایتی بسته دریافتی آن نباشید؛ زیرا نهایتا با سه فایل CSS از آن بیشتر کاری نداریم و مابقی مثال‌های آن هستند.
پس از دریافت آن، یک پوشه را به نام blueprint ایجاد کرده و سه فایل ie.css ،print.css و screen.css را در آن قرار دهید.
به علاوه داخل این پوشه، یک پوشه جدید دیگر را به نام src ایجاد کرده و فایل grid.png موجود در این بسته را نیز در آن کپی کنید.


ساختار ابتدایی یک صفحه مبتنی بر Blueprint CSS

پس از ایجاد پوشه blueprint و src به نحوی که توضیح داده شد، ابتدایی‌ترین ساختار یک صفحه تشکیل شده با blueprint css به نحو زیر است:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Blueprint test page</title>

    <!-- Framework CSS -->
    <link rel="stylesheet" href="blueprint/screen.css" type="text/css" media="screen, projection">
    <link rel="stylesheet" href="blueprint/print.css" type="text/css" media="print">
    <!--[if lt IE 8]><link rel="stylesheet" href="blueprint/ie.css" type="text/css" media="screen, projection"><![endif]-->
  </head>
  <body>
    <div class="container showgrid">
  test
  <hr class="space" />
  <hr class="space" />
  <hr class="space" />
  <hr class="space" />
  <hr class="space" />
  test
    </div>
  </body>
</html>
توضیحات:
پس از مشخص سازی DocType (مهم)، سه فایل CSS یاد شده به header صفحه اضافه خواهند شد. همانطور که ملاحظه می‌کنید، سازگاری با IE نیز مدنظر آن بوده است.
کار با blueprint css همواره داخل div زیر انجام می‌شود:
<div class="container">
  page
</div>
توسط کلاس container یک گرید به عرض 950px در میانه صفحه برای شما تشکیل خواهد شد.
اگر علاقمند باشید که این گرید را مشاهده نمائید و همچنین بتوانید ستون‌های آن‌را نیز شمارش کنید، تنها کافی است showgrid را به این class تعریف شده اضافه نمائید (همانند ساختار صفحه فوق). به این ترتیب شکل زیر نمایان خواهد شد:

مطابق شکل فوق، در این عرض مشخص، 24 ستون آن در اختیار ما خواهند بود.
به علاوه ذکر hr با class=space سبب خواهد شد تا مطابق تنظیمات و فاصله بندی منظم این فریم ورک، یک سطر خالی برای ما ایجاد شود.


طراحی بدون جدول با Blueprint CSS

در ادامه قصد داریم در این صفحه ابتدایی، یک جدول با دو ستون و دو ردیف را ایجاد کنیم:
  <body>
    <div class="container showgrid">
      <div class="span-12">
        row1-col1
      </div>
      <div class="span-12 last">
        row1-col2
      </div>
      <div class="span-12">
        row2-col1
      </div>
      <div class="span-12 last">
        row2-col2
      </div>
    </div>
  </body>
که شکل زیر را برای ما ایجاد خواهد کرد:

توضیحات:
ستون‌های گرید نهایی با رنگ آبی مشخص هستند (class=container showgrid). اگر نیاز به 12 ستون داریم، می‌نویسیم span-12 و ... همین! به این ترتیب یک سلول جدول، با 12 ستون در اختیار ما خواهد بود. سلول بعدی هم در اینجا 12 ستونه است. اما یک last را اضافه‌تر دارد. در span-12 last این last به معنای انتهای ردیف جاری است و ذکر آن الزامی است.
تا اینجا یک ردیف تمام شد. اکنون در ادامه ردیف دوم را نیز به همین ترتیب با دو div و class‌هایی که ملاحظه می‌کنید، مشخص خواهیم کرد.
نحوه کار کلی با Blueprint css به همین سادگی است که ملاحظه می‌کنید. تعداد ستون‌های مورد نیاز را با ذکر container showgrid به سادگی می‌توان شمارش کرد. سپس این اعداد شمارش شده و مد نظر را پس از span ذکر کنید. مثلا اگر یک طرح سه ستونه نیاز دارید به صورت زیر خواهد بود:
  <body>
    <div class="container showgrid">
      <div class="span-8">
        row1-col1
      </div>
      <div class="span-8">
        row1-col2
      </div>
      <div class="span-8 last">
        row1-col3
      </div>
    </div>
  </body>

طراحی سلول‌های تو در تو


سؤال: ما پیشتر در یک html table به سادگی می‌توانستیم داخل یک سلول آن حتی یک جدول جدید نیز قرار دهیم، اینجا چطور؟
پاسخ: در اینجا هم بجای td و tr و table، از divهای تو در تو استفاده کنید. بستن ستون آخر را با last یاد شده فراموش نکنید. مثلا:
  <body>
    <div class="container showgrid">
      <div class="span-8">
                <div class="span-4">
                    row-1, col1 : cell-1
                </div>
                <div class="span-4 last">
                    row-1, col1 : cell-2
                </div>
      </div>
      <div class="span-8">
        row1-col2
      </div>
      <div class="span-8 last">
        row1-col3
      </div>
    </div>
  </body>
در اینجا در اولین div تعریف شده دو div تو در تو اضافه شده‌اند. البته با توجه به اینکه div والد 8 ستونی است، جمع عرض divهای فرزند باید 8 باشد که در اینجا به دو div چهارستونی تقسیم شده است.


سایر امکانات Blueprint CSS

تا اینجا با کلیات نحوه طراحی یک جدول به کمک CSS و فریم ورک Blueprint CSS آشنا شدیم (به کمک container و span-n آن). در ادامه مرور سریعی خواهیم داشت بر سایر امکانات این فریم ورک CSS و منظور از این امکانات، کلمات و عبارات مجازی است که می‌توانید داخل classهای divهای تعریف شده اضافه نمائید (CSS selectors تعریف شده در آن):
prepend-n و border:
فرض کنید در divهای تو در توی قسمت قبل، قصد داریم عرض ستون اول را بجای 4 ستون به 3 ستون تبدیل کنیم، اما این div را یک ستون به سمت راست حرکت دهیم:
  <body>
    <div class="container showgrid">
      <div class="span-8">
        <div class="prepend-1 span-3 border">
               row-1, col1 : cell-1
        </div>
        <div class="span-4 last">
              row-1, col1 : cell-2
        </div>
      </div>
      <div class="span-8">
           row1-col2
      </div>
      <div class="span-8 last">
           row1-col3
      </div>
    </div>
  </body>
برای این منظور همانطور که ملاحظه می‌کنید از prepend-1 استفاده شده است. border در اینجا سبب خواهد شد تا در سمت راست div یک خط عمودی رسم شود. در مقابل آن colborder هم وجود دارد که سبب ترسیم حاشیه با فاصله بیشتری نسبت به border می‌شود.
شبیه به همین قابلیت، با append-x (افزودن تعدادی ستون به سمت راست)، prepend-top (فاصله‌ای به اندازه 1.5em را به بالای div اضافه می‌کند) و append-bottom (فاصله‌ای به اندازه 1.5em را به پایین div اضافه می‌کند) نیز وجود دارد.
در مقابل این‌ها، push-n و pull-n هم وجود دارند. کار append و prepend اضافه کردن چند ستون به بعد و قبل از یک div است. push یک div را به تعداد واحدی که مشخص می‌کنیم به سمت راست حرکت می‌دهد. pull یک div را n ستون به سمت چپ حرکت خواهد داد (بدون تغییری در تعداد ستون‌ها).


دریافت مرجع سریع Blueprint CSS
نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: روش تهیه‌ی ویژگی‌های سفارشی اعتبارسنجی، در برنامه‌های Blazor

اگر ویژگی‌های پیش‌فرض مهیا، پاسخگوی اعتبارسنجی مدنظر نبودند، می‌توان یک attribute سفارشی را تهیه کرد:
using System.ComponentModel.DataAnnotations;

namespace CustomValidators
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
    public class EmailDomainValidator : ValidationAttribute
    {
        public string AllowedDomain { get; set; }

        protected override ValidationResult IsValid(object value, 
            ValidationContext validationContext)
        {
            string[] strings = value.ToString().Split('@');
            if (strings[1].ToUpper() == AllowedDomain.ToUpper())
            {
                return null;
            }

            return new ValidationResult($"Domain must be {AllowedDomain}",
            new[] { validationContext.MemberName });
        }
    }
}
توضیحات:
- کار با ارث بری از کلاس پایه‌ی ValidationAttribute شروع می‌شود و باید متد IsValid آن‌را بازنویسی کرد.
- اگر متد IsValid، نال برگرداند، یعنی مشکلی نیست؛ در غیراینصورت خروجی آن باید از نوع ValidationResult باشد.
- پارامتر validationContext اطلاعاتی مانند نام خاصیت در حال بررسی را ارائه می‌دهد.
- در اینجا متد ()ValidationContext.GetService نال را بر می‌گرداند؛ یعنی فعلا از تزریق وابستگی‌ها در آن پشتیبانی نمی‌شود.

و در آخر روش استفاده‌ی از آن، همانند سایر ویژگی‌های اعتبارسنجی است:
public class Employee
{
    [EmailDomainValidator(AllowedDomain = "site.com")]
    public string Email { get; set; }
}
مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت سوم
پس از بررسی ساختار یک پروژه‌ی افزونه پذیر و همچنین بهبود توزیع فایل‌های استاتیک آن، اکنون نوبت به کار با داده‌ها است. هدف اصلی آن نیز داشتن مدل‌های اختصاصی و مستقل Entity framework code-first به ازای هر افزونه است و سپس بارگذاری و تشخیص خودکار آن‌ها در Context مرکزی برنامه.

پیشنیازها
- آشنایی با مباحث Migrations در EF Code first
- آشنایی با مباحث الگوی واحد کار
- چگونه مدل‌های EF را به صورت خودکار به Context اضافه کنیم؟
- چگونه تنظیمات مدل‌های EF را به صورت خودکار به Context اضافه کنیم؟


کدهایی را که در این قسمت مشاهده خواهید کرد، در حقیقت همان برنامه‌ی توسعه یافته «آشنایی با مباحث الگوی واحد کار» است و از ذکر قسمت‌های تکراری آن جهت طولانی نشدن مبحث، صرفنظر خواهد شد. برای مثال Context و مدل‌های محصولات و گروه‌های آن‌ها به همراه کلاس‌های لایه سرویس برنامه‌ی اصلی، دقیقا همان کدهای مطلب «آشنایی با مباحث الگوی واحد کار» است.


تعریف domain classes مخصوص افزونه‌ها

در ادامه‌ی پروژه‌ی افزونه پذیر فعلی، پروژه‌ی class library جدیدی را به نام MvcPluginMasterApp.Plugin1.DomainClasses اضافه خواهیم کرد. از آن جهت تعریف کلاس‌های مدل افزونه‌ی یک استفاده می‌کنیم. برای مثال کلاس News را به همراه تنظیمات Fluent آن به این پروژه‌ی جدید اضافه کنید:
using System.Data.Entity.ModelConfiguration;
 
namespace MvcPluginMasterApp.Plugin1.DomainClasses
{
    public class News
    {
        public int Id { set; get; }
 
        public string Title { set; get; }
 
        public string Body { set; get; }
    }
 
    public class NewsConfig : EntityTypeConfiguration<News>
    {
        public NewsConfig()
        {
            this.ToTable("Plugin1_News");
            this.HasKey(news => news.Id);
            this.Property(news => news.Title).IsRequired().HasMaxLength(500);
            this.Property(news => news.Body).IsOptional().IsMaxLength();
        }
    }
}
این پروژه برای کامپایل شدن نیاز به بسته‌ی نیوگت ذیل دارد:
 PM> install-package EntityFramework

مشکل! برنامه‌ی اصلی، همانند مطلب «آشنایی با مباحث الگوی واحد کار» دارای domain classes خاص خودش است به همراه تنظیمات Context ایی که صریحا در آن مدل‌های متناظر با این پروژه در معرض دید EF قرار گرفته‌اند:
public class MvcPluginMasterAppContext : DbContext, IUnitOfWork
{
    public DbSet<Category> Categories { set; get; }
    public DbSet<Product> Products { set; get; }
اکنون برنامه‌ی اصلی چگونه باید مدل‌ها و تنظیمات سایر افزونه‌ها را یافته و به صورت خودکار به این Context اضافه کند؟ با توجه به اینکه این برنامه هیچ ارجاع مستقیمی را به افزونه‌ها ندارد.


تغییرات اینترفیس Unit of work جهت افزونه پذیری

در ادامه، اینترفیس بهبود یافته‌ی IUnitOfWork را جهت پذیرش DbSetهای پویا و همچنین EntityTypeConfigurationهای پویا، ملاحظه می‌کنید:
namespace MvcPluginMasterApp.PluginsBase
{
    public interface IUnitOfWork : IDisposable
    {
        IDbSet<TEntity> Set<TEntity>() where TEntity : class;
        int SaveAllChanges();
        void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;
        IList<T> GetRows<T>(string sql, params object[] parameters) where T : class;
        IEnumerable<TEntity> AddThisRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
        void SetDynamicEntities(Type[] dynamicTypes);
        void ForceDatabaseInitialize();
        void SetConfigurationsAssemblies(Assembly[] assembly);
    }
}
متدهای جدید آن:
SetDynamicEntities : توسط این متد در ابتدای برنامه، نوع‌های مدل‌های جدید افزونه‌ها به صورت خودکار به Context اضافه خواهند شد.
SetConfigurationsAssemblies : کار افزودن اسمبلی‌های حاوی تعاریف EntityTypeConfigurationهای جدید و پویا را به عهده دارد.
ForceDatabaseInitialize: سبب خواهد شد تا مباحث migrations، پیش از شروع به کار برنامه، اعمال شوند.

در کلاس Context ذیل، نحوه‌ی پیاده سازی این متدهای جدید را ملاحظه می‌کنید:
namespace MvcPluginMasterApp.DataLayer.Context
{
    public class MvcPluginMasterAppContext : DbContext, IUnitOfWork
    {
        private readonly IList<Assembly> _configurationsAssemblies = new List<Assembly>();
        private readonly IList<Type[]> _dynamicTypes = new List<Type[]>(); 
 
        public void ForceDatabaseInitialize()
        {
            Database.Initialize(force: true);
        }
 
        public void SetConfigurationsAssemblies(Assembly[] assemblies)
        {
            if (assemblies == null) return;
 
            foreach (var assembly in assemblies)
            {
                _configurationsAssemblies.Add(assembly);
            }
        }
 
        public void SetDynamicEntities(Type[] dynamicTypes)
        {
            if (dynamicTypes == null) return;
            _dynamicTypes.Add(dynamicTypes);
        }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            addConfigurationsFromAssemblies(modelBuilder);
            addPluginsEntitiesDynamically(modelBuilder);
            base.OnModelCreating(modelBuilder);
        }
 
        private void addConfigurationsFromAssemblies(DbModelBuilder modelBuilder)
        {
            foreach (var assembly in _configurationsAssemblies)
            {
                modelBuilder.Configurations.AddFromAssembly(assembly);
            }
        }
 
        private void addPluginsEntitiesDynamically(DbModelBuilder modelBuilder)
        {
            foreach (var types in _dynamicTypes)
            {
                foreach (var type in types)
                {
                    modelBuilder.RegisterEntityType(type);
                }
            }
        }
    }
}
در متد استاندارد OnModelCreating، فرصت افزودن نوع‌های پویا و همچنین تنظیمات پویای آن‌ها وجود دارد. برای این منظور می‌توان از متدهای modelBuilder.RegisterEntityType و modelBuilder.Configurations.AddFromAssembly کمک گرفت.


بهبود اینترفیس IPlugin جهت پذیرش نوع‌های پویای EF

در قسمت اول، با اینترفیس IPlugin آشنا شدیم. هر افزونه باید دارای کلاسی باشد که این اینترفیس را پیاده سازی می‌کند. از آن جهت دریافت تنظیمات و یا ثبت تنظیمات مسیریابی و امثال آن استفاده می‌شود.
در اینجا متد GetEfBootstrapper آن کار دریافت تنظیمات EF هر افزونه را به عهد دارد.
namespace MvcPluginMasterApp.PluginsBase
{
    public interface IPlugin
    {
        EfBootstrapper GetEfBootstrapper();
        //...به همراه سایر متدهای مورد نیاز
    }
 
    public class EfBootstrapper
    {
        /// <summary>
        /// Assemblies containing EntityTypeConfiguration classes.
        /// </summary>
        public Assembly[] ConfigurationsAssemblies { get; set; }
 
        /// <summary>
        /// Domain classes.
        /// </summary>
        public Type[] DomainEntities { get; set; }
 
        /// <summary>
        /// Custom Seed method.
        /// </summary>
        public Action<IUnitOfWork> DatabaseSeeder { get; set; }
    } 
}
ConfigurationsAssemblies مشخص کننده‌ی اسمبلی‌هایی است که حاوی تعاریف EntityTypeConfigurationهای افزونه‌ی جاری هستند.
DomainEntities بیانگر لیست مدل‌ها و موجودیت‌های هر افزونه است.
DatabaseSeeder کار دریافت منطق متد Seed را بر عهده دارد. برای مثال اگر افزونه‌ای نیاز است در آغاز کار تشکیل جداول آن، دیتای پیش فرض و خاصی را در بانک اطلاعاتی ثبت کند، می‌توان از این متد استفاده کرد. اگر دقت کنید این Action یک وهله از IUnitOfWork را به افزونه ارسال می‌کند. بنابراین در این طراحی جدید، اینترفیس IUnitOfWork به پروژه‌ی MvcPluginMasterApp.PluginsBase منتقل می‌شود. به این ترتیب دیگر نیازی نیست تا تک تک افزونه‌ها ارجاع مستقیمی را به DataLayer پروژه‌ی اصلی پیدا کنند.


تکمیل متد GetEfBootstrapper در افزونه‌ها

اکنون جهت معرفی مدل‌ها و تنظیمات EF آن‌ها، تنها کافی است متد GetEfBootstrapper هر افزونه را تکمیل کنیم:
namespace MvcPluginMasterApp.Plugin1
{
    public class Plugin1 : IPlugin
    {
        public EfBootstrapper GetEfBootstrapper()
        {
            return new EfBootstrapper
            {
                DomainEntities = new[] { typeof(News) },
                ConfigurationsAssemblies = new[] { typeof(NewsConfig).Assembly },
                DatabaseSeeder = uow =>
                {
                    var news = uow.Set<News>();
                    if (news.Any())
                    {
                        return;
                    }
 
                    news.Add(new News
                    {
                        Title = "News 1",
                        Body = "news 1 news 1 news 1 ...."
                    });
 
                    news.Add(new News
                    {
                        Title = "News 2",
                        Body = "news 2 news 2 news 2 ...."
                    });
                }
            };
        }
در اینجا نحوه‌ی معرفی مدل‌های جدید را توسط خاصیت DomainEntities و تنظیمات متناظر را به کمک خاصیت ConfigurationsAssemblies مشاهده می‌کنید. باید دقت داشت که هر اسمبلی فقط باید یکبار معرفی شود و مهم نیست که چه تعداد تنظیمی در آن وجود دارند. کار یافتن کلیه‌ی تنظیمات از نوع EntityTypeConfigurationها به صورت خودکار توسط EF صورت می‌گیرد.
همچنین توسط delegate ایی به نام DatabaseSeeder، نحوه‌ی دسترسی به متد Set واحد کار و سپس استفاده‌ی از آن، برای تعریف متد Seed سفارشی نیز تکمیل شده‌است.


تدارک یک راه انداز EF، پیش از شروع به کار برنامه

در پوشه‌ی App_Start پروژه‌ی اصلی یا همان MvcPluginMasterApp، کلاس جدید EFBootstrapperStart را با کدهای ذیل اضافه کنید:
[assembly: PreApplicationStartMethod(typeof(EFBootstrapperStart), "Start")]
namespace MvcPluginMasterApp
{
    public static class EFBootstrapperStart
    {
        public static void Start()
        {
            var plugins = SmObjectFactory.Container.GetAllInstances<IPlugin>().ToList();
            using (var uow = SmObjectFactory.Container.GetInstance<IUnitOfWork>())
            {
                initDatabase(uow, plugins);
                runDatabaseSeeders(uow, plugins);
            }
        }
 
        private static void initDatabase(IUnitOfWork uow, IEnumerable<IPlugin> plugins)
        {
            foreach (var plugin in plugins)
            {
                var efBootstrapper = plugin.GetEfBootstrapper();
                if (efBootstrapper == null) continue;
 
                uow.SetDynamicEntities(efBootstrapper.DomainEntities);
                uow.SetConfigurationsAssemblies(efBootstrapper.ConfigurationsAssemblies);
            }
 
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MvcPluginMasterAppContext, Configuration>());
            uow.ForceDatabaseInitialize();
        }
 
        private static void runDatabaseSeeders(IUnitOfWork uow, IEnumerable<IPlugin> plugins)
        {
            foreach (var plugin in plugins)
            {
                var efBootstrapper = plugin.GetEfBootstrapper();
                if (efBootstrapper == null || efBootstrapper.DatabaseSeeder == null) continue;
 
                efBootstrapper.DatabaseSeeder(uow);
                uow.SaveAllChanges();
            }
        }
    }
}
در اینجا یک راه انداز سفارشی از نوع PreApplicationStartMethod تهیه شده‌است. Pre بودن آن به معنای اجرای کدهای متد Start این کلاس، پیش از آغاز به کار برنامه و پیش از فراخوانی متد Application_Start فایل Global.asax.cs است.
همانطور که ملاحظه می‌کنید، ابتدا لیست تمام افزونه‌های موجود، به کمک StructureMap دریافت می‌شوند. سپس می‌توان در متد initDatabase به متد GetEfBootstrapper هر افزونه دسترسی یافت و توسط آن تنظیمات مدل‌ها را یافته و به Context اصلی برنامه اضافه کرد. سپس با فراخوانی ForceDatabaseInitialize تمام این موارد به صورت خودکار به بانک اطلاعاتی اعمال خواهند شد.
کار متد runDatabaseSeeders، یافتن DatabaseSeeder هر افزونه، اجرای آن‌ها و سپس فراخوانی متد SaveAllChanges در آخر کار است.



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

ارزیابی در الگوریتم‌های دسته بندی 
برای سادگی معیارهای ارزیابی الگوریتم‌های دسته بندی، آنها را برای یک مسئله با دو دسته ارائه خواهیم نمود. در ابتدا با مفهوم ماتریس درهم ریختگی (Classification Matrix) آشنا می‌شویم. این ماتریس چگونگی عملکرد الگوریتم دسته بندی را با توجه به مجموعه داده ورودی به تفکیک انواع دسته‌های مساله دسته بندی، نمایش می‌دهد.

هر یک از عناصر ماتریس به شرح ذیل می‌باشد:
TN: بیانگر تعداد رکوردهایی است که دسته واقعی آنها منفی بوده و الگوریتم دسته بندی نیز دسته آنها را بدرستی منفی تشخیص داده است.
TP: بیانگر تعداد رکوردهایی است که دسته واقعی آنها مثبت بوده و الگوریتم دسته بندی نیز دسته آنها را بدرستی مثبت تشخیص داده است.
FP: بیانگر تعداد رکوردهایی است که دسته واقعی آنها منفی بوده و الگوریتم دسته بندی دسته آنها را به اشتباه مثبت تشخیص داده است.
FN: بیانگر تعداد رکوردهایی است که دسته واقعی آنها مثبت بوده و الگوریتم دسته بندی دسته آنها را به اشتباه منفی تشخیص داده است.

مهمترین معیار برای تعین کارایی یک الگوریتم دسته بندی دقت یا نرخ دسته بندی (Classification Accuracy - Rate) است که این معیار دقت کل یک دسته بند را محاسبه می‌کند. در واقع این معیار مشهورترین و عمومی‌ترین معیار محاسبه کارایی الگوریتم‌های دسته بندی است که نشان می‌دهد، دسته بند طراحی شده چند درصد از کل مجموعه رکوردهای آزمایشی را بدرستی دسته بندی کرده است.
دقت دسته بندی با استفاده از رابطه I بدست می‌آید که بیان می‌کند دو مقدار TP و TN مهمترین مقادیری هستند که در یک مسئله دودسته ای باید بیشینه شوند. (در مسائل چند دسته ای مقادیر قرار گرفته روی قطر اصلی این ماتریس - که در صورت کسر محاسبه CA قرار می‌گیرند - باید بیشینه باشند.)
معیار خطای دسته بندی (Error Rate) دقیقاً برعکس معیار دقت دسته بندی است که با استفاده از رابطه II بدست می‌آید. کمترین مقدار آن برابر صفر است زمانی که بهترین کارایی را داریم و بطور مشابه بیشترین مقدار آن برابر یک است زمانی که کمترین کارائی را داریم.
ذکر این نکته ضروری است که در مسائل واقعی، معیار دقت دسته بندی به هیچ عنوان معیار مناسبی برای ارزیابی کارایی الگوریتم‌های دسته بندی نمی‌باشد، به این دلیل که در رابطه دقت دسته بندی، ارزش رکوردهای دسته‌های مختلف یکسان در نظر گرفته می‌شوند. بنابراین در مسائلی که با دسته‌های نامتعادل سروکار داریم، به بیان دیگر در مسائلی که ارزش دسته ای در مقایسه با دسته دیگر متفاوت است، از معیارهای دیگری استفاده می‌شود.
همچنین در مسائل واقعی معیارهای دیگری نظیر DR و FAR که به ترتیب از روابط III و IV بدست می‌آیند، اهمیت ویژه ای دارند. این معیارها که توجه بیشتری به دسته بند مثبت نشان می‌دهند، توانایی دسته بند را در تشخیص دسته مثبت و بطور مشابه تاوان این توانایی تشخیص را تبیین می‌کنند. معیار DR نشان می‌دهد که دقت تشخیص دسته مثبت چه مقدار است و معیار FAR نرخ هشدار غلط را با توجه به دسته منفی بیان می‌کند.
 

معیار مهم دیگری که برای تعیین میزان کارایی یک دسته بند استفاده می‌شود معیار (AUC (Area Under Curve است.

AUC نشان دهنده سطح زیر نمودار (ROC (Receiver Operating Characteristic می‌باشد که هر چه مقدار این عدد مربوط به یک دسته بند بزرگتر باشد کارایی نهایی دسته بند مطلوب‌تر ارزیابی می‌شود. نمودار ROC روشی برای بررسی کارایی دسته بندها می‌باشد. در واقع منحنی‌های ROC منحنی‌های دو بعدی هستند که در آنها DR یا همان نرخ تشخیص صحیح دسته مثبت (True Positive Rate - TPR) روی محور Y و بطور مشابه FAR یا همان نرخ تشخیص غلط دسته منفی (False Positive Rate - FPR) روی محور X رسم می‌شوند. به بیان دیگر یک منحنی ROC مصالحه نسبی میان سودها و هزینه‌ها را نشان می‌دهد.

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

یک منحنی ROC اجازه مقایسه تصویری مجموعه ای از دسته بندی کننده‌ها را می‌دهد، همچنین نقاط متعددی در فضای ROC قابل توجه است. نقطه پایین سمت چپ (0,0) استراتژی را نشان می‌دهد که در یک دسته بند مثبت تولید نمی‌شود. استراتژی مخالف، که بدون شرط دسته بندهای مثبت تولید می‌کند، با نقطه بالا سمت راست (1,1) مشخص می‌شود. نقطه (0,1) دسته بندی کامل و بی عیب را نمایش می‌دهد. بطور کلی یک نقطه در فضای ROC بهتر از دیگری است اگر در شمال غربی‌تر این فضا قرار گرفته باشد. همچنین در نظر داشته باشید منحنی‌های ROC رفتار یک دسته بندی کننده را بدون توجه به توزیع دسته‌ها یا هزینه خطا نشان می‌دهند، بنابراین کارایی دسته بندی را از این عوامل جدا می‌کنند. فقط زمانی که یک دسته بند در کل فضای کارایی به وضوح بر دسته دیگری تسلط یابد، می‌توان گفت که بهتر از دیگری است. به همین دلیل معیار AUC که سطح زیر نمودار ROC را نشان می‌دهد می‌تواند نقش تعیین کننده ای در معرفی دسته بند برتر ایفا کند. برای درک بهتر نمودار ROC زیر را مشاهده کنید.
 

مقدار AUC برای یک دسته بند که بطور تصادفی، دسته نمونه مورد بررسی را تعیین می‌کند برابر 0.5 است. همچنین بیشترین مقدار این معیار برابر یک بوده و برای وضعیتی رخ می‌دهد که دسته بند ایده آل بوده و بتواند کلیه نمونه‌های مثبت را بدون هرگونه هشدار غلطی تشخیص دهد. معیار AUC برخلاف دیگر معیارهای تعیین کارایی دسته بندها مستقل از آستانه تصمیم گیری دسته بند می‌باشد. بنابراین این معیار نشان دهنده میزان قابل اعتماد بودن خروجی یک دسته بند مشخص به ازای مجموعه داده‌های متفاوت است که این مفهوم توسط سایر معیارهای ارزیابی کارایی دسته بندها قابل محاسبه نمی‌باشد. در برخی از مواقع سطح زیر منحنی‌های ROC مربوط به دو دسته بند با یکدیگر برابر است ولی ارزش آنها برای کاربردهای مختلف یکسان نیست که باید در نظر داشت در این گونه مسائل که ارزش دسته‌ها با یکدیگر برابر نیست، استفاده از معیار AUC مطلوب نمی‌باشد. به همین دلیل در این گونه مسائل استفاده از معیار دیگری به جزء هزینه (Cost Matrix) منطقی به نظر نمی‌رسد. در انتها باید توجه نمود در کنار معیارهای بررسی شده که همگی به نوعی دقت دسته بند را محاسبه می‌کردند، در دسته بندهای قابل تفسیر نظیر دسته بندهای مبتنی بر قانون و یا درخت تصمیم، پیچیدگی نهایی و قابل تفسیر بودن مدل یاد گرفته شده نیز از اهمیت بالایی برخوردار است. 

از روش‌های ارزیابی الگوریتم‌های دسته بندی (که در این الگوریتم روال کاری بدین صورت است که مدل دسته بندی توسط مجموعه داده آموزشی ساخته شده و بوسیله مجموعه داده آزمایشی مورد ارزیابی قرار می‌گیرد.) می‌توان به روش Holdout اشاره کرد که در این روش چگونگی نسبت تقسیم مجموعه داده‌ها (به دو مجموعه داده آموزشی و مجموعه داده آزمایشی) بستگی به تشخیص تحلیگر دارد که معمولاً دو سوم برای آموزش و یک سوم برای ارزیابی در نظر گرفته می‌شود. مهمترین مزیت این روش سادگی و سرعت بالای عملیات ارزیابی است ولیکن روش Holdout معایب زیادی دارد از جمله اینکه مجموعه داده‌های آموزشی و آزمایشی به یکدیگر وابسته خواهند شد، در واقع بخشی از مجموعه داده اولیه که برای آزمایش جدا می‌شود، شانسی برای حضور یافتن در مرحله آموزش ندارد و بطور مشابه در صورت انتخاب یک رکورد برای آموزش دیگر شانسی برای استفاده از این رکورد برای ارزیابی مدل ساخته شده وجود نخواهد داشت. همچنین مدل ساخته شده بستگی فراوانی به چگونگی تقسیم مجموعه داده اولیه به مجموعه داده‌های آموزشی و آزمایشی دارد. چنانچه روش Holdout را چندین بار اجرا کنیم و از نتایج حاصل میانگین گیری کنیم از روشی موسوم به Random Sub-sampling استفاده نموده ایم. که مهمترین عیب این روش نیز عدم کنترل بر روی تعداد دفعاتی که یک رکورد به عنوان نمونه آموزشی و یا نمونه آزمایشی مورد استفاده قرار می‌گیرد، است. به بیان دیگر در این روش ممکن است برخی رکوردها بیش از سایرین برای یادگیری و یا ارزیابی مورد استفاده قرار گیرند.
چنانچه در روش Random Sub-sampling به شکل هوشمندانه‌تری عمل کنیم به صورتی که هر کدام از رکوردها به تعداد مساوی برای یادگیری و تنها یکبار برای ارزیابی استفاده شوند، روش مزبور در متون علمی با نام Cross Validation شناخته می‌شود.
همچنین در روش جامع k-Fold Cross Validation کل مجموعه داده‌ها به k قسمت مساوی تقسیم می‌شوند. از k-1 قسمت به عنوان مجموعه داده‌های آموزشی استفاده می‌شود و براساس آن مدل ساخته می‌شود و با یک قسمت باقی مانده عملیات ارزیابی انجام می‌شود. فرآیند مزبور به تعداد k مرتبه تکرار خواهد شد، به گونه ای که از هر کدام از k قسمت تنها یکبار برای ارزیابی استفاده شده و در هر مرتبه یک دقت برای مدل ساخته شده، محاسبه می‌شود. در این روش ارزیابی دقت نهایی دسته بند برابر با میانگین k دقت محاسبه شده خواهد بود. معمول‌ترین مقداری که در متون علمی برای k در نظر گرفته می‌شود برابر با 10 می‌باشد. بدیهی است هر چه مقدار k بزرگتر شود، دقت محاسبه شده برای دسته بند قابل اعتماد‌تر بوده و دانش حاصل شده جامع‌تر خواهد بود و البته افزایش زمان ارزیابی دسته بند نیز مهمترین مشکل آن می‌باشد. حداکثر مقدار k برابر با تعداد رکوردهای مجموعه داده اولیه است که این روش ارزیابی با نام Leaving One Out شناخته می‌شود.
در روش هایی که تاکنون به آن اشاره شده، فرض بر آن است که عملیات انتخاب نمونه‌های آموزشی بدون جایگذاری صورت می‌گیرد. به بیان دیگر یک رکورد تنها یکبار در یک فرآیند آموزشی مورد توجه واقع می‌شود. چنانچه هر رکورد در صورت انتخاب شدن برای شرکت در عملیات یادگیری مدل بتواند مجدداً برای یادگیری مورد استفاده قرار گیرد روش مزبور با نام Bootstrap و یا   0.632 Bootstrap  شناخته می‌شود. (از آنجا که هر Bootstrap معادل 0.632 مجموعه داده اولیه است)
 

 
 ارزیابی در الگوریتم‌های خوشه بندی
به منظور ارزیابی الگوریتم‌های خوشه بندی می‌توان آنها به دو دسته تقسیم نمود:
شاخص‌های ارزیابی بدون ناظر، که گاهی در متون علمی با نام معیارهای داخلی شناخته می‌شوند، به آن دسته از معیارهایی گفته می‌شود که تعیین کیفیت عملیات خوشه بندی را با توجه به اطلاعات موجود در مجموعه داده بر عهده دارند. در مقابل، معیارهای ارزیابی با ناظر با نام معیار‌های خارجی نیز شناخته می‌شوند، که با استفاده از اطلاعاتی خارج از حیطه مجموعه داده‌های مورد بررسی، عملکرد الگوریتم‌های خوشه بندی را مورد ارزیابی قرار می‌دهند.
از آنجا که مهمترین وظیفه یک الگوریتم خوشه بندی آن است که بتواند به بهترین شکل ممکن فاصله درون خوشه ای را کمینه و فاصله بین خوشه ای را بیشینه نماید، کلیه معیارهای ارزیابی بدون ناظر سعی در سنجش کیفیت عملیات خوشه بندی با توجه به دو فاکتور تراکم خوشه ای و جدائی خوشه ای دارند. برآورده شدن هدف کمینه سازی درون خوشه ای و بیشینه سازی میان خوشه ای به ترتیب در گرو بیشینه نمودن تراکم هر خوشه و نیز بیشینه سازی جدایی میان خوشه‌ها می‌باشد. طیف وسیعی از معیارهای ارزیابی بدون ناظر وجود دارد که همگی در ابتدا تعریفی برای فاکتورهای تراکم و جدائی ارائه می‌دهند سپس توسط تابع (F(Cohesion, Separation مرتبط با خود، به ترکیب این دو فاکتور می‌پردازند. ذکر این نکته ضروری است که نمی‌توان هیچ کدام از معیارهای ارزیابی خوشه بندی را برای تمامی کاربردها مناسب دانست.

ارزیابی با ناظر الگوریتم‌های خوشه بندی، با هدف آزمایش و مقایسه عملکرد روش‌های خوشه بندی با توجه به حقایق مربوط به رکوردها صورت می‌پذیرد. به بیان دیگر هنگامی که اطلاعاتی از برچسب رکوردهای مجموعه داده مورد بررسی در اختیار داشته باشیم، می‌توانیم از آنها در عملیات ارزیابی عملکرد الگوریتم‌های خوشه بندی بهره بریم. لازم است در نظر داشته باشید در این بخش از برچسب رکوردها تنها در مرحله ارزیابی استفاده می‌شود و هر گونه بهره برداری از این برچسب‌ها در مرحله یادگیری مدل، منجر به تبدیل شدن روش کاوش داده از خوشه بندی به دسته بندی خواهد شد. مشابه با روش‌های بدون ناظر طیف وسیعی از معیارهای ارزیابی با ناظر نیز وجود دارد که در این قسمت با استفاده از روابط زیر به محاسبه معیارهای Rand Index و Jaccard می پردازیم به ترتیب در رابطه I و II نحوه محاسبه آنها نمایش داده شده است:

Rand Index را می‌توان به عنوان تعداد تصمیمات درست در خوشه بندی در نظر گرفت.
TP: به تعداد زوج داده هایی گفته می‌شود که باید در یک خوشه قرار می‌گرفتند، و قرار گرفته اند.
TN: به تعداد زوج داده هایی گفته می‌شود که باید در خوشه‌های جداگانه قرار داده می‌شدند و به درستی در خوشه‌های جداگانه جای داده شده اند.
FN: به تعداد زوج داده هایی گفته می‌شود که باید در یک خوشه قرار می‌گرفتند ولی در خوشه‌های جداگانه قرار داده شده اند.
FP: به تعداد زوج داده هایی اشاره دارد که باید در خوشه‌های متفاوت قرار می‌گرفتند ولی در یک خوشه قرار گرفته اند. 


 ارزیابی در الگوریتم‌های کشف قوانین انجمنی
به منظور ارزیابی الگوریتم‌های کشف قوانین انجمنی از آنجایی که این الگوریتم‌ها پتانسیل این را دارند که الگوها و قوانین زیادی تولید نمایند، جهت ارزیابی این قوانین به عواملی همچون شخص استفاده کننده از قوانین و نیز حوزه ای که مجموعه داده مورد بررسی به آن تعلق دارد، وابستگی زیادی پیدا می‌کنیم و بدین ترتیب کار پیدا کردن قوانین جذاب، به آسانی میسر نیست. فرض کنید قانونی با نام R داریم که به شکل A=>B می‌باشد، که در آن A و B زیر مجموعه ای از اشیاء می‌باشند.
پیشتر به معرفی دو معیار Support و Confidence پرداختیم. می‌دانیم از نسبت تعداد تراکنش هایی که در آن اشیاء A و B هر دو حضور دارند، به کل تعداد رکوردها Support بدست می‌آید که دارای مقداری عددی بین صفر و یک می‌باشد و هر چه این میزان بیشتر باشد، نشان می‌دهد که این دو شیء بیشتر با هم در ارتباط هستند. کاربر می‌تواند با مشخص کردن یک آستانه برای این معیار، تنها قوانینی را بدست آورد که Support آنها بیشتر از مقدار آستانه باشد، بدین ترتیب می‌توان با کاهش فضای جستجو، زمان لازم جهت پیدا کردن قوانین انجمنی را کمینه کرد. البته باید به ضعف این روش نیز توجه داشت که ممکن است قوانین با ارزشی را بدین ترتیب از دست دهیم. در واقع استفاده از این معیار به تنهایی کافی نیست. معیار Confidence نیز مقداری عددی بین صفر و یک می‌باشد، که هر چه این عدد بزرگتر باشد بر کیفیت قانون افزوده خواهد شد. استفاده از این معیار به همراه Support مکمل مناسبی برای ارزیابی قوانین انجمنی خواهد بود. ولی مشکلی که همچنان وجود دارد این است که امکان دارد قانونی با Confidence بالا وجود داشته باشد ولی از نظر ما ارزشمند نباشد.
از معیارهای دیگر قوانین انجمنی می‌توان به معیار Lift که با نام‌های Intersect Factor یا Interestingness نیز شناخته می‌شود اشاره کرد، که این معیار میزان استقلال میان اشیاء A و B را نشان می‌دهد که می‌تواند مقدار عددی بین صفر تا بی نهایت باشد. در واقع Lift میزان هم اتفاقی بین ویژگی‌ها را در نظر می‌گیرد و میزان رخداد تکی بخش تالی قانون (یعنی شیء B) را در محاسبات خود وارد می‌کند. (بر خلاف معیار Confidence)
مقادیر نزدیک به عدد یک معرف این هستند که A و B مستقل از یکدیگر می‌باشند، بدین ترتیب نشان دهنده قانون جذابی نمی‌باشند. چنانچه این معیار از عدد یک کمتر باشد، نشان دهنده این است که A و B با یکدیگر رابطه منفی دارند. هر چه مقدار این معیار بیشتر از عدد یک باشد، نشان دهنده این است که A اطلاعات بیشتری درباره B فراهم می‌کند که در این حالت جذابیت قانون A=>B بالاتر ارزیابی می‌شود. در ضمن این معیار نسبت به سمت چپ و راست قانون متقارن است در واقع اگر سمت چپ و راست قانون را با یکدیگر جابجا کنیم، مقدار این معیار تغییری نمی‌کند. از آنجائی که این معیار نمی‌تواند به تنهایی برای ارزیابی مورد استفاده قرار گیرد، و حتماً باید در کنار معیارهای دیگر باشد، باید مقادیر آن بین بازه صفر و یک نرمال شود. ترکیب این معیار به همراه Support و Confidence جزو بهترین روش‌های کاوش قوانین انجمنی است. مشکل این معیار حساس بودن به تعداد نمونه‌های مجموعه داده، به ویژه برای مجموعه تراکنش‌های کوچک می‌باشد. از این رو معیارهای دیگری برای جبران این نقص معرفی شده اند.
معیار Conviction برخی ضعف‌های معیارهای Confidence و Lift را جبران می‌نماید. محدوده قابل تعریف برای این معیار در حوزه 0.5 تا بی نهایت قرار می‌گیرد که هر چه این مقدار بیشتر باشد، نشان دهنده این است که آن قانون جذاب‌تر می‌باشد. بر خلاف Lift این معیار متقارن نمی‌باشد و مقدار این معیار برای دلالت‌های منطقی یعنی در جایی که Confidence قانون یک می‌باشد برابر با بی نهایت است و چنانچه A و B مستقل از هم باشند، مقدار این معیار برابر با عدد یک خواهد بود.


معیار Leverage که در برخی متون با نام Novelty (جدید بودن) نیز شناخته می‌شود، دارای مقداری بین 0.25- و 0.25+ می‌باشد. ایده مستتر در این معیار آن است که اختلاف بین میزان هم اتفاقی سمت چپ و راست قانون با آن مقداری که مورد انتظار است به چه اندازه می‌باشد.
معیار Jaccard که دارای مقداری عددی بین صفر و یک است، علاوه بر اینکه نشان دهنده وجود نداشتن استقلال آماری میان A و B می‌باشد، درجه همپوشانی میان نمونه‌های پوشش داده شده توسط هر کدام از آنها را نیز اندازه گیری می‌کند. به بیان دیگر این معیار فاصله بین سمت چپ و راست قانون را بوسیله تقسیم تعداد نمونه هایی که توسط هر دو قسمت پوشش داده شده اند بر نمونه هایی که توسط یکی از آنها پوشش داده شده است، محاسبه می‌کند. مقادیر بالای این معیار نشان دهنده این است که A و B تمایل دارند، نمونه‌های مشابهی را پوشش دهند. لازم است به این نکته اشاره شود از این معیار برای فهمیدن میزان همبستگی میان متغیرها استفاده می‌شود که از آن می‌توان برای یافتن قوانینی که دارای همبستگی بالا ولی Support کم هستند، استفاده نمود. برای نمونه در مجموعه داده سبد خرید، قوانین نادری که Support کمی دارند ولی همبستگی بالایی دارند، توسط این معیار می‌توانند کشف شوند.   

معیار (Coefficient (φ نیز به منظور اندازه گیری رابطه میان A و B مورد استفاده قرار می‌گیرد که محدوده این معیار بین 1- و 1+ می‌باشد.
از دیگر معیارهای ارزیابی کیفیت قوانین انجمنی، طول قوانین بدست آمده می‌باشد. به بیان دیگر با ثابت در نظر گرفتن معیارهای دیگر نظیر Support، Confidence و Lift قانونی برتر است که طول آن کوتاه‌تر باشد، بدلیل فهم آسانتر آن.
 

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

مطالب
React 16x - قسمت 9 - ترکیب کامپوننت‌ها - بخش 3 - Lifecycle Hooks
کامپوننت‌ها در طول چرخه‌ی عمر خود، از چندین مرحله عبور می‌کنند. اولین مرحله، mount نام دارد و زمانی رخ می‌دهد که وهله‌ای از یک کامپوننت، ایجاد و به DOM افزوده شده‌است. در این حالت تعدادی متد خاص را می‌توان به کامپوننت خود اضافه کرد که به صورت خودکار توسط React فراخوانی می‌شوند. به این متدها Lifecycle Hooks می‌گویند. در طی مرحله‌ی mount، سه متد Lifecycle Hooks مخصوص constructor، render و componentDidMount قابل تعریف هستند. React این متدها را به ترتیب فراخوانی می‌کند. دومین مرحله، update نام دارد و زمانی رخ می‌دهد که state و یا props کامپوننت تغییر می‌کنند. در طی مرحله‌ی update، دو متد Lifecycle Hooks مخصوص render و componentDidUpdate قابل تعریف هستند. آخرین فاز یا مرحله، unmount نام دارد و زمانی رخ می‌دهد که کامپوننتی از DOM حذف می‌شود، مانند حذف کامپوننت Counter در قسمت‌های قبل. در طی مرحله‌ی unmount، یک متد Lifecycle Hooks مخصوص componentWillUnmount قابل تعریف است.
البته این Lifecycle Hooks ای که در اینجا نام برده شدند، بیشترین استفاده را دارند. در مستندات React مواردی دیگری نیز ذکر شده‌اند که در عمل آنچنان مورد استفاده قرار نمی‌گیرند.


مرحله‌ی Mount

در کامپوننت App، یک constructor را اضافه می‌کنیم تا بتوان مرحله‌ی Mount را بررسی کرد. این سازنده تنها یکبار در زمان وهله سازی این کامپوننت فراخوانی می‌شود. یکی از کاربردهای آن می‌تواند مقدار دهی اولیه‌ی خواص این وهله باشد. برای مثال یکی از کاربردهای آن می‌تواند مقدار دهی اولیه‌ی state بر اساس مقادیر props رسیده باشد.  در اینجا است که می‌توان خاصیت state را مستقیما مقدار دهی کرد (مانند this.state = this.props.something) و در این حالت نیازی به فراخوانی متد this.setState نیست و اگر فراخوانی شود، یک خطا را دریافت می‌کنیم. از این جهت که this.setState را تنها زمانیکه کامپوننتی رندر شده و در DOM قرار گرفته باشد، می‌توان فراخوانی کرد.

یک نکته: فراخوانی this.state = this.props.something در سازنده‌ی کلاس میسر نیست، مگر اینکه props را به صورت پارامتر به سازنده‌ی کلاس و سازنده‌ی base class توسط متد super ارسال کنیم:
  constructor(props) {
    super(props);
    console.log("App - constructor");
    this.state = this.props.something;
  }
در غیراینصورت this.props، مقدار undefined را بازگشت می‌دهد.

دومین متد lifecycle hooks ای که بررسی می‌کنیم، componentDidMount است:
class App extends Component {
  constructor() {
    super();
    console.log("App - constructor");
  }

  componentDidMount() {
    // Ajax calls
    console.log("App - mounted");
  }
این متد پس از رندر کامپوننت در DOM فراخوانی می‌شود و بهترین محلی است که از آن می‌توان برای ارسال درخواست‌های Ajaxای به سمت سرور و دریافت اطلاعات از backend، استفاده کرد و سپس setState را با اطلاعات جدید فراخوانی نمود.

سومین lifecycle hooks در مرحله‌ی mounting، متد رندر است که در اینجا به ابتدای آن، یک  console.logرا جهت بررسی بیشتر اضافه می‌کنیم:
  render() {
    console.log("App - rendered");
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را می‌توان در کنسول توسعه دهندگان مرورگر مشاهده کرد:


در اینجا ترتیب فراخوانی این متدها را مشاهده می‌کنید. ابتدا سازنده‌ی کلاس فراخوانی شده‌است. سپس در مرحله‌ی رندر، یک المان React که در DOM مجازی React قرار می‌گیرد، بازگشت داده می‌شود. سپس React این DOM مجازی را با DOM اصلی هماهنگ می‌کند. پس از آن مرحله‌ی Mount فرا می‌رسد. یعنی در این مرحله، کامپوننت در DOM اصلی قرار دارد. اینجا است که اعمال Ajax ای دریافت اطلاعات از سرور باید انجام شوند.

یک نکته: در مرحله‌ی رندر، تمام فرزندان یک کامپوننت نیز به صورت بازگشتی رندر می‌شوند. برای نمایش این ویژگی، به متد Render کامپوننت‌های NavBar، Counters و Counter، متد  console.log ای را جهت درج این مرحله در کنسول، اضافه می‌کنیم:
class Counter extends Component {
  render() {
    console.log("Counter - rendered");
//...

class Counters extends Component {
  render() {
    console.log("Counters - rendered");
//...

const NavBar = ({ totalCounters }) => {
  console.log("NavBar - rendered");
//...

یک نکته: نمی‌توان از lifecycle hooks در کامپوننت‌های بدون حالت تابعی استفاده کرد.

پس از این تغییرات و ذخیره سازی برنامه، با بارگذاری مجدد آن در مرورگر، چنین خروجی در کنسول توسعه دهندگان مرورگر ظاهر می‌شود:


همانطور که مشاهده می‌کنید، پس از فراخوانی App - rendered، تمام فرزندان کامپوننت App رندر شده‌اند و در آخر به App - mounted رسیده‌ایم.


مرحله‌ی Update

مرحله‌ی Update زمانی رخ می‌دهد که state و یا props یک کامپوننت تغییر می‌کنند. برای مثال با کلیک بر روی دکمه‌ی Increment، وضعیت کامپوننت به روز رسانی می‌شود. پس از آن فراخوانی خودکار متد رندر در صف قرار می‌گیرد. به این معنا که تمام فرزندان آن نیز قرار است مجددا رندر شوند. برای آزمایش آن، یکبار لاگ‌های کنسول توسعه دهندگان مرورگر را پاک کنید. سپس بر روی دکمه‌ی Increment کلیک کنید:


همانطور که ملاحظه می‌کنید با کلیک بر روی دکمه‌ی Increment، کل Component tree برنامه مجددا رندر شده‌است. البته این مورد به معنای به روز رسانی کل DOM اصلی در مرورگر نیست. زمانیکه کامپوننتی رندر می‌شود، فقط یک React element حاصل آن خواهد بود که در نتیجه‌ی آن DOM مجازی React به روز رسانی خواهد شد. سپس React، کپی DOM مجازی قبلی را با نمونه‌ی جدید آن مقایسه می‌کند. در آخر، محاسبه‌ی تغییرات صورت گرفته و تنها بر اساس موارد تغییر یافته‌است که DOM اصلی را به روز رسانی می‌کند. به همین جهت زمانیکه بر روی دکمه‌ی Increment کلیک می‌شود، فقط span کنار آن در DOM اصلی به روز رسانی می‌شود. برای اثبات آن در مرورگر بر روی المان span که شماره‌ها را نمایش می‌دهد، کلیک راست کرده و گزینه‌ی inspect را انتخاب کنید. سپس بر روی دکمه‌ی Increment کلیک نمائید. مرورگر قسمتی را که به روز می‌شود، با رنگی مشخص و متمایز، به صورت لحظه‌ای نمایش می‌دهد.

متد componentDidUpdate، پس از به روز رسانی کامپوننت فراخوانی می‌شود. به این معنا که در این حالت وضعیت و یا props جدیدی را داریم. در این حالت می‌توان این اشیاء به روز شده را با نمونه‌های قبلی آن‌ها مقایسه کرد و در صورت وجود تغییری، برای مثال یک درخواست Ajax ای را به سمت سرور برای دریافت اطلاعات تکمیلی ارسال کرد و در غیراینصورت خیر. بنابراین می‌توان به آن به عنوان یک روش بهینه سازی نگاه کرد. برای نمایش این قابلیت می‌توان متد componentDidUpdate را که مقادیر قبلی props و state را دریافت می‌کند، لاگ کرد:
class Counter extends Component {
  componentDidUpdate(prevProps, prevState) {
    console.log("Counter - updated", { prevProps, prevState });
    if (prevProps.counter.value !== this.props.counter.value) {
      // Ajax call and get new data
    }
  }
برای آزمایش آن، یکبار لاگ‌های کنسول توسعه دهندگان مرورگر را پاک کنید. سپس بر روی دکمه‌ی Increment اولین شمارشگر کلیک کنید:


همانطور که مشاهده می‌کنید، مقدار شیء counter، پیش از کلیک بر روی دکمه‌ی Increment، مساوی 4 بوده‌است. در یک چنین حالتی می‌توان مقدار قبلی prevProps.counter.value را با مقدار جدید this.props.counter.value مقایسه کرد و در صورت نیاز یک درخواست Ajax ای را برای دریافت اطلاعات به روز، صادر کرد.


مرحله‌ی Unmount

در این مرحله تنها یک lifecycle hook به نام componentWillUnmount قابل تعریف است که درست پیش از حذف یک کامپوننت از DOM فراخونی می‌شود.
class Counter extends Component {
  componentWillUnmount(){
    console.log("Counter - Unmount");
  }
پس از افزودن متد فوق و بارگذاری مجدد برنامه در مرورگر، یکبار دیگر لاگ‌های کنسول توسعه دهندگان مرورگر را پاک کنید. سپس اولین Counter رندر شده را حذف کنید.


در اینجا پس از حذف یک کامپوننت، state کامپوننت App تغییر کرده‌است. به همین جهت کل Component tree رندر مجدد شده‌است. اینبار یک DOM مجازی جدید را داریم که تعداد Counterهای آن 3 مورد است. سپس React این DOM مجازی جدید را با نمونه‌ی قبلی خود مقایسه کرده و متوجه می‌شود که یکی از Counterها حذف شده‌است. در ادامه متد componentWillUnmount را پیش از حذف این ‍Counter از DOM، فراخوانی می‌کند. به این ترتیب فرصت خواهیم یافت تا رهاسازی منابع را در صورت نیاز انجام دهیم تا برنامه دچار نشتی حافظه نشود.


یک مثال: افزودن دکمه‌ی Decrement به کامپوننت Counter

در ادامه می‌خواهیم دکمه‌ای را برای کاهش مقدار یک شمارشگر، به کامپوننت Counter اضافه کنیم. همچنین اگر مقدار value شمارشگر مساوی صفر بود، دکمه‌ی کاهش مقدار آن باید غیرفعال شود و برعکس. به علاوه از سیستم طرحبندی بوت استرپ نیز برای تعریف دو ستون، یکی برای نمایش مقدار شمارشگرها و دیگری برای نمایش دکمه‌ها استفاده خواهیم کرد.


برای پیاده سازی آن ابتدا متد رندر کامپوننت Counter را به صورت زیر تغییر می‌دهیم:
class Counter extends Component {

  render() {
    console.log("Counter - rendered");
    return (
      <div className="row">
        <div className="col-1">
          <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        </div>
        <div className="col">
          <button
            onClick={() => this.props.onIncrement(this.props.counter)}
            className="btn btn-secondary btn-sm"
          >
            +
          </button>
          <button
            onClick={() => this.props.onDecrement(this.props.counter)}
            className="btn btn-secondary btn-sm m-2"
            disabled={this.props.counter.value === 0 ? "disabled" : ""}
          >
            -
          </button>
          <button
            onClick={() => this.props.onDelete(this.props.counter.id)}
            className="btn btn-danger btn-sm"
          >
            Delete
          </button>
        </div>
      </div>
    );
  }
در اینجا یک row تعریف شده و سپس دو div، با کلاس‌های تعیین عرض ستون‌ها. در ادامه span نمایش شمارشگر، به div اول و دکمه‌ها به div دوم منتقل شده‌اند. همچنین marginها را هم اصلاح کرده‌ایم تا بین دکمه‌ها فضای مناسبی ایجاد شود.
در این بین، دکمه‌ی جدید کاهش مقدار را که با یک - مشخص شده‌است نیز مشاهده می‌کنید. رویدادگردان onClick آن به this.props.onDecrement اشاره می‌کند. همچنین ویژگی disabled نیز به آن اضافه شده‌است تا بر اساس مقدار value شیء counter، در مورد فعال یا غیرفعالسازی دکمه تصمیم گیری کند.
پس از آن نیاز است این this.props.onDecrement را تعریف کنیم. به همین جهت به والد آن که کامپوننت Counters است مراجعه کرده و آن‌را به صورت زیر تغییر می‌دهیم:
<Counter
  key={counter.id}
  counter={counter}
  onDelete={this.props.onDelete}
  onIncrement={this.props.onIncrement}
  onDecrement={this.props.onDecrement}
/>
در اینجا onDecrement اضافه شده‌است تا شیء this.props ارسالی به کامپوننت Counter را مقدار دهی کند. اکنون باید ارجاع به this.props.onDecrement این کامپوننت را نیز تکمیل کرد. این ارجاع نیز به والد Counters که در اینجا کامپوننت App است اشاره می‌کند:
<Counters
  counters={this.state.counters}
  onReset={this.handleReset}
  onIncrement={this.handleIncrement}
  onDecrement={this.handleDecrement}
  onDelete={this.handleDelete}
/>
در کامپوننت App، ویژگی onDecrement ارسالی به کامپوننت Counters، به صورت props مقدار دهی شده‌است. این ویژگی به متد this.handleDecrement اشاره می‌کند که به صورت زیر در کامپوننت App تعریف می‌شود:
  handleDecrement = counter => {
    console.log("handleDecrement", counter);
    const counters = [...this.state.counters]; // cloning an array
    const index = counters.indexOf(counter);
    counters[index] = { ...counter }; // cloning an object
    counters[index].value--;
    console.log("this.state.counters", this.state.counters[index]);
    this.setState({ counters });
  };
که کدهای آن با کدهای handleIncrement بحث شده‌ی در قسمت قبل یکی است. اکنون اگر برنامه را اجرا کنید، به تصویر ابتدای توضیحات این مثال خواهید رسید.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-09.zip
نظرات مطالب
معرفی System.Text.Json در NET Core 3.0.
یک نکته‌ی تکمیلی: نوشتن تبدیلگرهای نوع‌ها برای System.Text.Json

System.Text.Json، در حال حاضر از مفهومی به نام type coercion/inference، پشتیبانی نمی‌کند. type coercion یعنی تبدیل یک مقدار، به مقداری دیگر که به صورت مستقیم قابل انتساب به یکدیگر نیستند. برای مثال اگر رشته‌ی "true" را درنظر بگیریم، قابلیت انتساب به یک خاصیت از نوع bool را ندارد. برای یک چنین مواردی در این API جدید، باید تبدیلگر نوشت.
یک مثال:
using System.Collections.Generic;
using System.Text.Json;

namespace JsonTests
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsInStock { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {            
         var products = JsonSerializer.Deserialize<List<Product>>("[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]");
        }
    }
}
در این مثال، خاصیت IsInStock از نوع bool است، اما مقداری را که باید از طریق متد Deserialize دریافت کنیم، یک رشته‌ی bool ای است که قابل انتساب به bool نیست. در این حالت اگر برنامه را اجرا کنیم به استثنای زیر خواهیم رسید:
An unhandled exception of type 'System.Text.Json.JsonException' occurred in System.Text.Json.dll
Inner exceptions found, see $exception in variables window for more details.
Innermost exception System.InvalidOperationException : Cannot get the value of a token type 'String' as a boolean.
برای رفع این مشکل، می‌توان تبدیلگر زیر را تدارک دید:
    public class BooleanConverter : JsonConverter<bool>
    {
        public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            if (value.Equals("true", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("yes", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("1", StringComparison.Ordinal))
            {
                return true;
            }

            if (value.Equals("false", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("no", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("0", StringComparison.Ordinal))
            {
                return false;
            }

            throw new NotSupportedException($"`{value}` can't be converted to `bool`.");
        }

        public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
        {
            switch (value)
            {
                case true:
                    writer.WriteStringValue("true");
                    break;
                case false:
                    writer.WriteStringValue("false");
                    break;
            }
        }
    }
برای نوشتن یک تبدیلگر bool، کلاس مرتبط، باید <JsonConverter<bool را پیاده سازی کند. کلاس JsonConverter نیز به صورت زیر تعریف شده‌است:
    public abstract class JsonConverter<T> : JsonConverter
    {
        protected internal JsonConverter();

        public override bool CanConvert(Type typeToConvert);
        public abstract T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);
        public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options);
    }
و پیاده سازی دو متد Read و Write آن الزامی است.
در متد Read آن، مقدار رشته‌ای دریافت شده‌ی از منبع داده، در اختیار ما قرار می‌گیرد. سپس باید بر اساس این مقدار، مقدار متناظری را از نوع T که در اینجا bool است، بازگشت دهیم. برای مثال اگر یکی از مقادیر رشته‌ای true ،yes و 1 را دریافت کردیم، بجای آن true را بازگشت می‌دهیم.

اکنون برای استفاده‌ی از آن خواهیم داشت:
var options = new JsonSerializerOptions();
options.Converters.Add(new BooleanConverter());
var products = JsonSerializer.Deserialize<List<Product>>(
   "[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]",
   options);
و یا روش دیگر انجام اینکار، استفاده از ویژگی JsonConverter، برای معرفی تبدیلگر تهیه شده‌است:
[JsonConverter(typeof(BooleanConverter))]
public bool IsInStock { get; set; }

از این تبدیلگر برای حالت Serialize نیز می‌توان استفاده کرد:
var options  = new JsonSerializerOptions() {WriteIndented = true };
options.Converters.Add(new BooleanConverter());
var data = JsonSerializer.Serialize<List<Product>>(productList, options);