مطالب
اگر نصب سرویس پک اس کیوال سرور Fail شد ...

همانطور که مطلع هستید سرویس پک سه SQL Server چند روزی است که منتشر شده. این به روز رسانی بر روی یک سرور بدون مشکل نصب شد؛ در سرور دیگر به علت داشتن یک سری برنامه امنیتی مزاحم (که مثلا دسترسی به رجیستری را مونیتور و سد می‌کنند) با شکست مواجه و در آخر پیغام Fail نمایش داده شد. مجددا آنرا اجرا کردم، سریع تمام مراحل را تمام کرد باز هم Fail را نمایش داد.
خوب؛ گفتم احتمالا مشکلی نیست. سعی کردم به سرور وصل شوم ... پیغام «این سرور دسترسی از راه دور را نمی‌پذیرد» و از این حرف‌های متداول ظاهر شد. به لاگ موجود در Event log ویندوز که مراجعه کردم پیغام خطای زیر نمایان بود:

Script level upgrade for database 'master' failed because upgrade step 'sqlagent100_msdb_upgrade.sql' encountered error 5597, state 1, severity 16. This is a serious error condition which might interfere with regular operation and the database will be taken offline. If the error happened during upgrade of the 'master' database, it will prevent the entire SQL Server instance from starting. Examine the previous errorlog entries for errors, take the appropriate corrective actions and re-start the database so that the script upgrade steps run to completion.

اوه! اوه! اوه! در این لحظه‌ی عرفانی، دیتابیس master نابود شده! نمی‌شود وصل شد. سروری که داشت تا مدتی قبل بدون هیچ مشکلی کار می‌کرد، الان دیگر حتی نمی‌شود به آن وصل شد. به کنسول سرویس‌های ویندوز مراجعه کردم (services.msc)، سعی کردم سرویس اس کیوال را که از کار افتاده دستی اجرا کنم، پیغام زیر مجددا در event log ظاهر شد:

FILESTREAM feature could not be initialized. The Windows Administrator must enable FILESTREAM on the instance using Configuration Manager before enabling through sp_configure.

قابلیت FILESTREAM را نمی‌تواند آغاز کند. پس از مدتی جستجو مشخص شد که این مورد را می‌شود در رجیستری ویندوز غیرفعال کرد؛ به صورت زیر:

1) Open up Registry Editor
2) Go To HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10.MSSQLServer\MSSQLSERVER\FileStream
3) Edit the value "EnableLevel" and set it to 0
4) Restart SQL Server.

پس از انجام اینکار، سرویس اس کیوال استارت شد (از طریق کنسول سرویس‌های ویندوز). در ادامه، امکان اتصال به آن نبود (حتی با اکانت sa):

Login failed for user 'sa'. Reason: Server is in script upgrade mode. Only administrator can connect at this time. (Microsoft SQL Server, Error: 18401)


باز هم پس از مدتی جستجو معلوم گردید که «کمی باید صبر کرد». آن پیغام اول کار مبتنی بر تخریب دیتابیس master هم بی‌مورد است. پس از fail شدن نصب سرویس پک، هنوز برنامه نصاب آن در پشت صحنه مشغول به کار است. این مورد به وضوح در task manager ویندوز مشخص است. سرور به مدت 15 دقیقه به حال خود رها شد. پس از آن بدون مشکل اتصال برقرار گردید و همه چیز مجددا شروع به کار کرد.

بنابراین اگر در حین نصب سرویس پک SQL Server مشکلی پیش آمد، نگران نباشید. باید به نصاب آن زمان داد (برنامه mscorsw.exe در پشت صحنه مشغول به کار است). برنامه نصاب آن هم هیچ نوع خطای مفهومی را گزارش نمی‌دهد. تمام مراحل، بجای نمایش در برنامه تمام صفحه نصاب آن، در event log ویندوز ثبت می‌شود. این برنامه تمام صفحه فقط کارش نمایش یک progress bar است!


اگر ... هیچکدام از این موارد جواب نداد، امکان بازسازی دیتابیس master نیز وجود دارد: [^ , ^]
ولی دست نگه دارید و سریع اقدام نکنید. ابتدا به task manager مراجعه کنید. آیا برنامه mscorsw.exe در حال اجرا است؟ اگر بله، یعنی هنوز کار نصب تمام نشده. حداقل یک ربع باید صبر کنید.

مطالب
آشنایی با Refactoring - قسمت 1

کارهای سورس باز قابل توجهی از برنامه نویس‌های ایرانی یافت نمی‌شوند؛ عموما کارهای ارائه شده در حد یک سری مثال یا کتابخانه‌های کوچک است و در همین حد. یا گاهی هم انگشت شمار پروژه‌هایی کامل. مثل یک وب سایت یا یک برنامه نصفه نیمه دبیرخانه و امثال آن. این‌ها هم خوب است از دیدگاه به اشتراک گذاری اطلاعات، ایده‌ها و هم ... یک مزیت دیگر را هم دارد و آن این است که بتوان کیفیت عمومی کد نویسی را حدس زد.
اگر کیفیت کدها رو بررسی ‌کنید به یک نتیجه‌ی کلی خواهید رسید: "عموم برنامه نویس‌های ایرانی (حداقل این‌هایی که چند عدد کار سورس باز به اشتراک گذاشته‌اند) با مفهومی به نام Refactoring هیچگونه آشنایی ندارند". مثلا یک برنامه‌ی WinForm تهیه کرده‌اند و کل سورس برنامه همان چند عدد فرم برنامه است و هر فرم بالای 3000 سطر کد دارد. دوستان عزیز! به این می‌گویند «فاجعه‌ای به نام کدنویسی!» صاحب اول و آخر این نوع کدها خودتان هستید! شاید به همین جهت باشد که عمده‌ی پروژه‌های سورس باز پس از اینکه برنامه نویس اصلی از توسعه‌ی آن دست می‌کشد، «می‌میرند». چون کسی جرات نمی‌کند به این کدها دست بزند. مشخص نیست الان این قسمت را که تغییر دادم، کجای برنامه به هم ریخت. تستی ندارند. ساختاری را نمی‌توان از آن‌ها دریافت. منطق قسمت‌های مختلف برنامه از هم جدا نشده است. برنامه یک فرم است با چند هزار سطر کد در یک فایل! کار شما شبیه به کد اسمبلی چند هزار سطری حاصل از decompile یک برنامه که نباید باشد!
به همین جهت قصد دارم یک سری «ساده» Refactoring را در این سایت ارائه دهم. روی سادگی هم تاکید کردم، چون اگر عموم برنامه نویس‌ها با همین موارد به ظاهر ساده آشنایی داشتند، کیفیت کد نویسی بهتری را می‌شد در نتایج عمومی شده، شاهد بود.
این مورد در راستای نظر سنجی انجام شده هم هست؛ درخواست مقالات خالص سی شارپ در صدر آمار فعلی قرار دارد.



Refactoring چیست؟

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


قسمت اول - مجموعه‌ها را کپسوله کنید

برای مثال کلاس‌های ساده زیر را در نظر بگیرید:

namespace Refactoring.Day1.EncapsulateCollection
{
public class OrderItem
{
public int Id { set; get; }
public string Name { set; get; }
public int Amount { set; get; }
}
}

using System.Collections.Generic;

namespace Refactoring.Day1.EncapsulateCollection
{
public class Orders
{
public List<OrderItem> OrderItems { set; get; }
}
}

نکته اول: هر کلاس باید در داخل یک فایل جدا قرار گیرد. «لطفا» یک فایل درست نکنید با 50 کلاس داخل آن. البته اگر باز هم یک فایل باشد که بتوان 50 کلاس را داخل آن مشاهده کرد که چقدر هم عالی! نه اینکه یک فایل باشد تا بعدا 50 کلاس را با Refactoring از داخل آن بیرون کشید!

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

Error, Certainty 95, for Do Not Expose Generic Lists

بله. لیست‌های جنریک را نباید به همین شکل در اختیار مصرف کننده قرار داد؛ چون به این صورت هر کاری را می‌توانند با آن انجام دهند، مثلا کل آن را تعویض کنند، بدون اینکه کلاس تعریف کننده آن از این تغییرات مطلع شود.
پیشنهاد FxCop این است که بجای List از Collection یا IList و موارد مشابه استفاده شود. اگر اینکار را انجام دهیم اینبار به خطای زیر خواهیم رسید:

Warning, Certainty 75, for Collection Properties Should Be ReadOnly

FxCop پیشنهاد می‌دهد که مجموعه تعریف شده باید فقط خواندنی باشد.

چکار باید کرد؟
بجای استفاده از List جهت ارائه مجموعه‌ها، از IEnumerable استفاده کنید و اینبار متدهای Add و Remove اشیاء به آن‌را به صورت دستی تعریف نمائید تا بتوان از تغییرات انجام شده بر روی مجموعه ارائه شده، در کلاس اصلی آن مطلع شد و امکان تعویض کلی آن‌را از مصرف کننده گرفت. برای مثال:

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

namespace Refactoring.Day1.EncapsulateCollection
{
public class Orders
{
private int _orderTotal;
private List<OrderItem> _orderItems;

public IEnumerable<OrderItem> OrderItems
{
get { return _orderItems; }
}

public void AddOrderItem(OrderItem orderItem)
{
_orderTotal += orderItem.Amount;
_orderItems.Add(orderItem);
}

public void RemoveOrderItem(OrderItem orderItem)
{
var order = _orderItems.Find(o => o == orderItem);
if (order == null) return;

_orderTotal -= orderItem.Amount;
_orderItems.Remove(orderItem);
}
}
}


اکنون اگر برنامه را مجددا با fxCop آنالیز کنیم، دو خطای ذکر شده دیگر وجود نخواهند داشت. اگر این تغییرات صورت نمی‌گرفت، امکان داشتن فیلد _orderTotal غیر معتبری در کلاس Orders به شدت بالا می‌رفت. زیرا مصرف کننده مجموعه OrderItems می‌توانست به سادگی آیتمی را به آن اضافه یا از آن حذف کند، بدون اینکه کلاس Orders از آن مطلع شود یا اینکه بتواند عکس العمل خاصی را بروز دهد.


مطالب
آشنایی با Refactoring - قسمت 2

قسمت دوم آشنایی با Refactoring به معرفی روش «استخراج متدها» اختصاص دارد. این نوع Refactoring بسیار ساده بوده و مزایای بسیاری را به همراه دارد؛ منجمله:
- بالا بردن خوانایی کد؛ از این جهت که منطق طولانی یک متد به متدهای کوچکتری با نام‌های مفهوم شکسته می‌شود.
- به این ترتیب نیاز به مستند سازی کدها نیز بسیار کاهش خواهد یافت. بنابراین در یک متد، هر جایی که نیاز به نوشتن کامنت وجود داشت، یعنی باید همینجا آن‌ قسمت را جدا کرده و در متد دیگری که نام آن، همان خلاصه کامنت مورد نظر است، قرار داد.
- این نوع جدا سازی منطق‌های پیاده سازی قسمت‌های مختلف یک متد، در آینده نگهداری کد نهایی را نیز ساده‌تر کرده و انجام تغییرات بر روی آن را نیز تسهیل می‌بخشد؛ زیرا اینبار بجای هراس از دستکاری یک متد طولانی، با چند متد کوچک و مشخص سروکار داریم.

برای نمونه به مثال زیر دقت کنید:
using System.Collections.Generic;

namespace Refactoring.Day2.ExtractMethod.Before
{
public class Receipt
{
private IList<decimal> Discounts { get; set; }
private IList<decimal> ItemTotals { get; set; }

public decimal CalculateGrandTotal()
{
// Calculate SubTotal
decimal subTotal = 0m;
foreach (decimal itemTotal in ItemTotals)
subTotal += itemTotal;

// Calculate Discounts
if (Discounts.Count > 0)
{
foreach (decimal discount in Discounts)
subTotal -= discount;
}

// Calculate Tax
decimal tax = subTotal * 0.065m;
subTotal += tax;

return subTotal;
}
}
}

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

using System.Collections.Generic;

namespace Refactoring.Day2.ExtractMethod.After
{
public class Receipt
{
private IList<decimal> Discounts { get; set; }
private IList<decimal> ItemTotals { get; set; }

public decimal CalculateGrandTotal()
{
decimal subTotal = CalculateSubTotal();
subTotal = CalculateDiscounts(subTotal);
subTotal = CalculateTax(subTotal);
return subTotal;
}

private decimal CalculateTax(decimal subTotal)
{
decimal tax = subTotal * 0.065m;
subTotal += tax;
return subTotal;
}

private decimal CalculateDiscounts(decimal subTotal)
{
if (Discounts.Count > 0)
{
foreach (decimal discount in Discounts)
subTotal -= discount;
}
return subTotal;
}

private decimal CalculateSubTotal()
{
decimal subTotal = 0m;
foreach (decimal itemTotal in ItemTotals)
subTotal += itemTotal;
return subTotal;
}
}
}

بهتر شد! عملکرد کد نهایی، تغییری نکرده اما کیفیت کد ما بهبود یافته است (همان مفهوم و معنای Refactoring). خوانایی کد افزایش یافته است. نیاز به کامنت نویسی به شدت کاهش پیدا کرده و از همه مهم‌تر، اعمال مختلف، در متدهای خاص آن‌ها قرار گرفته‌اند.
به همین جهت اگر حین کد نویسی، به یک متد طولانی برخوردید (این مورد بسیار شایع است)، در ابتدا حداقل کاری را که جهت بهبود کیفیت آن می‌توانید انجام دهید، «استخراج متدها» است.

ابزارهای کمکی جهت پیاده سازی روش «استخراج متدها»:

- ابزار Refactoring توکار ویژوال استودیو پس از انتخاب یک قطعه کد و سپس کلیک راست و انتخاب گزینه‌ی Refactor->Extract method، این عملیات را به خوبی می‌تواند مدیریت کند و در وقت شما صرفه جویی خواهد کرد.
- افزونه‌های ReSharper و همچنین CodeRush نیز چنین قابلیتی را ارائه می‌دهند؛ البته توانمندی‌های آن‌ها از ابزار توکار یاد شده بیشتر است. برای مثال اگر در میانه کد شما جایی return وجود داشته باشد، گزینه‌ی Extract method ویژوال استودیو کار نخواهد کرد. اما سایر ابزارهای یاده شده به خوبی از پس این موارد و سایر موارد پیشرفته‌تر بر می‌آیند.

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


و ... «لطفا» این نوع پیاده سازی‌ها را خارج از فایل code behind هر نوع برنامه‌ی winform/wpf/asp.net و غیره قرار دهید. تا حد امکان سعی کنید این مکان‌ها، استفاده کننده‌ی «نهایی» منطق‌های پیاده سازی شده توسط کلاس‌های دیگر باشند؛ نه اینکه خودشان محل اصلی قرارگیری و ابتدای تعریف منطق‌های مورد نیاز قسمت‌های مختلف همان فرم مورد نظر باشند. «لطفا» یک فرم درست نکنید با 3000 سطر کد که در قسمت code behind آن قرار گرفته‌اند. code behind را محل «نهایی» ارائه کار قرار دهید؛ نه نقطه‌ی آغاز تعریف منطق‌های پیاده سازی کار. این برنامه نویسی چندلایه که از آن صحبت می‌شود، فقط مرتبط با کار با بانک‌های اطلاعاتی نیست. در همین مثال، کدهای فرم برنامه، باید نقطه‌ی نهایی نمایش عملیات محاسبه مالیات باشند؛ نه اینکه همانجا دوستانه یک قسمت مالیات حساب شود، یک قسمت تخفیف، یک قسمت جمع بزند، همانجا هم نمایش بدهد! بعد از یک هفته می‌بینید که code behind فرم در حال انفجار است! شده 3000 سطر! بعد هم سؤال می‌پرسید که چرا اینقدر میل به «بازنویسی» سیستم این اطراف زیاد است! برنامه نویس حاضر است کل کار را از صفر بنویسد، بجای اینکه با این شاهکار بخواهد سرو کله بزند! هر چند یکی از روش‌های برخورد با این نوع کدها جهت کاهش هراس نگهداری آن‌ها، شروع به Refactoring است.

مطالب
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS

زمانیکه اولین نگارش ASP.NET‌ حدود 10 سال قبل منتشر شد،‌ تنها سیستم عاملی که از آن پشتیبانی می‌کرد، ویندوز سرور 2000 بود، تنها پروسه‌ی اجرایی آن aspnet_wp نام داشت و تنها معماری پشتیبانی شده هم X86 بود. به پروسه‌ی aspnet_wp محدودیت مصرف حافظه‌ای اعمال شده بود که در حین آغاز آن بر اساس مقدار قابل تغییر processModel memoryLimit محاسبه و اعمال می‌شد (تعریف شده در فایل ماشین کانفیگ). این عدد به صورت درصدی از ظرفیت RAM فیزیکی سیستم، قابل تعریف و به صورت پیش فرض به 60 درصد تنظیم شده بود. به این ترتیب این پروسه مجاز نبود تا تمام حافظه‌ی فیزیکی مهیا را مصرف کند و در صورت وجود نشتی حافظه‌ای در برنامه‌ای خاص، این پروسه امکان بازیابی مجدد حافظه را پیدا می‌کرد (recycling). همچنین یک مورد دیگر را هم باید در نظر داشت و آن هم وجود قابلیتی است به نام ASP.NET Cache است که امکان ذخیره سازی مقادیر اشیاء را در حافظه‌ی مصرفی این پروسه مهیا می‌سازد. هر زمان که میزان این حافظه‌ی مصرفی به حد نزدیکی از محدودیت تعریف شده برسد، این پروسه به صورت خودکار شروع به حذف آن‌ها خواهد کرد.
محدودیت 60 درصدی تعریف شده، برای سیستم‌هایی با میزان RAM کم بسیار مفید بود اما در سیستم‌هایی با میزان RAM بیشتر، مثلا 4 گیگ به 2.4GB حافظه مهیا (60 درصد حافظه فیزیکی سیستم) محدود می‌شد و همچنین باید در نظر داشت که میزان user mode virtual address space مهیا نیز تنها 2 گیگابایت بود. بنابراین هیچگاه استفاده مؤثری از تمام ظرفیت RAM مهیا صورت نمی‌گرفت و گاها مشاهده می‌شد که یک برنامه تنها با مصرف 1.5GB RAM می‌توانست پیغام OutOfMemoryException را صادر کند. در این حالت مطابق بررسی‌های صورت گرفته مشخص شد که اگر مقدار processModel memoryLimit به حدود 800 مگابایت تنظیم شود، بهترین عملکرد را برای سیستم‌های مختلف می‌توان مشاهده کرد.

با ارائه‌ی ویندوز سرور 2003 و همچنین ارائه‌ی نسخه‌ی 1.1 دات نت فریم ورک و ASP.NET ، این وضعیت تغییر کرد. پروسه‌ی جدید در اینجا w3wp نام دارد و این پروسه تعاریف مرتبط با محدودیت حافظه‌ی خود را از تنظیمات IIS دریافت می‌کند (قسمت Maximum Used Memory در برگه‌ی Recycling مربوط به خواص Application Pool مرتبط). متاسفانه این عدد به صورت پیش فرض محدودیتی ندارد و به ظاهر برنامه مجاز است تا حد امکان از حافظه‌ی مهیا استفاده کند. به همین جهت یکی از مواردی را که باید در نظر داشت، مقدار دهی Maximum Used Memory ذکر شده است. خصوصا اینکه در نگارش 1.1 ، تنظیمات میزان مصرف RAM مرتبط با ASP.NET Cache نیز با برنامه یکی است.

در نگارش 2.0 دات نت فریم ورک، تنظیمات مرتبط با ASP.NET cache از تنظیمات میزان RAM مصرفی یک برنامه‌ی ASP.NET جدا شد و این مورد توسط قسمت cache privateBytesLimit قابل تنظیم و مدیریت است (در فایل IIS Metabase و همچنین فایل web.config برنامه).

نکته!
اگر process memory limit و همچنین cache memory limit را تنظیم نکنید، باز به همان عدد 60 درصد سابق بازخواهیم گشت و این مورد به صورت خودکار توسط IIS محاسبه و اعمال می‌شود. البته محدودیت ذکر شده برای پروسه‌های 64 بیتی در این حالت بسیار بهتر خواهد بود. اگر هر دوی این‌ها را تنظیم کنید، عدد حداقل بکارگرفته شده، مبنای کار خواهد بود و اگر تنها یکی را تنظیم کنید ، این عدد به هر دو حالت اعمال می‌گردد. برای بررسی بهتر می‌توان به مقدار Cache.EffectivePrivateBytesLimit و Cache.EffectivePercentagePhysicalMemoryLimit مراجعه کرد.

و ... اکنون بهتر می‌توانید به این سؤال پاسخ دهید که «سرور ما بیشتر از 4 گیگ رم دارد و برنامه‌ی ASP.NET من الان فقط 850 مگ رم مصرف کرده (که البته این هم نشانی از عدم dispose صحیح منابع است یا عدم تعیین تقدم و تاخر و زمان منقضی شدن، حین تعریف اشیاء کش)، اما پیغام out of memory exception را دریافت می‌کنم. چرا؟!»


بنابراین ایجاد یک Application pool جدید به ازای هر برنامه‌ی ASP.NET امری است بسیار مهم زیرا:
- به این ترتیب هر برنامه‌ی ASP.NET در پروسه‌ای ایزوله از پروسه‌ی دیگر اجرا خواهد شد (این مساله از لحاظ امنیتی هم بسیار مهم است). در اینجا هر برنامه، از پروسه‌ی w3wp.exe مجزای خاص خود استفاده خواهد کرد (شبیه به مرورگرهایی که هر tab را در یک پروسه جدید اجرا می‌کنند).
- اگر پروسه‌ای به حد بالای مصرف حافظه‌ی خود رسید با تنظیمات انجام شده در قسمت recycling مرتبط با Application pool اختصاصی آن، به صورت خودکار کار بازیابی حافظه صورت می‌گیرد و این امر بر روی سایر برنامه‌ها تاثیر نخواهد داشت (کاربران سایر برنامه‌ها مدام شکایت نمی‌کنند که سشن‌ها پرید. کش خالی شد. زیرا در حالت وجود application pool اختصاصی به ازای هر برنامه، مدیریت حافظه برنامه‌ها از هم ایزوله خواهند بود)
- کرش صورت گرفته در یک برنامه به دلیل عدم مدیریت خطاها، بر روی سایر برنامه‌ها تاثیر منفی نخواهد گذاشت. (زمانیکه ASP.NET worker process به دلیل استثنایی مدیریت نشده خاتمه یابد بلافاصله و به صورت خودکار مجددا «وهله‌ی دیگری» از آن شروع به کار خواهد کرد؛ یعنی تمام سشن‌های قبلی از بین خواهند رفت؛ که در صورت ایزوله سازی ذکر شده، سایر برنامه‌ها در امان خواهند ماند؛ چون در پروسه ایزوله‌ی خود مشغول به کار هستند)
- با وجود application pool اختصاصی به ازای هر برنامه، می‌توان برای سایت‌های کم ترافیک و پرترافیک، زمان‌های recycling متفاوتی را اعمال کرد. به این ترتیب مدیریت حافظه‌ی بهتری قابل پیاده سازی می‌باشد. همچنین در این حالت می‌توان مشخص کرد کدام سایت از تعداد worker process بیشتر یا کمتری استفاده کند.
- کاربری که پروسه‌ی ASP.NET تحت آن اجرا می‌شود نیز همینجا تعریف می‌گردد. بنابراین به این ترتیب می‌توان به برنامه‌ای دسترسی بیشتر و یا کمتر داد، بدون تاثیر گذاری بر روی سایر برنامه‌های موجود.

نتیجه گیری:
- از IIS استفاده می‌کنید؟ آیا می‌دانید Application pool چیست؟
- آیا می‌دانید در صورت عدم مقدار دهی پارامترهای حافظه‌ی یک Application pool ، به صورت پیش فرض چند درصد از حافظه‌ی فیزیکی مهیا در اختیار شما است؟


برای مطالعه بیشتر:

مطالب
معرفی برنامه‌ی Subtitle Tools

این روزها زیرنویس‌های فارسی فیلم‌های روز دنیا را راحت می‌شود در اینترنت یافت، اما مشکلات زیادی هم به همراه این نوع فایل‌ها وجود دارند:
- گاها با فیلم دریافت شده هماهنگ نیستند.
- عموما با فرمت windows-1256 تهیه می‌شوند که برای استفاده از آن‌ها در سیستم‌های مختلف بهتر است به UTF8 تبدیل شوند.
- اکثر برنامه‌های موجود برای کار با زیر نویس‌ها و ویرایش آن‌ها، درکی از یونیکد ندارند.
- عموما نیاز است جهت استفاده از آن‌ها در یک جمع، تعدادی از سطور آن‌ها را با حفظ شماره بندی فایل، حذف کرد!
و ...

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

الف) تغییر encoding فایل دریافتی به UTF-8
هنگام گشودن اکثر فایل‌های زیر نویس فارسی با تصویر زیر روبرو خواهید شد:



برای تبدیل آن به فرمت یونیکد تنها کافی است بر روی دکمه‌ی To UTF-8 کلیک کنید. در این حالت نتیجه به صورت زیر خواهد بود:



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



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

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



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



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

ز) پیدا کردن زیرنویس یک فایل بر اساس امضای دیجیتال آن
سایت opensubtitles.org یک API را جهت پیدا کردن زیرنویسی مطابق با هش یک فایل ویدیویی ارائه داده است، که در برنامه‌ی جاری، کلاینتی برای آن تهیه شده است:



فقط کافی است فایل ویدیویی خود را در این قسمت انتخاب نمائید. برنامه هش فایل را محاسبه کرده و سپس با کمک سرویس XML-RPC سایت opensubtitles.org سعی در یافتن زیرنویس هماهنگ با آن خواهد کرد. در اینجا دیگر مهم نیست نام فایل انتخابی چیست؛ امضای دیجیتال آن مهم است. برای دریافت موارد مورد نظر، ابتدا آن‌ها را تیک زده و سپس بر روی دکمه‌ی دریافت کلیک کنید. کلیک راست بر روی ردیف مورد نظر نیز این امکان دریافت را لحاظ کرده است.
همچنین قسمتی هم برای آپلود زیرنویس به این سایت پیش بینی شده است (لطفا مصرف کننده‌ی صرف یا به قولی لیچر نباشید!)



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

پیشنیاز استفاده از این برنامه، نصب دات نت فریم ورک 4 است که اگر از ویندوز 7 استفاده می‌کنید، جزو به روز رسانی‌های اختیاری آن است و در حالت کلی نسخه‌ی کامل 32 بیتی و 64 بیتی آن از این آدرس قابل دریافت است.


مطالب
انتقال SVN به یک سیستم جدید

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

الف) دریافت و نصب Visual SVN server
یا می‌توان SVN خالص را از سایت آن دریافت کرد و یا جهت سهولت کار و همچنین دسترسی به یک کنسول مدیریتی می‌توان برنامه‌ی رایگان Visual SVN server را از آدرس زیر دریافت و نصب کرد:

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



ب)دریافت و نصب TortoiseSVN

نصب آن نکته‌ی خاصی ندارد. اما یک سری نکته‌ی ریز پس از نصب آن بهتر است رعایت شود که در ادامه ذکر می‌شود:

ج) دریافت و نصب برنامه‌ی WinMerge
برنامه‌ی Diff پیش فرض TortoiseSVN آنچنان قوی نیست. به همین جهت می‌توان برنامه‌ی WinMerge را با آن یکپارچه کرد. برای این منظور ابتدا آن‌را دریافت نمائید:

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

C:\Program Files (x86)\WinMerge\WinMergeU.exe -e -x -ub -dl %bname -dr %yname %base %mine



بدیهی است مسیر WinMergeU.exe مطابق مسیر نصب در سیستم شما باید تنظیم شود.

د) تنظیم مسیر تحت نظر قرار گرفتن سیستم
TortoiseSVN به صورت پیش فرض کل سیستم را جهت مشاهده‌ی تغییرات تحت نظر قرار می‌دهد که گاهی باعث کاهش کارآیی آن خواهد شد. برای رفع این مشکل می‌توان مسیرهایی را که پروژه‌های شما در آن قرار دارند را به آن معرفی نمود تا بار کلی سیستم کاهش یابد.



همانطور که در شکل نیز ملاحظه می‌کنید، Include path مقدار دهی شده است.

ه) مشخص سازی پسوندهایی که بهتر است از آن‌ها صرفنظر شود
به برگه‌ی general تنظیمات TortoiseSVN مراجعه کرده و در قسمت global ignore pattern آن، موارد زیر را وارد نمائید:



این موارد شامل پروژه‌های دات نت، دلفی ، VC و امثال آن است و همچنین یک سری فایل بایناری که عموما با پروژه‌های برنامه نویسی نیازی به ثبت نگارش آن‌ها نیست.

*.dcu *.~* dcu temp *.exe *.zip *.bkm *.ddp *.cfg *.dof *.dsk *.ini *.hlp *.gid *.bmp *.png *.gif ~* *.log bin debug release *.map *.chm *.bkf Thumbs.db *.mdb .obj *.elf *.stat *.ddp *.bpl *.map *.GID *.hlp *.opt *.dll *.raw *.BIN *.obj *.pdb *.scc Debug Release *.xml obj *.~* *.backup *.INI *.ArmLog *.KeyLog *.NanoLog *.Stats *.PreARM *.old *.drc *.*~ *.doc *.pdf *.bmp *.jpg *.MRW *.NEF *.ORF *.psd *.X3F __history *.local *.identcache *.bak Thumbs.db *.ldb *.dex *.rar DllDcu *.lck CVS cvs *.txt *.TXT *.jdbg *.HLP *.KWF *.xls *.cnt *.dsm *.dti *.tmp *.lnk *.cbk *.mes *.suo *.ncb *.user _ReSharper.* [Bb]in obj [Dd]ebug [Rr]elease *.aps *.eto


در همین برگه، اگر هنوز از VS2003 استفاده می‌کنید، تیک مربوط به استفاده از _svn بجای .svn را قرار دهید تا VS.Net با پوشه‌های مدیریتی ذکر شده مشکل پیدا نکند.

و) نصب افزونه‌های SVN سازگار با VS.Net
یا می‌توان از افزونه‌ی Visual SVN استفاده کرد (که رایگان نیست) و یا AnkhSVN که رایگان و سورس باز است.
ولی در کل یک مورد را بیشتر نصب نکنید. علت هم کند شدن VS.Net است به دلیل فعالیت‌های پشت صحنه‌ی هر کدام از این افزونه‌ها که زیاده روی در تعداد آن‌ها گاها باعث کرش هم می‌شود. بنابراین همان یک مورد کافی است.

ز) Import مخزن‌های قبلی
تا اینجا مقدمات کار فراهم شد. اکنون نوبت به import مخزن‌های بجا مانده از سیستم قبلی است. برای اینکار مطابق شکل زیر، گزینه‌ی import existing repositories را انتخاب کرده و مسیر مخزن‌های قبلی خود را باید معرفی نمود (به ازای هر کدام یکبار باید این عملیات صورت گیرد).



پس از انجام این مراحل یکبار باید سیستم reboot شود و اکنون همه چیز مثل قبل خواهد شد!


نکته:
اگر مسیر ریشه مخزن‌های جدید با مسیر آن‌ها در سیستم قبلی متفاوت است، هنگام commit کارهای خود با خطای زیر متوقف خواهید شد:
Commit failed (details follow): Unable to open an ra_local session to URL
Unable to open repository 'file:///C:/Repositories/tracking/trunk'




اشکالی ندارد! برای رفع آن باید از گزینه‌ی relocate مربوط به TortoiseSVN استفاده کرد.
بر روی پوشه کاری پروژه خود کلیک راست کرده، انتخاب گزینه‌ی TortoiseSVN و سپس انتخاب گزینه‌ی Relocate آن باید صورت گیرد. در اینجا می‌توان مسیر جدید ریشه اصلی مخزن را در سیستم جدید معرفی کرد.



مطالب
React 16x - قسمت 29 - احراز هویت و اعتبارسنجی کاربران - بخش 4 - محافظت از مسیرها
در قسمت قبل، دکمه‌ی new movie را برای کاربران وارد نشده‌ی به سیستم، از صفحه‌ی نمایش لیست فیلم‌ها، مخفی کردیم. اما ... اگر آدرس http://localhost:3000/movies/new مستقیما در مرورگر وارد شود، هنوز هم برای عموم کاربران قابل دسترسی است.


روش محافظت از مسیریابی‌های تعریف شده‌ی در برنامه

شبیه به روشی را که در قسمت قبل، برای انتقال شیء user، به مسیریابی کامپوننت Movies استفاده کردیم:
<Route
     path="/movies"
     render={props => <Movies {...props} user={this.state.currentUser} />}
/>
در اینجا نیز می‌توان برای محافظت از یک مسیریابی، استفاده کرد. به همین جهت به app.js مراجعه کرده و مسیریابی فعلی کامپوننت MovieForm را:
<Route path="/movies/:id" component={MovieForm} />
به صورت زیر تغییر می‌دهیم:
<Route
  path="/movies/:id"
  render={props => {
    if (!this.state.currentUser) {
      return <Redirect to="/login" />;
    }
    return <MovieForm {...props} />;
  }}
/>
اینبار نیز بجای ویژگی component، از ویژگی render استفاده می‌کنیم تا بتوان در اینجا به صورت پویا، کدنویسی کرد. ابتدا بررسی می‌کنیم که آیا کاربر جاری تنظیم شده‌است؟ اگر خیر، او را به صفحه‌ی لاگین هدایت می‌کنیم؛ در غیراینصورت، همان کامپوننت MovieForm را به همراه تمام props مرتبط با آن، بازگشت می‌دهیم.

اکنون اگر این تغییرات را ذخیره کرده و در حالت Logout، مسیر http://localhost:3000/movies/new را مستقیما درخواست دهیم، به صفحه‌ی لاگین هدایت خواهیم شد.


ایجاد کامپوننتی با قابلیت استفاده‌ی مجدد، برای محافظت از مسیریابی‌ها

هرچند روشی که تا اینجا برای محافظت از مسیریابی‌ها معرفی شد، بدون مشکل کار می‌کند، اما اگر قرار باشد برای تمام مسیریابی‌های اینگونه، استفاده شود، به تکرار بیش از اندازه‌ی کدهای یکسانی خواهیم رسید. به همین جهت می‌توان این منطق را تبدیل به یک کامپوننت با قابلیت استفاده‌ی مجدد کرد؛ تا دیگر نیازی به تکرار این if/else‌ها نباشد. برای این منظور، فایل جدید src\components\common\protectedRoute.jsx را ایجاد می‌کنیم. کامپوننت جدید protectedRoute را هم در پوشه‌ی common قرار داده‌ایم؛ چون وابستگی به دومین این برنامه نداشته و می‌تواند در سایر برنامه نیز مورد استفاده قرار گیرد. سپس با استفاده از میانبرهای imrc و sfc، یک کامپوننت تابعی بدون حالت را به نام ProtectedRoute ایجاد کرده و در آن، همان کامپوننت اصلی Route را بازگشت می‌دهیم. بنابراین هر زمانیکه از ProtectedRoute استفاده شود، خروجی آن، همان کامپوننت استاندارد Route خواهد بود که اینبار قرار است از وضعیت کاربر جاری وارد شده‌ی به سیستم، مطلع باشد. به همین جهت در اولین قدم، همان قطعه کد Route فوق را که به همراه if/else نوشتیم، از فایل app.js کپی کرده و به اینجا، داخل متد رندر کامپوننت، منتقل می‌کنیم. سپس شروع می‌کنیم به متغیر کردن عباراتی که در آن به صورت صریح و ثابت، مقدار دهی شده‌اند تا به یک کامپوننت با قابلیت استفاده‌ی مجدد برسیم:
import React from "react";
import { Route, Redirect } from "react-router-dom";
import * as auth from "../../services/authService";

const ProtectedRoute = ({ path, component: Component, render, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props => {
        if (!auth.getCurrentUser())
          return (
            <Redirect
              to={{
                pathname: "/login",
                state: { from: props.location }
              }}
            />
          );
        return Component ? <Component {...props} /> : render(props);
      }}
    />
  );
};

export default ProtectedRoute;
- در ابتدا بجای ذکر props بعنوان پارامتر این کامپوننت، از طریق Object Destructuring، خواصی را که قرار است به صورت props دریافت کنیم، مشخص کرده‌ایم. مزیت اینکار، مشخص شدن اینترفیس این کامپوننت به نحو واضحی است. برای مثال بجای ذکر مقدار ویژگی path، به صورت یک رشته‌ی ثابت، آن‌را از طریق یک متغیر دریافت می‌کنیم.
- در این کامپوننت نیاز است اطلاعات کاربر جاری وارد شده‌ی به سیستم در دسترس باشد. یا می‌توان آن‌را به عنوان یکی از خواص props دریافت کرد و یا همانند این مثال، امکان دریافت مستقیم آن از  authService نیز وجود دارد.
- در ادامه اگر CurrentUser مقدار دهی نشده باشد، کامپوننت Redirect را که کاربر را به صفحه‌ی لاگین هدایت می‌کند، بازگشت می‌دهیم. در غیراینصورت نیاز است یک کامپوننت را بجای برای مثال MovieForm، بازگشت دهیم. علت استفاده‌ی از component: Component این است که React انتظار دارد، کامپوننت‌ها با نام بزرگ شروع شوند. به همین جهت خاصیت component را از props دریافت کرده و آن‌را به Component تغییر نام می‌دهیم.
- زمانیکه از کامپوننت Route استاندارد استفاده می‌شود، یا از ویژگی component آن استفاده می‌شود و یا از ویژگی render آن که یک تابع است، تا بتوان داخل آن، کدهای پویایی را درج کرد. به همین جهت ممکن است که مقدار متغیر کامپوننت دریافت شده، نال باشد. بنابراین در اینجا بررسی می‌شود که آیا Component، مقدار دهی شده‌است یا خیر؟ اگر بله، همان کامپوننت را به همراه props آن بازگشت می‌دهیم. در غیراینصورت، متد render مقدار دهی شده را به همراه props ارسالی به آن، بازگشت خواهیم داد.
- علت وجود پارامتر rest نیز این است که این کامپوننت علاوه بر ویژگی‌هایی که تاکنون پیش بینی کرده‌ایم، ممکن است در آینده ویژگی‌های دیگری را نیز نیاز داشته باشد. به همین جهت مابقی آن‌ها را توسط {rest...}، به صورت خودکار در اینجا درج می‌کنیم. برای نمونه در اینجا ذکر path={path} را مشاهده نمی‌کنید؛ چون توسط همان {rest...} به صورت خودکار تامین می‌شود.

اکنون به app.js بازگشته و کدهای قبلی را با این کامپوننت جدید ProtectedRoute، جایگزین می‌کنیم:
import ProtectedRoute from "./components/common/protectedRoute";
// ...

<ProtectedRoute path="/movies/:id" component={MovieForm} />
اینبار نحوه‌ی تعریف ProtectedRoute، همانند نحوه‌ی تعریف کامپوننت Route استاندارد است؛ با این تفاوت که این کامپوننت در پشت صحنه، از وضعیت کاربر جاری سیستم مطلع است و بر اساس آن واکنش نشان می‌دهد.


مدیریت بازگشت کاربران، پس از لاگین به سیستم

پس از خروج از برنامه، اگر سعی در ویرایش یکی از فیلم‌های موجود کنیم، به صفحه‌ی لاگین هدایت خواهیم شد. پس از لاگین موفق، مجددا به ریشه‌ی سایت بازگشت داده می‌شویم و نه به صفحه‌ای که پیش از لاگین، مدنظر کاربر بوده‌است. برای رفع این مشکل نیاز است بتوان به آدرس قبلی درخواستی، دسترسی یافت و این مورد توسط سیستم مسیریابی، به کامپوننت‌ها به صورت خودکار تزریق می‌شود. برای مثال اگر در کامپوننت ProtectedRoute، مقدار شیء props دریافتی را لاگ کنیم:
  return (
    <Route
      {...rest}
      render={props => {
        console.log(props);
و سپس بر روی لینک به مشاهده‌ی جزئیات و ویرایش یک فیلم کلیک کنیم، تصویر زیر حاصل می‌شود:


همانطور که مشخص است، شیء location دریافتی از props، به همراه اطلاعات آدرسی است که پیش از هدایت خودکار به صفحه‌ی لاگین، درخواست کرده بودیم. به همین جهت یک چنین تنظیمی، در تعاریف کامپوننت ProtectedRoute درنظر گرفته شده‌اند:
<Redirect
              to={{
                pathname: "/login",
                state: { from: props.location }
              }}
            />
در کامپوننت Redirect، مقدار to می‌تواند یک رشته و یا یک شیء باشد. اگر حالت انتساب یک شیء را انتخاب کردیم، خاصیت pathname آن مانند قبل است و مکان نهایی Redirect را مشخص می‌کند. اما کار خاصیت state آن، ارسال اطلاعاتی اضافی است به کامپوننتی که قرار است کار Redirect به آن صورت گیرد. برای مثال در تنظیم فوق، شیء ای که دارای خاصیت from و با مقدار props.location است، به صورت خودکار به کامپوننت مقصد ارسال می‌شود.
اکنون که این شیء، به کامپوننت لاگین، پس از Redirect خودکار ارسال می‌شود، نیاز است به src\components\loginForm.jsx مراجعه کرده و تغییرات زیر را اعمال کنیم:
  doSubmit = async () => {
    try {
      const { data } = this.state;
      await auth.login(data.username, data.password);

      const { state } = this.props.location;
      window.location = state ? state.from.pathname : "/";
    } catch (ex) {
      //...
در اینجا خاصیت state، از شیء location تزریق شده‌ی به props این کامپوننت، استخراج می‌شود. سپس با مقدار دهی window.location به from.pathname آن، کار هدایت کاربر را پس از لاگین موفق، به آدرس قبلی مدنظر او، انجام می‌دهیم.

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


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

آخرین تغییری را که در اینجا اعمال خواهیم کرد، رفع مشکل امکان مشاهده‌ی مجدد صفحه‌ی لاگین، با وارد کردن مستقیم آدرس آن در مرورگر، پس از ورود موفقیت آمیز به سیستم است. برای این منظور، ابتدای متد رندر کامپوننت فرم لاگین را به صورت زیر تغییر می‌دهیم تا اگر کاربر، پیشتر به سیستم وارد شده بود، به صورت خودکار به ریشه‌ی سایت هدایت شده و مجددا فرم لاگین برای او رندر نشود:
import { Redirect } from "react-router-dom";
//...


  render() {
    if (auth.getCurrentUser()) return <Redirect to="/" />;


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-29-backend.zip و sample-29-frontend.zip
مطالب
React 16x - قسمت 6 - کامپوننت‌ها - بخش 3 - یک تمرین
در این قسمت می‌خواهیم دانسته‌های 5 قسمت قبل را در طی یک تمرین کنار هم قرار داده و مرور کنیم.


برپایی ساختار ابتدایی پروژه‌ی تمرین

ابتدا یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app sample-05
> cd sample-05
> npm start
سپس بسته‌های بوت استرپ و font-awesome را نیز در آن نصب می‌کنیم:
> npm install --save bootstrap
> npm install --save font-awesome

در ادامه نیاز است فایل‌های CSS این کتابخانه‌ها و قلم‌های وب را import کنیم. به همین جهت ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";
در نهایت کار مدیریت این فایل‌ها و قرار دادن آن‌ها در بسته‌ی نهایی برنامه، توسط webpack به صورت خودکار انجام می‌شود.

همچنین به فایل index.css هم مراجعه کرده و یک padding را به بالای صفحه اضافه می‌کنیم؛ تا اطلاعات نمایش داده شده، با کمی فاصله از لبه‌ی مرورگر رندر شوند:
body {
  margin: 0;
  padding: 20px 0 0 0;
  font-family: sans-serif;
}

پس از نصب و import این کتابخانه‌های ثالث، به فایل App.js مراجعه کرده و کلاس container اصلی بوت استرپ را در آن تعریف می‌کنیم تا در برگیرنده‌ی محتوای برنامه شود:
  return (
    <main className="container">
      <h1>Hello world!</h1>
    </main>
  );
همانطور که در قسمت چهارم نیز بحث شد، برای ذکر classهای عناصر در React، از خاصیت className استفاده می‌شود.


معرفی سرویس‌های داده‌ی برنامه

کدهای نهایی این قسمت را از فایل پیوست شده‌ی در انتهای مطلب، می‌توانید دریافت کنید. در اینجا یک پوشه‌ی src\services تعریف شده‌است که داخل آن دو فایل fakeGenreService.js و fakeMovieService.js قرار دارند. این فایل‌ها، منبع داده‌ی درون حافظه‌ای مثال تمرین ما هستند.
سرویس fakeGenre چنین ساختاری را دارد و ژانرهای سینمایی، مانند اکشن، کمدی و غیره در آن لیست شده‌اند:
export const genres = [
  { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
  // ...
];
این سرویس دارای متد ()getGenres، برای بازگشت لیست کامل genres است. علت ذکر خاصیت id با یک _، روش نامگذاری خاصیت id در mongo-db است.

و سرویس fakeMovie که دارای ساختار کلی زیر است، لیست 9 فیلم سینمایی را به همراه دارد:
const movies = [
  {
    _id: "5b21ca3eeb7f6fbccd471815",
    title: "Terminator",
    genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
    numberInStock: 6,
    dailyRentalRate: 2.5,
    publishDate: "2018-01-03T19:04:28.809Z"
  },

  //...
];
به علاوه این سرویس دارای متدهای ()getMovies برای دریافت لیست فیلم‌ها، getMovie(id) برای بازگشت یک فیلم خاص، saveMovie(movie) برای افزودن یک فیلم جدید به لیست و deleteMovie(id) برای حذف یک فیلم از لیست درون حافظه‌ای سرویس جاری است.


ایجاد کامپوننت Movies برای نمایش لیست فیلم‌ها در برنامه

اکنون می‌خواهیم یک کامپوننت جدید را به نام Movies در فایل جدید src\components\movies.jsx ایجاد کنیم، تا لیست فیلم‌های سرویس fakeMovieService را نمایش دهد. برای اینکار مراحل زیر را طی خواهیم کرد:
- نمایش ساده‌ی لیست فیلم‌ها توسط یک جدول. برای دریافت لیست اشیاء موجود در fakeMovieService، از متد ()getMovies آن می‌توان استفاده کرد.
- اضافه کردن یک دکمه‌ی حذف، به هر ردیف، به نحوی که با کلیک بر روی آن، آن ردیف حذف شود.
- نمایش یک پیام بالای جدول که تعداد فیلم‌های موجود در سرویس درون حافظه‌ای را نمایش می‌دهد. همچنین پس از حذف تمام ردیف‌ها، باید پیام «فیلمی موجود نیست» را نمایش دهد.

خروجی نهایی مثال ما به صورت زیر است:


و اگر تمام آیتم‌های آن‌را حذف کنیم، چنین پیامی نمایش داده می‌شود:


پس از ایجاد فایل خالی جدید movies.jsx در پوشه‌ی جدید components، با استفاده از «simple react snippets» نصب شده‌ی در VSCode، یکبار imrc را تایپ کرده (مخفف import react component است) و سپس دکمه‌ی tab را فشار می‌دهیم، در آخر اینکار را برای cc نیز تکرار می‌کنیم (مخفف create class است) تا importها و سپس ساختار ابتدایی کامپوننت React ما تشکیل شوند. نام این کامپوننت را هم Movies که با حرف بزرگ شروع می‌شود، وارد می‌کنیم.

اکنون مجددا به App.js مراجعه می‌کنیم و بجای Hello world ای که نمایش دادیم، کامپوننت Movies را اضافه می‌کنیم. برای این منظور ابتدا import آن‌را به ابتدای فایل اضافه می‌کنیم:
import Movies from "./components/movies";
سپس متد return آن‌را جهت درج المان کامپوننت Movies اصلاح خواهیم کرد:
return (
    <main className="container">
      <Movies />
    </main>
  );


دریافت لیست اشیاء فیلم‌ها از سرویس fakeMovieService

برای دریافت لیست اشیاء فیلم‌ها، ابتدا تعریف سرویس آن‌را به ابتدای کامپوننت Movies اضافه می‌کنیم:
 import { getMovies } from "../services/fakeMovieService";
در اینجا از {} استفاده شده، چون یک named export را import کرده‌ایم.

سپس خاصیت state را جهت تعریف خاصیت movies که با متد ()getMovies سرویس fakeMovieService مقدار دهی می‌شود، به نحو زیر تکمیل می‌کنیم:
 state = {
    movies: getMovies()
  };
البته این روش مقدار دهی اولیه‌ی خاصیت state، برای دریافت اطلاعات سرویس‌ها، هرچند در اینجا بدون مشکل کار می‌کند، اما بهتر است توسط component life cycle hooks مدیریت شود که در قسمت‌های بعدی بیشتر به جزئیات آن‌ها خواهیم پرداخت.


نمایش لیست فیلم‌ها، به همراه مدیریت حذف هر ردیف

در ادامه، کدهای کامل و تکمیل شده‌ی این کامپوننت را ملاحظه می‌کنید:
import React, { Component } from "react";

import { getMovies } from "../services/fakeMovieService";

class Movies extends Component {
  state = {
    movies: getMovies()
  };

  handleDelete = movie => {
    const movies = this.state.movies.filter(m => m._id !== movie._id);
    this.setState({ movies });
  };

  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    return (
      <React.Fragment>
        <p>Showing {count} movies in the database.</p>
        <table className="table">
          <thead>
            <tr>
              <th>Title</th>
              <th>Genre</th>
              <th>Stock</th>
              <th>Rate</th>
              <th />
            </tr>
          </thead>
          <tbody>
            {this.state.movies.map(movie => (
              <tr key={movie._id}>
                <td>{movie.title}</td>
                <td>{movie.genre.name}</td>
                <td>{movie.numberInStock}</td>
                <td>{movie.dailyRentalRate}</td>
                <td>
                  <button
                    onClick={() => this.handleDelete(movie)}
                    className="btn btn-danger btn-sm"
                  >
                    Delete
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}

export default Movies;
توضیحات:
همانطور که در ابتدای بحث نیز ذکر شد، هدف از این تمرین، مرور قسمت‌های قبل است و تمام نکات زیر را در قسمت‌های پیشین، با جزئیات بیشتری بررسی کرده‌ایم:

- ابتدا خاصیت state و سپس خاصیت movies شیء منتسب به آن، با لیست فیلم‌های موجود در سرویس مرتبط، مقدار دهی شده‌اند.
- سپس در ابتدای متد render، کار رندر شرطی انجام شده‌است. اگر تعداد فیلم‌های دریافتی صفر بود، پیام «فیلمی در بانک اطلاعاتی موجود نیست» نمایش داده می‌شود و در غیراینصورت، جدول اصلی بوت استرپی برنامه رندر خواهد شد.
در اینجا چون از خاصیت طول آرایه‌ی فیلم‌ها در چندین قسمت قرار است استفاده شود، آن‌را توسط Object Destructuring به یک متغیر نسبت داده‌ایم. همچنین توسط یک نام مستعار هم خاصیت length را با نام جدید count استفاده می‌کنیم.
- در ادامه بازگشت React.Fragment را مشاهده می‌کنید. علت اینجا است که نمی‌خواهیم div اضافه‌تری را در UI رندر کنیم. React.Fragment سبب می‌شود تا بتوانیم چندین فرزند را به المان جاری تبدیل شده‌ی به کدهای جاوا اسکریپتی اضافه کنیم، بدون اینکه خودش به المانی ترجمه شود.
- پس از return، یک () قابل مشاهده‌است. چون خروجی return ما چند سطری است، اگر در سطری که return قرار می‌گیرد، اطلاعاتی درج نشود، موتور جاوا اسکریپت آن‌را با یک سمی‌کالن خاتمه خواهد داد! و دیگر سطرهای بعدی دیده نمی‌شوند و پردازش نخواهند شد. به همین جهت از روش ذکر یک () پس از return در فایل‌های jsx زیاد استفاده می‌شود.
- در ابتدای return، همان خاصیت count را نمایش می‌دهیم.
- سپس کار رندر جدول اصلی برنامه که با کلاس‌های جداول بوت استرپ نیز مزین شده، انجام شده‌است. در React برای عدم تداخل ویژگی class با نام از پیش رزرو شده‌ی class، از خاصیت className برای ذکر کلاس‌های CSSای استفاده می‌شود.
- قسمت thead این جدول مشخص است و سرستو‌ن‌های جدول را مشخص می‌کند.
- پس از آن نیاز است ردیف‌های جدول را رندر کنیم. این‌کار را توسط متد Array.map، با نگاشت هر آیتم آرایه‌ی this.state.movies، به یک tr جدول انجام داده‌ایم.
- React برای اینکه بتواند DOM مجازی خودش را کنترل کند، نیاز دارد عناصر موجود در آن‌را به صورت منحصربفردی تشخص دهد. به همین جهت در اینجا ذکر key را بر روی المان tr که با movie._id مقدار دهی شده‌است، مشاهده می‌کنید.
- رندر مقادیر سلو‌ل‌های ردیف‌ها توسط درج {} و سپس ذکر مقداری از شیء movie دریافتی توسط متد Array.map انجام می‌شود.
- در اینجا ستون رندر دکمه‌ی Delete را نیز مشاهده می‌کنید. برای مدیریت this در آن و دسترسی به شیء movie جاری (ارسال پارامتر به رویداد گردان آن) و همچنین دسترسی به شیء this کلاس جاری برای کار با آرایه‌ی this.state.movies، از روش arrow functions برای تعریف رویدادگردان onClick استفاده کرده‌ایم.
- در متد handleDelete، یک آرایه‌ی جدید را که id ردیف‌های آن با id شیء ردیف انتخابی یکی نیست، بازگشت می‌دهیم. انتساب این آرایه‌ی جدید به آرایه‌ی this.state.movies، تغییری را در برنامه‌های React ایجاد نمی‌کند. در اینجا باید توسط متد this.setState که از کلاس پایه‌ی extends Component دریافت می‌شود، خاصیت movies را بازنویسی کرد تا React از تغییرات مطلع شده و DOM مجازی جدیدی را با مقایسه‌ی با نمونه جدید، محاسبه کرده و به DOM اصلی، جهت به روز رسانی UI اعمال کند.
- البته در اینجا this.setState({ movies }) را بجای this.setState({ movies: movies }) مشاهده می‌کنید. علت اینجا است که اگر عبارات key و value یکی باشند، می‌توان تنها همان عبارت key را جهت حذف تکرار واژه‌ها، ذکر کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-05.zip
مطالب
الگوریتم‌های داده کاوی در SQL Server Data Tools یا SSDT - قسمت چهارم - الگوریتم‌ Clustering یا خوشه بندی
در قسمت قبل با الگوریتم های Decision trees و Linear Regression آشنا شدیم. در این قسمت به الگوریتم Clustering یا خوشه بندی می‌پردازیم.

مقدمه


تصور کنید شما بچه‌ای هستید که با یک کیسه تیله روی زمین نشسته‌اید. لحظه‌ای که تیله‌ها را از کیسه روی زمین می‌ریزید، متوجه می‌شوید که تیله‌ها، چهار رنگ دارند (آبی، قرمز، سبز و زرد). تیله‌ها را در چهار گروه با توجه به رنگ‌هایشان قرار می‌دهید. اما بعد متوجه می‌شوید که اندازه بعضی از تیله‌ها متوسط و برخی بزرگ و تعدادی هم کوچک هستند. شما تصمیم می‌گیرید که تیله‌های کوچک و متوسط، در کنار یکدیگر و در یک گروه قرار گیرند؛ اما تیله‌های بزرگ را در یک گروه دیگر قرار می‌دهید. چرا که تنها یکی از آن‌ها را باید به هر بازیکن داد. تبریک می‌گویم! شما یک عمل خوشه بندی را انجام دادید.
حال زمانیکه قدری با دقت بیشتری به خوشه بندی خود نگاه می‌کنید، متوجه می‌شوید که برخی از تیله‌ها کریستالی، برخی دیگر سه پر و چهارپر، بعضی از آن‌ها صاف و صیقلی و بعضی دیگر دارای خراش می‌باشند. اینجاست که قدری سردرگم می‌شوید. آیا باید همان گروه بندی ساده براساس رنگ و اندازه را مدنظر قرار دهید، یا بهتر است عوامل دیگری مانند سبک، مواد تشکیل دهنده و وضعیت ظاهری را نیز اضافه کنید؟
خوشه بندی، یک عمل انسانی راحت، طبیعی و حتی می‌شود گفت اتوماتیک برای مواجه شدن با مجموعه ویژگی‌های کوچک می‌باشد. اما همینطور که ویژگی‌ها بیشتر می‌شوند، حل مساله برای انسان خیلی سخت و غیرممکن می‌شود. ذهن یک انسان معمولی، تقریبا قادر به درنظر گرفتن 5 یا 6 بُعد می‌باشد. این درحالی است که مجموعه داده‌های مدرن گاها دارای ده‌ها بعد (اگر نگوییم صدها) می‌باشند.
الگوریتم خوشه بندی مایکروسافت، گروه بندی‌هایی ذاتی را داخل مجموعه داده شما پیدا می‌کند که ممکن است به چشم نیایند. به عبارت دیگر، متغیرهای پنهانی را که به طور دقیق داده‌های شما را خوشه بندی می‌کنند، پیدا می‌نماید. برای مثال فرض کنید که شما جزیی از یک گروه بزرگ مسافران هستید که در بخش نوار نقاله حمل بار در فرودگاه منتظر برداشتن چمدان می‌باشید. متوجه می‌شوید که درصد قابل توجهی از مسافران شلوار کوتاه پوشیده و پوستشان در اثر آفتاب قدری تیره‌تر شده است؛ درحالیکه مابقی مسافران لباس گرم مانند ژاکت و کت به تن دارند. بنابراین به یک حقیقت پی می‌برید. یک گروه از نواحی گرمسیری آمده‌اند و دیگری از یک جای سرد و مرطوب. این همان متغیر پنهان است.

الگوریتم Clustering یا خوشه بندی مایکروسافت  

الگوریتم خوشه بندی مایکروسافت رفتارهای خاصی را در مواجه با نوع ویژگی‌ها از خود نشان می‌دهد. در ارتباط با ستون‌های ورودی (Input) و ورودی-خروجی (Predict) مانند آنچه قبلا گذشت عمل می‌کند. البته با یک تفاوت و آن اینکه ستون‌های ورودی-خروجی در حین پیش بینی قابل انتخاب هستند؛ حال آنکه ستون‌های ورودی اینطور نیستند. ستون‌هایی که فقط خروجی (Predict Only) هستند، در طی فاز خوشه بندی برای آموزش مدل به کار نمی‌روند.
همانطور که قبلا نیز اشاره شد، خوشه بندی، رایج‌ترین عملی است که با این الگوریتم انجام می‌دهند. بنابراین جهت کشف خوشه بندی‌ها در یک مجموعه داده می‌توان این الگوریتم را روی مجموعه داده اعمال کرده و خوشه بندی‌های کشف شده را برچسب زد. بعد از برچسب زدن می‌توان از آن، جهت گزارش گیری و تحلیل داده‌ها استفاده نمود. از آنجا که این الگوریتم سربار پردازشی و حافظه‌ای زیادی دارد، بنابراین در رابطه با مجموعه داده‌های بزرگ (رکوردهای میلیونی و پیچیده) بهتر است که فقط بخش کوچکی از داده را برای آموزش استفاده کرده (که البته کافی و وافی است) و از طریق آن‌ها ویژگی‌های خوشه بندی را کشف کرد.
توسط این الگوریتم می‌توان مدل را تجزیه-تحلیل نمود و نابهنجاری‌ها را نیز تشخیص داد.

محتوای مدل خوشه بندی

درک محتوای مدل خوشه بندی بسیار ساده است. شکل زیر دیاگرام خوشه بندی یا Cluster Diagram می‌باشد. همانطور که در شکل آمده است SSAS در نشان دادن نام هر گره به خوبی عمل نمی‌کند زیرا هر گره توسط Cluster و یک ایندکس نشان داده می‌شود و نام معناداری برای آن در نظر نمی‌گیرد. برای مثال خوشه مربوط به تیله‌های آبی بزرگ سه پر (برای مثال Cluster2، Cluster1 و ....).


بنابراین برای برچسب زدن مناسب برروی هر گره باید به شکل زیر عمل کرد:
  • مرور اجمالی مدل: توسط دو برگه اول یعنی Cluster Diagram و Cluster Profiles می‌توان توپولوژی مدل خوشه بندی را به دست آورد. در برگه Cluster Diagram هر خوشه یک گره را تشکیل می‌دهد که براساس شباهت به یکدیگر متصل شده‌اند. بدیهی است خوشه‌هایی که در ضعیف‌ترین ارتباط هم به یکدیگر متصل نیستند، هیچگونه شباهتی ندارند. براساس میزان شباهت، نوار اتصال بین گره‌ها، تیره‌تر یا روشن‌تر می‌گردد. همانطور که در شکل فوق مشخص است هرچه این نوار تیره‌تر باشد، بیانگر شباهت بیشتر بین دو خوشه است. Cluster Profiles یک ستون را برای هر خوشه و یک سطر را برای هر ویژگی درنظر می‌گیرد. درصورتیکه یک ویژگی برای شما جالب توجه باشد می‌توانید به صورت افقی توزیع آن را در خوشه‌های مختلف مشاهده کنید. هر زمانیکه آیتمی نظر شما را جلب کرد می‌توان به سلول‌های مجاور یا سلول‌های هم خوشه آن نگاه کرد و مفهوم آن خوشه را بیشتر درک نمود. با کلیک برروی هر یک از سلول‌ها می‌توان جزییات مربوط به آن سلول را مشاهده کرد. برای مثال می‌توان فهمید این خوشه براساس چه شروطی ایجاد شده‌است. شکل زیر نمایی از Cluster Profiles را نشان می‌دهد. همانطور که در قسمت قبل نیز بحث شد، نوارهای هیستوگرام مربوط به ویژگی‌های گسسته بوده و نوارهای الماسی نشان دهنده ویژگی‌های پیوسته می‌باشند.


  • انتخاب یک خوشه و تشخیص وجه تمایز آن: از برگه Cluster Diagram شروع می‌نماییم. یک راه این است که ببینیم کدام خوشه‌ها، قوی‌ترین ارتباط را دارند و یکی از آن‌ها را انتخاب نماییم. راه دیگر این است که خوشه‌ای را انتخاب کنیم که به نظر دور  از بقیه خوشه‌ها است. پس از انتخاب خوشه موردنظر به تب Cluster Characteristics می‌رویم. همانطور که در شکل زیر مشخص است این بخش مشخصات حالات مختلف یک خوشه را توسط نمودار احتمال با روند کاهشی  نشان می‌دهد. بنابراین می‌توان متوجه شد چه ویژگی هایی و با چه احتمالی سبب ایجاد یک خوشه شده‌اند.

    ممکن است تعدادی ویژگی با احتمال بالا در یک خوشه وجود داشته باشند اما سوال اینجاست که از کجا معلوم که تمام این ویژگی‌ها در خوشه‌های دیگر نیز این احتمال را نداشته باشند؟ برای اینکه متوجه شویم که بیشتر چه ویژگی سبب وجه تمایز این خوشه شده‌است باید به برگه Cluster Discrimination مراجعه کنیم که نمونه‌ای از آن در شکل زیر آمده است.

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

  • تشخیص چگونگی تمایز یک خوشه از خوشه‌های نزدیک به آن: حال می‌توان با اطلاعاتی که تا به حال کسب کرده‌ایم یک خوشه را به صورت دقیق برچسب بزنیم. اما ممکن است این خوشه خیلی شبیه به خوشه‌های دیگر باشد و بنابراین مجبور شویم که یک برچسب را بر روی دو خوشه بزنیم. پس توصیه می‌شود که خوشه انتخاب شده را با خوشه‌های همسایه مقایسه کنیم. برای این منظور به تب Cluster Diagram مراجعه نموده و نگاه می‌کنیم که کدام خوشه‌ها به خوشه مدنظر ما نزدیک هستند. اگر هیچ اتصال قوی بین دو خوشه نبود کار تمام است. اما اگر اینگونه نبود آنگاه باید مجددا به تب Cluster Characteristics مراجعه نموده و تک تک ویژگی‌های دو خوشه نزدیک به هم را مقایسه نماییم، تا فرق بین آن‌ها را در صورت وجود به دست آوریم.

خوشه بندی سخت و خوشه بندی نرم

مهمترین فرق بین الگوریتم‌های خوشه بندی، روشی است که الگوریتم‌ها در رابطه با انتساب حالت‌ها، به خوشه‌ها اتخاذ می‌کنند. الگوریتم خوشه بندی مایکروسافت، دو روش مختلف را برای اینکار دارند: K-means و Expectation Maximization. روش اول (شکل سمت چپ) براساس فاصله حالت‌ها نسبت به خوشه‌ها، آن‌ها را نسبت می‌دهد و در پایان مرکز خوشه طوری قرار خواهد گرفت که وسط حالت‌ها باشد. به این تکنیک، خوشه بندی سخت می‌گویند (شکل سمت چپ) زیرا همانطور که در شکل سمت چپ مشخص است هر شیء فقط و فقط در یک خوشه قرار می‌گیرد و هیچ یک از خوشه‌ها با یکدیگر هم پوشانی ندارند. روش دوم (شکل سمت راست) به جای استفاده محض از مقیاس فاصله، از یک مقیاس احتمالی استفاده می‌کند. این روش یک منحنی زنگوله شکل را که دارای میانگین و انحراف معیار است برای هر بُعد درنظر می‌گیرد. چنانچه نقطه‌ای داخل یک منحنی بیفتد با یک احتمال معینی به آن خوشه نسبت داده می‌شود. به دلیل اینکه منحنی‌ها می‌توانند هم پوشانی داشته باشند، بنابراین هر نقطه می‌تواند به چندین خوشه منتسب شود؛ البته با احتمالات مختلف. به این تکنیک، خوشه بندی نرم گفته می‌شود (شکل سمت راست). این تکنیک در شناسایی خوشه‌های پیوسته خیلی موثر است مانند وضعیت تراکم جمعیت مناطق. 


خوشه بندی با قابلیت مدرج کردن

یکی از مسایلی که در الگوریتم خوشه بندی وجود دارد این است که جهت به دست آوردن خوشه بندی مناسب، نیاز به تکرار آموزش برروی داده‌ها است. این تکرار در مجموعه داده‌های کوچک، مشکلی ایجاد نمی‌کند، اما در رابطه با مجموعه داده‌های بزرگ این امر امکان پذیر نیست. زیرا کل مجموعه داده داخل رم قرار می‌گیرد و مشکلات کارآیی را ایجاد می‌کند. الگوریتم خوشه بندی مایکروسافت یک چارچوب برای مدرج کردن خوشه بندی را در اختیار ما قرار می‌دهد که با استفاده از آن می‌توان بر این مشکل فایق آمد. این مهم توسط پارامتر Sample_Size مرتفع می‌شود که یکی از پارامترهای این الگوریتم می‌باشد. همانطور که در قسمت قبل نیز گفته شد دسترسی به پارامترهای هر الگوریتم به شکل زیر صورت می‌پذیرد:
مراجعه به برگه mining models ، کلیک بر روی الگوریتم، رفتن به پنجره properties  الگوریتم. حال می‌توان  به بخش Algorithm Parameters رفت و پارامترها را مقداردهی کرد. البته اگر از نظر حافظه رم مشکلی ندارید، می‌توانید مقدار این پارامتر را صفر درنظر بگیرید و با این کار تمام حافظه رم را به پردازش الگوریتم اختصاص بدهید، تا الگوریتم به هر میزانی که نیاز دارد، از حافظه رم استفاده نماید.
مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت دوم - ایجاد اولین فرم
در قسمت قبل، مروری داشتیم بر تفاوت‌های دو نوع مختلف فناوری‌های ایجاد و مدیریت فرم‌ها در Angular و هچنین ساختار ابتدایی برنامه‌ی این سری را ایجاد کردیم. در ادامه، اولین فرم مبتنی بر قالب‌ها را ایجاد خواهیم کرد.


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

پس از ایجاد کامپوننت employee-register، فایل قالب آن یا src\app\employee\employee-register\employee-register.component.html را گشوده و به نحو ذیل تکمیل می‌کنیم:
<h3>Angular Forms</h3>
<form #form="ngForm">
    <input type="text" placeholder="Name">
    <button type="submit">Ok</button>
</form>

form.pristine: {{ form.pristine }}
زمانیکه المان فرم به صفحه اضافه می‌شود، Angular به صورت خودکار دایرکتیو مرتبطی را به فرم اضافه می‌کند. برای دسترسی به این دایرکتیو نیاز است یک template reference variable را تعریف کرد. برای مثال "form="ngForm#  به معنای تعریف متغیر form است که به دایرکتیو توکار ngForm متصل شده‌است و اکنون حاوی وهله‌ای از این دایرکتیو می‌باشد. به همین جهت است که امکان دسترسی به اطلاعات این وهله توسط درج form.pristine در همان قالب وجود دارد.
خاصیت pristine مشخص می‌کند که آیا فرم توسط کاربر تغییر یافته‌است یا خیر؟


مقدار خاصیت pristine در ابتدای کار true است؛ به این معنا که هنوز تغییری در آن اعمال نشده‌است.

یک نکته: ممکن است در حین توسعه‌ی برنامه، خطای ذیل را در کنسول developer tools مرورگرها مشاهده کنید:
 There is no directive with "exportAs" set to "ngForm"
این دایرکتیو تنها زمانی قابل دسترسی است که در قسمت imports ماژول جاری که با آن کار می‌کنید، تعریف FormsModule را به همان نحوی که در انتهای قسمت قبل بررسی کردیم (قسمت «افزودن ماژول فرم‌ها به برنامه»)، افزوده باشید.

در ادامه، در همین فرمی که تعاریف آن‌را در بالا مشاهده می‌کنید، اطلاعاتی را وارد نمائید. هنوز هم مقدار خاصیت pristine مساوی true است. علت اینجا است که هنوز به Angular اعلام نکرده‌ایم که کدام فیلد یا فیلدهای فرم را باید تحت نظر قرار دهد. برای این منظور ابتدا به المان تعریف شده نامی را انتساب داده و سپس دایرکتیو ngModel را نیز به انتهای تعاریف آن اضافه می‌کنیم:
<h3>Angular Forms</h3>
<form #form="ngForm">
    <input type="text" placeholder="Name" name="name" ngModel>
    <button type="submit">Ok</button>
</form>

form.pristine: {{ form.pristine }}

اکنون اگر مقدار فرم را تغییر دهیم، مشاهده خواهیم کرد که مقدار خاصیتpristine  به false تغییر می‌کند:


یک نکته: زمانیکه دایرکتیو ngModel ذکر می‌شود، تعریف name المان متناظر با آن، الزامی است؛ در غیراینصورت خطای ذیل را در کنسول developer tools مرورگرها مشاهده خواهید کرد:
 Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.


خاموش کردن اعتبارسنجی توکار مرورگرها

یکی از کارهایی را که نیاز است در حین کار با فرم‌ها انجام داد، خاموش کردن اعتبارسنجی توکار مرورگرها است. فرض کنید ویژگی معتبر و استاندارد required را به یکی از المان‌های ورودی اضافه کرده‌اید:
<input type="text" required placeholder="Name" name="name" ngModel>
در این حالت اگر برنامه را اجرا کنید و بدون تکمیل این فیلد بر روی دکمه‌ی ارسال فرم کلیک نمائید، به ازای مرورگرهای مختلف، پیام انگلیسی «لطفا این فیلد را تکمیل کنید» ظاهر خواهد شد و هر کدام شکل متفاوتی را دارند که جزئیات آن‌ها را نمی‌توان تغییر داد و یا سفارشی سازی کرد. به این مورد، browser validation می‌گویند. به همین جهت برای خاموش کردن این اعتبارسنجی توکار مرورگرها و ارائه‌ی تجربه‌ی کاربری یکنواخت و یکدستی در تمام مرورگرها، نیاز است ویژگی novalidate را به تگ فرم اضافه کرد:
<form #form="ngForm" novalidate>
هر دوی ویژگی‌های novalidate و یاrequired ، جزو استاندارد HTML هستند و ارتباطی به Angular ندارند.


بهبود ظاهر فرم توسط اعمال شیوه‌نامه‌های بوت استرپ

در قسمت قبل، در ابتدای کار تدارک ساختار مثال این سری، بوت استرپ را نیز نصب و تنظیم کردیم. در ادامه می‌خواهیم اندکی ظاهر این فرم را بر اساس شیوه‌نامه‌های بوت استرپ بهبود ببخشیم:
<div class="container">
    <h3>Angular Forms</h3>
    <form #form="ngForm" novalidate>
        <div class="form-group">
            <label>First Name</label>
            <input type="text" class="form-control" required name="firstName" ngModel>
        </div>
        <div class="form-group">
            <label>Last Name</label>
            <input type="text" class="form-control" required name="lastName" ngModel>
        </div>
        <button class="btn btn-primary" type="submit">Ok</button>
    </form>
</div>

form.pristine: {{ form.pristine }}


- برای افزودن بوت استرپ نیازی نیست تا شیوه‌نامه‌ی آن‌را به صورت دستی به Index.html برنامه اضافه کرد. همینقدر که ارجاعی از آن در فایل angular-cli.json. در قسمت شیوه‌نامه‌های آن وجود داشته باشد، به صورت خودکار در bundle نهایی تولید شده‌ی توسط سیستم ساخت برنامه‌ی Angular CLI ظاهر خواهد شد.
- در اینجا ابتدا فرم خود را در داخل یک container قرار داده‌ایم. این مورد سبب می‌شود تا محتوای آن به میانه‌ی صفحه منتقل شود.
- سپس شیوه‌نامه‌ی btn به دکمه‌ی ارسال فرم اضافه شده‌است تا شکل دکمه‌های بوت استرپ را پیدا کند.
- سپس هر فیلد ورودی داخل یک div با کلاس form-group محصور می‌شود و هر کنترل، کلاس form-control را خواهد یافت.


افزودن سایر المان‌های ورودی به فرم

تا اینجا دو text box را به فرم اضافه کرده‌ایم. در ادامه می‌خواهیم المان‌های دیگری را نیز تعریف کنیم:

افزودن Check boxes

        <div class="checkbox">
            <label>
                <input type="checkbox" name="is-full-time" ngModel> Full Time Employee
            </label>
        </div>
چون با بوت استرپ کار می‌کنیم، نیاز است المان ورودی از نوع checkbox را داخل یک div با کلاس checkbox محصور کنیم. سپس یک label را تعریف کرده و Input را داخل آن قرار دهیم. در اینجا نیز همانند سایر المان‌ها نیاز است نامی را به آن انتساب داده و سپس دایرکتیو ngModel را قید نمود تا Angular این کنترل را تحت نظر قرار دهد.


افزودن Radio buttons

        <label>Payment Type</label>
        <div class="radio">
            <label>
                <input type="radio" name="pay-type" value="FullTime" checked>
                Full Time
            </label>
        </div>
        <div class="radio">
            <label>
                <input type="radio" name="pay-type" value="PartTime">
                Part Time
            </label>
        </div>
Radio buttons نیز شبیه به Check boxها تعریف می‌شوند. در اینجا نیز یک div با کلاس radio و سپس label ایی که المان ورودی از نوع radio داخل آن قرار می‌گیرد، افزوده خواهد شد. فقط در اینجا باید دقت داشت که گروه بندی این المان‌ها بر اساس نام آن‌ها انجام می‌شود. به همین جهت است که نام این دو المان یکی وارد شده‌است. همچنین باید value آن‌را نیز تنظیم کرد. این مقداری است که در نهایت به سرور ارسال خواهد شد.


افزودن Drop downs

        <div class="form-group">
            <label>Primary Language</label>
            <select class="form-control">
                <option *ngFor="let lang of languages">
                    {{ lang }}
                </option>
            </select>
        </div>
در اینجا از المان select برای تشکیل یک drop down استفاده می‌کنیم و نحوه‌ی تعریف آن بسیار شبیه است به تعریف text boxهایی که داخل form-group محصور شده و همچنین کلاس form-control را پیدا می‌کنند.
اما قسمت مهم آن، اطلاعاتی است که قرار است در این drop down نمایش داده شوند. این اطلاعات را می‌توان از آرایه‌ی languages گرفت و سپس توسط یک ngFor به المان select اضافه کرد. بنابراین باید به فایل employee-register.component.ts مراجعه کرده و آرایه‌ی languages را به آن افزود:
export class EmployeeRegisterComponent implements OnInit {
  languages = ["Persian", "English", "Spanish", "Other"];
کاری که در اینجا انجام می‌شود، تکرار المان option توسط ngFor است. برای مثال در اینجا 4 بار المان option توسط عناصر آرایه‌ی زبان‌ها در داخل المان select تکرار خواهد شد. به عبارتی select نهایی رندر شده‌ی در صفحه، چنین شکلی را پیدا می‌کند:
<select class="form-control">
  <option>Persian</option>
  <option>English</option>
  <option>Spanish</option>
  <option>Other</option>
</select>

تا اینجا فرم تشکیل شده‌ی ما چنین نمایی را پیدا می‌کند:


در قسمت بعد این فرم را توسط مباحث data binding و بررسی نحوه‌ی دسترسی به اطلاعات آن در کامپوننت مرتبط، تکمیل خواهیم کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-02.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.