پاسخ به پرسش‌ها
تا چه میزان کلیدهای خارجی باید در لایه Application بررسی شوند؟

آنچه که در خصوص کتابخانه های مخصوص اعتبارسنجی فرمودید آیا با ارجاع به بانک اطلاعاتی هم پیاده سازی میشوند؟ چون مفهوم اعتبارسنجی را در سطح کلاس های برنامه بلد هستم. برای سناریوی عنوان شده در پست اولیه، یک لایه اعتبارسنجی توسط FluentValidation پیاده سازی شده و null بودن کلیدها بررسی می شوند ولی وجود چنین شناسه ای به عنوان کلید اصلی در بانک اطلاعاتی چطور باید بررسی شود؟ آیا با همان FluentValidation امکانپذیر است یا اینکه در همان لایه Application باید تمام کلیدها بررسی و پیغام مناسب به کاربر برگردانده شود؟

مطالب
WatIn - Web Application Testing in .Net
معرفی:
امروزه تست کردن کدها به دلیل وجود ابزارهای مختلف زیادی، کار آسانی شده است. اما بعضی‌ها در web application ها، یکی از تست‌هایی را که خیلی هم مهم است را فراموش می‌کنند که آن هم تست UI است. شما را در این مقاله با یکی از روش‌های خوب تست UI آشنا خواهم کرد. ابزارهای زیادی برای تست UI وجود دارد که کار کردن با آنها نه تنها زمان بر بلکه بسیار خسته کننده می‌باشند و به خاطر همین خیلی‌ها از انجام تست UI صرف نظر می‌کنند.
WatIn چیست؟
WatIn مخفف Web Application Testing in .Net می‌باشد؛ که یک فریم ورک تست web application‌ها است. WatIn این اجازه را به شما می‌دهد که با استفاده از IE ویا FireFox عناصر داخل صفحات را مقدار دهی کنید و یا حتی رویدادی را برای عناصر فراخوانی کنید.
شروع کار با WatIn:
در زیر یک نمونه از کار با WatIn را می‌توانید مشاهده کنید:
[TestMethod] 
public void SearchForWatiNOnGoogle()
{
  using (var browser = new IE("http://www.google.com"))
  {
    browser.TextField(Find.ByName("q")).TypeText("WatiN");
    browser.Button(Find.ByName("btnG")).Click();
    Assert.IsTrue(browser.ContainsText("WatiN"));
  }
}
WatIn یک فریم ورک کاربر پسند است و در ادامه متوجه می‌شوید که استفاده از این فریم ورک چه مزایایی دارد. برای نصب، WatIn را می‌توانید از اینجا دانلود کنید ویا اگر خواستید می‌توانید با NuGet هم این فریم ورک را دانلود کرده و نصب نمایید. برای شروع کار با Watin باید reference هایی را به پروژه تان اضافه کنید که یکی از این reference‌ها WatiN.Core.dll می‌باشد و برای استفاده از IE ویا FireFox باید فضای نام Watin.Core را اضافه کنیم. Watin چند فضای نام دیگری را هم به همراه دارد که در زیر به توضیح مختصری از آن‌ها می‌پردازیم:

1- Watin.Core.DialogHandlers: این فضای نام این امکان را به شما می‌دهد تا دیالوگ هایی را که مرورگر می‌تواند به کاربر نمایش دهد، مدیریت کنید. از handler‌های این فضای نام AlertDialogHandler، ConfirmDialogHandler، FileUploadDialogHandler، PrintDialogHandler و LoginDialogHandler می‌باشد.
2- Watin.Core.Exceptions: این فضای نام دارای یک سری exception می‌باشد و این امکان را به ما می‌دهد تا یک سری رفتارهای ناخواسته را کنترل کنیم. بعضی از این exception‌ها ElementNotFoundException، IENotFoundException، TimeoutException و WatinException می‌باشد.
3- Watin.Core.Logging: این فضای نام کلاس هایی را در اختیار ما می‌گذارد تا بتوانیم عملیاتی را که در کدمان انجام می‌دهیم log کنیم.

مثالی از watin که در بالا نشان دادیم به این صورت عمل می‌کند که مرورگر IE را باز کرده و به سایت google خواهد رفت. در این صفحه جعبه متنی یا TextBox با نام "q" را پیدا کرده و عبارت "Watin" را در آن تایپ می‌کند و همچنین Buttonی با نام "btnG" پیدا کرده و آن را کلیک می‌نماید و در آخر بررسی می‌کند که در مرورگر متنی شامل WatIn وجود دارد یا خیر.
مشاهده کردید که به همین سادگی یک تست UI نوشتیم. به نظر شما جالب نبود؟ فرض کنید که اگر می‌خواستید با مثلا Microsoft Test Manager این کار را انجام دهید چه دردسرهایی را باید تحمل می‌کردید. حالا تست UI برای همه برنامه نویس‌ها جذاب خواهد شد.
به جای مثال بالا می‌توانیم به صورت زیر هم عمل کنیم:
[TestMethod] 
public void SearchForWatiNOnGoogle()
{
  using (var browser = new IE("http://www.google.com"))
  {
    browser.TextField(Find.ByName("q")).Value="WatiN";
    browser.Button(Find.ByName("btnG")).ClickNoWait();
    Thread.Sleep(3000);
    Assert.IsTrue(browser.ContainsText("WatiN"));
  }
}
تفاوت کد دوم با کد اول این است چون در کد اول از متد TypeText استفاده کردیم یک مقدار سرعت تست را پایین می‌آورد ولی اگر از Value ویا از SetAttribute استفاده کنیم دیگر عمل تایپ را انجام نداده و مقدار را مستقیما در مقدار TextField قرار می‌دهد. شاید بپرسید چرا بعد از متد ClickNoWait چند ثانیه صبر می‌کنم؟ چون صفحه برای اینکه بارگذاری شود و نتیجه جستجو را نشان دهد کمی طول کشیده و Assert.IsTrue شما Failed می‌شود. البته به جای Thread.Sleep می‌توانیم از متدهای مربوط به Watin هم استفاده کنیم مانند WaitUntilComplete ویا از WaitUntilContainsText.
نظرات مطالب
ASP.NET MVC #23
- گزینه‌ی «"uncheck “Verify that file exists» را هم امتحان کنید.
- این سؤال خارج از بحث است. بازگرداندن View هیچ ارتباطی به مسیریابی ندارد. فقط کافی است بنویسید:
return View("~/Views/....مسیر کامل فایل", model);
تولید URLهای خودکار بر اساس اطلاعات مسیریابی در Viewهای برنامه، توسط متدهای توکار ActionLink و امثال آن انجام می‌شود.
- تمام خطاهای مدیریت نشده‌ی برنامه‌های وب در لاگ ویندوز ثبت می‌شوند. آن‌ها را بررسی کنید. همچنین ELMAH را هم نصب کنید تا خطاها را برای بررسی بیشتر لاگ کند.
- روش‌های قدیمی را با MVC کار نکنید. صفحه‌ی اول سایت، همان صفحه‌ای است که در مسیریابی پیش فرض تعریف شده‌است. یعنی همان اکشن متد Index در کنترلر Home، به همراه View ایی که مد نظر شما است.
مطالب
کار با Docker بر روی ویندوز - قسمت پنجم - ایجاد Imageهای سفارشی
تا اینجا با نحوه‌ی اجرای برنامه‌های مختلف توسط داکر مانند وب سرور لینوکسی nginx و یا IIS ویندوزی آشنا شدیم؛ اما هنوز محتوایی را در آن‌ها هاست نکرده‌ایم. در این قسمت این موضوع را بررسی خواهیم کرد و در طی این فرآیند، با نحوه‌ی ساخت Imageهای سفارشی نیز آشنا خواهیم شد.


روش نگاشت محتوای یک سایت استاتیک در یک Container که وب سرور است

فرض کنید یک سایت استاتیک بوت استرپی را تهیه کرده‌اید و قصد دارید آن‌را توسط وب سرور nginx، هاست کنید. برای این‌کار، چندین گزینه پیش روی ما هستند:
گزینه‌ی اول: دریافت image مربوط به nginx، سپس ایجاد یک container از آن و در آخر با استفاده از «روش به اشتراک گذاری فایل سیستم میزبان با کانتینرها» که در قسمت قبل بررسی کردیم، این وب سایت را آماده‌ی اجرا و دسترسی می‌کنیم.
گزینه‌ی دوم: کپی کردن فایل‌های وب سایت از سیستم میزبان، به درون فایل سیستم خود container.
گزینه‌ی سوم: ایجاد یک image سفارشی که از ابتدا به همراه فایل‌های وب سایت استاتیک ما است و در این حالت تنها کافی است این image را تبدیل به container اجرایی کنیم.


روش اول: به اشتراک گذاری فایل سیستم میزبان با کانتینر وب سرور جهت هاست آن

در قسمت قبل، یک فایل tar ایجاد شده‌ی در سیستم میزبان ویندوزی را با یک کانتینر لینوکسی به اشتراک گذاشتیم تا بتوانیم محتویات آن‌را استخراج کنیم. در اینجا قصد داریم پوشه‌ی وب سایت استاتیک خود را که در سیستم میزبان ویندوزی قرار دارد، با وب سرور nginx که توسط یک container در حال اجرا است، به اشتراک بگذاریم تا آن‌را هاست کند.
فرض کنید وب سایت استاتیک ما در مسیر c:\users\vahid\mysite سیستم میزبان قرار دارد که داخل آن یک فایل index.html و تعدادی فایل css و js آماده‌ی برای هاست شدن، وجود دارند. برای هاست آن توسط nginx، از دستور زیر استفاده خواهیم کرد:
 docker run --rm -it -p 8080:80 -v c:\users\vahid\mysite:/usr/share/nginx/html nginx
در این دستور:
- سوئیچ rm سبب می‌شود تا پس از خاتمه‌ی کار nginx، این container نیز حذف شود.
- از سوئیچ it استفاده شده‌است تا با فشردن ctrl+c، بتوانیم پروسه‌ی container را خاتمه دهیم و پس از آن، برنامه‌ی nginx دیگر در background در حال اجرا نباشد (اجرای آن در foreground).
- سپس پورت 8080 سیستم میزبان، به پورت 80 وب سرور nginx نگاشت شده‌است. چون containerها دارای network stack خاص خودشان هستند (که آن‌را در قسمت سوم بررسی کردیم)، پورت 80 آن‌ها با پورت 80 سیستم میزبان تداخل نمی‌کند و اگر برای مثال بر روی پورت 80 سیستم جاری، IIS در حال اجرا باشد، سبب عدم اجرا شدن وب سرور nginx به دلیل تداخل پورت‌ها نمی‌شود.
- در ادامه روش volume mount را مشاهده می‌کنید که در قسمت قبل بررسی کردیم. مسیر c:\users\vahid\mysite سیستم میزبان، به مسیر ویژه‌ی /usr/share/nginx/html داخل container نگاشت شده‌است. این مسیر، یک مسیر استاندارد بوده و در مستندات docker hub این وب سرور، ذکر شده‌است.
- در آخر هم نام image این وب سرور را ذکر کرده‌ایم.

پس از اجرای این دستور، اگر nginx پیش‌تر دریافت نشده باشد، image آن دریافت شده، یک container بر اساس آن ساخته می‌شود و سپس با پارامترهایی که توضیح دادیم، اجرا خواهد شد. اکنون اگر در سیستم میزبان، مسیر http://localhost:8080 را در مرورگر باز کنید، وب سایت استاتیک خود را مشاهده خواهید کرد.


روش دوم: کپی کردن فایل‌های وب سایت از سیستم میزبان، به درون فایل سیستم خود container

همانطور که در قسمت سوم نیز بررسی کردیم، فایل سیستم مربوط به هاست، به طور کامل از فایل سیستم container، جدا و ایزوله است و بدون volume mount، یک container نمی‌تواند به فایل‌های میزبان خود دسترسی پیدا کند. بنابراین گزینه‌ی دیگری که در اینجا وجود خواهد داشت، کپی کردن فایل‌های میزبان و انتقال آن‌ها به container می‌باشد؛ شبیه به کپی کردن فایل‌ها از یک کامپیوتر موجود در شبکه به کامپیوتر دیگری در آن.
برای این منظور ابتدا nginx را در پس‌زمینه اجرا می‌کنیم:
 docker run -d -p 8080:80 --name nginx nginx
در این دستور، سوئیچ‌های rm و it حذف شده‌اند. علت اینجا است که سوئیچ d، سبب اجرای این دستور در پس‌زمینه می‌شود؛ یعنی بلافاصله سبب بازگشت ما به خط فرمان خواهد شد و در این حالت نمی‌خواهیم که این container حذف شود. همچنین یک نام نیز به آن انتساب داده شده‌است تا بتوان ساده‌تر با آن کار کرد.
پس از اجرای این دستور و بازگشت به command prompt، جهت اطمینان حاصل کردن از اجرای آن در پس زمینه، دستور docker ps را صادر می‌کنیم که لیست آن، حاوی گزارشی از container‌های در حال اجرا است.
اکنون توسط دستور ویژه‌ی docker exec، می‌خواهیم درون یک container در حال اجرا، پروسه‌ای را اجرا کنیم. یعنی با اینکه پروسه‌ی nginx داخل این container در حال اجرا است، برای مثال می‌خواهیم یک shell را نیز داخل آن اجرا کنیم:
 docker exec -it nginx bash
در اینجا دستور docker exec، سبب اجرای bash shell داخل کانتینری با نام nginx می‌شود (همان سوئیچ name در دستور قبلی و نه نام image آن) و چون می‌خواهیم به این shell در foreground دسترسی داشته باشیم، از سوئیچ it نیز استفاده شده‌است. پس از اجرا شدن bash shell، اکنون به فایل سیستم این container دسترسی یافته‌ایم. برای مثال دستور ls را صادر کنید تا لیستی از آن‌را مشاهده نمائید. سپس به کمک آن، به پوشه‌ی ویژه‌ی html این وب سرور وارد می‌شویم:
 cd /usr/share/nginx/html
و برای مثال می‌توان در آن تغییر ایجاد کرد:
ls
mv index.html index2.html
exit
این دستورات سبب می‌شوند تا فایل پیش‌فرض index.html آن، به index2.html تغییر نام یابد و سپس از این shell خارج می‌شویم و به shell سیستم میزبان باز خواهیم گشت. در اینجا دستور docker cp (که در PowerShell سیستم میزبان اجرا می‌شود)، امکان کپی کردن فایل‌ها را از سیستم میزبان به یک container میسر می‌کند.
 docker cp c:\users\vahid\mysite nginx:/usr/share/nginx/html
پس از دستور docker cp ابتدا مسیر مبداء مشخص می‌شود و سپس ابتدا نام container مقصد به همراه یک : و در ادامه مسیر مقصد نهایی کپی در آن container ذکر خواهند شد. به این ترتیب فایل‌های وب سایت استاتیک ما در سیستم میزبان به پوشه‌ی html مخصوص nginx، در کانیتنری که در حال اجرای آن است کپی می‌شوند. برای آزمایش صحت این کپی می‌توان دستور زیر را صادر کرد که لیست فایل‌های این پوشه‌ی html را نمایش می‌دهد:
 docker exec nginx ls /usr/share/nginx/html
اینبار نیز اگر در سیستم میزبان، مسیر http://localhost:8080 را در مرورگر باز کنید، وب سایت استاتیک خود را مشاهده خواهید کرد که فایل‌های آن از داخل خود container تامین می‌شوند و وابستگی به سیستم میزبان ندارند.


روش سوم: ایجاد یک image سفارشی که از ابتدا به همراه فایل‌های وب سایت استاتیک ما است

در روش دوم، موفق شدیم که فایل‌های مدنظر خود را به درون container در حال اجرا کپی کنیم. اکنون می‌خواهیم یک snapshot را از آن تهیه کنیم؛ شبیه به کاری که با ماشین‌های مجازی نیز انجام می‌شود و این روشی است که از آن برای ساخت یک image سفارشی استفاده می‌شود. برای این منظور از دستور docker commit استفاده می‌شود تا تصویری را از وضعیت یک container در حال اجرا، در آن لحظه تهیه کنیم:
 docker commit nginx mysite:nginx
پس از دستور docker commit، نام container ای که می‌خواهیم تصویر وضعیت جاری آن‌را ذخیره کنیم، ذکر می‌شود. پس از آن به صورت اختیاری می‌توان یک نام جدید و همچنین tag ای را برای آن ذکر کرد.
اکنون پس از اجرای این دستور، با استفاده از فرمان docker images می‌توان مشاهده کرد که image جدید mysite، با tag ای معادل nginx، ایجاد شده‌است.
در ادامه برای اجرای این image جدید، می‌توان از دستور زیر استفاده کرد:
 docker run -d -p 8090:80 --name mysite mysite:nginx
روش اجرای آن همانند سایر imageهای موجود است و در اینجا از نام image به همراه tag آن استفاده شده‌است. همچنین پورت نگاشت شده‌ی آن‌را به سیستم میزبان نیز 8090 انتخاب کرده‌ایم. نامی را نیز به آن نسبت داده‌ایم تا بتوان از آن در دستور docker exec استفاده کرد.
اکنون اگر در سیستم میزبان، مسیر http://localhost:8090 را در مرورگر باز کنید، وب سایت استاتیک خود را مشاهده خواهید کرد و یا توسط دستور زیر می‌توانید فایل‌های موجود در پوشه‌ی html وب سرور nginx این container جدید در حال اجرا را ملاحظه نمائید:
 docker exec mysite ls /usr/share/nginx/html
که این فایل‌ها نه از طریق نگاشت فایل سیستم میزبان، به مسیری در container جاری تامین شده‌اند و نه از جائی به داخل آن کپی شده‌اند. بلکه دقیقا از image از پیش آماده شده‌ی آن خوانده شده‌اند.


نگاهی به لایه‌های یک Image در مقایسه با یک Container

زمانیکه خواستیم image جدید و سفارشی خاص خود را ایجاد کنیم، با image اصلی nginx شروع کردیم. اولین لایه‌ی موجود در این image، سیستم عاملی است که می‌تواند آن‌را اجرا کند. برفراز این لایه، لایه‌ی خود nginx قرار گرفته‌است. اگر خواستید تاریخچه‌ی ایجاد یک image را مشاهده کنید، از دستور docker history nginx استفاده نمائید. خروجی آن لیست دستوراتی را نمایش می‌دهد که برای ساخت این image مورد استفاده قرار گرفته‌اند. البته دستور docker history nginx --no-trunc، اطلاعات بیشتری را با نمایش لیست کامل و خلاصه نشده‌ی دستورات، ارائه می‌دهد. این دستورات را در صفحه‌ی docker hub هر image نیز می‌توان مشاهده کرد. در قسمت full description هر image، در ابتدای توضیحات، قسمتی است به نام supported tags and respective dockerfile links. در اینجا هر tag نامبرده شده، در حقیقت لینکی است به یک فایل که دقیقا همین دستورات را لیست کرده‌است. به این فایل، docker file گفته می‌شود که روش ساخت یک image را توضیح می‌دهد. هدف آن، خودکار سازی اجرای دستوراتی است که سبب ساخت یک image می‌شوند.

در ادامه اگر از این image، یک container را ایجاد کنیم، این container هر دو لایه‌ی OS و Framework را به همراه خواهد داشت؛ به علاوه‌ی لایه‌ی دیگری به نام Container/Run که می‌توان فایل‌های آن‌را خواند و یا در آن نوشت. بنابراین لایه‌ای که فایل‌های وب سایت استاتیک ما در آن کپی شدند، دقیقا همین لایه‌است.


و زمانیکه از یک container تصویری تهیه می‌شود، تغییراتی را که به فایل سیستم آن ایجاد کرده‌ایم، به صورت یک لایه‌ی جدید بر روی لایه‌های قبلی آن image، ظاهر و ثبت می‌شود. برای اثبات این موضوع، می‌توان از دستور docker diff nginx استفاده کرد. در اینجا nginx نام container ای است که می‌خواهیم تغییرات آن‌را با image قبلی که بر پایه‌ی آن ایجاد شده‌است، مشاهده کنیم.


تبدیل دستورات docker به یک docker file

تا اینجا یک چنین دستوراتی را برای اجرای کانتینر nginx، کپی فایل‌ها به آن و سپس تهیه‌ی یک تصویر از آن، اجرا کردیم:
docker run -d -p 8080:80 --name nginx nginx
docker cp c:\users\vahid\mysite nginx:/usr/share/nginx/html
docker commit nginx mysite:nginx
برای خودکار سازی آن‌ها هرچند می‌توان این دستورات را در یک اسکریپت نیز قرار داد، اما docker، قابلیت پردازش اسکریپت‌های خاص خود را نیز دارد که به آن Dockerfile گفته می‌شود. برای این منظور سطرهای فوق به صورت زیر تغییر می‌کنند:
بجای سطر اول، تنها نام image ای را که می‌خواهیم کار را بر مبنای آن انجام دهیم، ذکر می‌کنیم:
 FROM nginx
دستور دوم نیز تبدیل به دستور کپی Docker می‌شود:
 COPY mysite /usr/share/nginx/html
این دو سطر را به صورت یک فایل متنی، با نام ویژه‌ی Dockerfile ذخیره می‌کنیم (بدون پسوند) و این Dockerfile را دقیقا در کنار پوشه‌ی mysite قرار می‌دهیم (داخل پوشه‌ی c:\users\vahid) تا کار کپی را از همینجا شروع کند.
سپس برای اجرای این فایل، بجای دستور docker commit آخر، از دستور زیر استفاده می‌کنیم:
 docker build -f Dockerfile -t mysite:nginx-df .
البته می‌توان f Dockerfile- را نیز از این دستور حذف کرد؛ چون مقدار پیش‌فرض آن است (مگر آنکه بخواهیم مسیر خاصی را دقیقا مشخص کنیم):
 docker build -t mysite:nginx-df .
در هر دو دستور آخری که ذکر شدند، در انتهای دستور، یک نقطه نیز قرار دارد که به آن build context گفته می‌شود؛ یا دقیقا همین پوشه‌ای که در آن قرار داریم (c:\users\vahid).
تگ این image را نیز متفاوت با قبلی‌ها انتخاب کرده‌ایم؛ nginx-df بجای مقدار قبلی.
در این حالت اگر دستور آخر را اجرا کنیم، دستور docker images گزارش اضافه شدن این image جدید را ارائه خواهد داد.

مرجع کامل ساخت Dockerfileها را در اینجا می‌توانید مطالعه کنید.


ساخت یک image سفارشی برای هاست یک وب سایت استاتیک در IIS

تا اینجا از وب سرور لینوکسی nginx برای هاست وب سایت استاتیک خود استفاده کردیم. در ادامه می‌خواهیم از وب سرور IIS برای اینکار استفاده نمائیم. بنابراین ابتدا نیاز است یا از ویندوز سرور استفاده کنیم و یا می‌توان با کلیک راست بر روی آیکن Docker در قسمت Tray Icons ویندوز، به Windows Containers سوئیچ کرد و سپس به صورت زیر عمل نمود.
اینبار محتوای Dockerfile ای که کنار پوشه‌ی mysite قرار می‌گیرد، به صورت زیر خواهد بود:
FROM microsoft/iis:nanoserver

COPY mysite c:/inetpub/wwwroot
کار با image اصلی iis با tag مخصوص nanoserver که کم حجم‌تر است، شروع می‌شود. سپس فایل‌های mysite به پوشه‌ی wwwroot این وب سرور کپی خواهد شد.
در ادامه با استفاده از دستور زیر و اجرای فایل Dockerfile، این image جدید را با tag ای به نام iis ایجاد می‌کنیم:
 docker build -t mysite:iis .
پس از آن دستورات docker images و docker ps را جهت مشاهده‌ی وضعیت این image جدید اجرا کنید.


به اشتراک گذاری imageهای سفارشی در Docker Hub

برای به اشتراک گذاری imageهای سفارشی خود در Docker Hub، نیاز است tag آن‌ها را توسط دستور docker tag مطابق فرمت ویژه‌ی docker hub ویرایش کرد:
 docker tag mysite:nginx-df my_user_name/some_name:new_tag_name
در این دستور، Tag فعلی، با ذکر نام کاربری، نام مخزنی جدید در docker hub و سپس یک tag دلخواه، ویرایش می‌شود.
و در آخر برای انتشار آن می‌توان از دستور docker push استفاده کرد:
 docker push my_user_name/some_name:new_tag_name
اگر در اینجا پیام خطای unauthorized را مشاهده کردید، ابتدا دستور docker login را اجرا کنید تا بتوانید به سایت docker hub لاگین کنید (بر اساس مشخصات اکانت خود در داکر هاب) و سپس دستور فوق را اجرا نمائید.
پس از پایان کار اگر به سایت docker hub و مخازن خود مراجعه کنید، این image جدید قابل مشاهده خواهد بود.
مطالب
بررسی الگوی Chain of Responsibility در جاوا اسکریپت
الگوی Chain of Responsibility، یک زنجیر، از اشیاء متصل شده‌ی به هم را فراهم می‌کند که یکی از آنها می‌تواند درخواست رسیده را راضی کند؛ به عبارتی دیگر به محض دریافت درخواست، آن را پردازش می‌کند. این الگو اساسا یک جستجوی خطی ( linear search )، برای یک شیء می‌باشد که می‌تواند یک درخواست ویژه را handle کند. 

الگوی chain-of-responsibility، ارتباط با الگوی Chaining دارد که به دفعات در جاوا اسکریپت استفاده شده‌است (jQuery  استفاده‌ی گسترده‌ای از این الگو کرده‌است).
این الگو اجازه میدهد که یک درخواست ارسال شده‌ی توسط کلاینت، توسط یک یا بیش از یک object، دریافت شود. یک مثال عمومی از این الگو،  event bubbling در DOM  می‌باشد. یک رویداد از طریق element‌‌‌های تودرتوی متفاوت انتشار پیدا می‌کند؛ تا زمانیکه یکی از آن‌ها، آن را handle کند.


مثال زیر را در نظر بگیرید:
class HandlerChain 
{ 
   setNextObj(nextObjInChain){}
   processMultiple(req){
     console.log("No multiple for: " + req.getMultiple());
   }
} 
  
class Multiple
{
  constructor(multiple){
    this.multiple = multiple;
  }
  
  getMultiple(){ 
    return this.multiple; 
  } 
  
} 
  

class MultipleofTwoHandler extends HandlerChain
{
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  } 
  
  setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) { 
    if ((req.getMultiple() % 2) == 0) { 
      console.log("Multiple of 2: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
    } 
  } 
} 
  
class MultipleofThreeHandler extends HandlerChain 
{ 
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  } 
  
   setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) 
  { 
    if ((req.getMultiple() % 3) == 0) { 
      console.log("Multiple of 3: " + req.getMultiple()); 
    }else{ 
          this.nextObjInChain.processMultiple(req); 
        } 
    } 
} 


class MultipleofFiveHandler extends HandlerChain
{ 
  constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
  }
  
  setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
  } 
  
  processMultiple(req) { 
    if ((req.getMultiple() % 5) == 0) { 
      console.log("Multiple of 5: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
      } 
    } 
} 

//configuring the chain of handler objects
var c1 = new MultipleofTwoHandler(); 
var c2 = new MultipleofThreeHandler(); 
var c3 = new MultipleofFiveHandler(); 
c1.setNextObj(c2); 
c2.setNextObj(c3); 
  
//the chain handling different cases
c1.processMultiple(new Multiple(95));  // Multiple of 5: 95  
c1.processMultiple(new Multiple(50));  // Multiple of 2: 50 
c1.processMultiple(new Multiple(9));   // Multiple of 3: 9 
c1.processMultiple(new Multiple(4));   // Multiple of 2: 4 
c1.processMultiple(new Multiple(21));  // Multiple of 3: 21 
c1.processMultiple(new Multiple(23));  // No multiple for: 23  
مثال بالا، الگوی chain of responsibility را پیاده سازی می‌کند و بررسی می‌کند که آیا عدد وارد شده، مضربی از 2، 3 یا 5 است و یا نه.
چگونه می‌توانیم این قابلیت را پیاده سازی کنیم؟ ما می‌خواهیم که یک عدد داشته باشیم و سپس به handler‌ها در زنجیره اجازه بدهیم که تصمیم گیری کنند که آیا آنها می‌خواهند عدد وارد شده را پردازش کنند، یا به handler بعدی پاس بدهند.

ما 3 نوع handler در این زنجیره داریم:

  • MultipleofTwoHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 2 است یا نه. 
  • MultipleofThreeHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 3 است یا نه 
  • MultipleofFiveHandler: بررسی می‌کند که آیا عدد وارد شده، مضربی از 5 است یا نه. 

اولین قدم، ایجاد یک زنجیره از handler‌های بالا می‌باشد. در اینجا یک کلاس را به نام HandlerChain، برای همین منظور داریم و این کلاس شامل دو تابع، به نام‌های setNextObj و processMultiple می‌باشد.
class HandlerChain 
{ 
   setNextObj(nextObjInChain){}
   processMultiple(req){
     console.log("No multiple for: " + req.getMultiple());
   }
}

پیاده سازی پیش فرض processMultiple، زمانی اجرا می‌شود که هیچ مضربی برای یک عدد، وجود نداشته باشد. به عبارت دیگر، عدد مورد نظر، مضربی از 2،3 و 5 نباشد ( از بین مضرب‌های تعین شده). 
تمام handler‌ها در این زنجیره، از این کلاس ارث بری می‌کنند. هر handler می‌تواند دو عملیات را انجام دهد:

  1. تنظیم کردن handler بعدی در زنجیره 
  2. پردازش عدد وارد شده به این منظور که آیا در مضرب مورد نظر قرار دارد یا نه. 

class MultipleofTwoHandler extends HandlerChain
{ 
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}
  
class MultipleofThreeHandler extends HandlerChain
{
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}


class MultipleofFiveHandler extends HandlerChain
{
  constructor(){/*code*/}
  setNextObj(nextObj){/*code*/}  
  processMultiple(req){/*code*/}
}

constructor برای هر handler، همانند زیر تعریف شده‌است:
constructor(){
    super()
    this.nextObjInChain = new HandlerChain()
}

در ابتدا توسط  super ،  مقدار دهی اولیه‌ای برای متد‌های setNextObj و processMultiple از کلاس پدر انجام میشود. همچنین در constructor، متغیر nextObjInChain
مقدار دهی اولیه می‌شود که تعیین کننده‌ی شیء بعدی در زنجیره می‌باشد. 
 
 چگونه شیء جاری، شیء بعد را در زنجیره تعیین می‌کند؟ اجازه بدهید برای این منظور نگاهی به تابع setNextObj داشته باشیم. setNextObj در هر handler، همانند زیر تعیین شده‌است: 
setNextObj(nextObj){ 
    this.nextObjInChain = nextObj; 
}

اکنون می‌توانیم زنجیره‌ای از handler‌ها را همانند زیر ایجاد کنیم: 
var c1 = new MultipleofTwoHandler(); 
var c2 = new MultipleofThreeHandler(); 
var c3 = new MultipleofFiveHandler(); 
c1.setNextObj(c2); 
c2.setNextObj(c3);

در اینجا ما handler‌‌‌های c2, c1  و c3 را به منظور پردازش کردن مضرب‌های 2، 3 و 5 ایجاد کرده‌ایم. آن‌ها به یکدیگر به حالت زیر متصل شده‌اند:
 c2 به عنوان handler بعدی برای c1 و c3 به عنوان handler بعدی برای c2. 


اکنون که زنجیره ایجاد شده‌است، زمان آن است که عدد وارد شده را پردازش کنیم. اجازه بدهید که کار را با ایجاد کردن شیء “multiple”  با استفاده از کلاس Multiple، شروع کنیم.

class Multiple
{
  constructor(multiple){
    this.multiple = multiple
  }
  
  getMultiple(){ 
    return this.multiple; 
  }  
}

یک شیء Multiple، شامل یک خصوصیت به نام multiple و یک متد است به نام getMultiple که به منظور برگشت دادن multiple می‌باشد.
 
MultipleofTwoHandler به صورت زیر تعریف شده‌است:
processMultiple(req) { 
    if ((req.getMultiple() % 2) == 0) { 
      console.log("Multiple of 2: " + req.getMultiple()); 
    }else{ 
      this.nextObjInChain.processMultiple(req); 
    } 
}

در اینجا، یک multiple دریافت شده و چک می‌شود که آیا مضربی از 2 می‌باشد یا نه؛ اگر چنین است، سپس عدد دریافت شده را به عنوان مضربی از 2 نمایش می‌دهد. در صورتیکه مضربی از 2 نباشد، handler آن را به شیء بعدی در زنجیره، پاس میدهد و سپس همین روال دوباره تکرار می‌شود و تا زمانیکه یکی از handler ‌ها، جواب را برگشت دهد، این روال ادامه پیدا می‌کند. 
 
تعریف تابع processMultiple  برای هر 3 handler یکسان میباشد؛ با این تفاوت که MultipleofThreeHandler، برای بررسی مضربی از 3 بودن و MultipleofFiveHandler  برای بررسی مضربی از 5 بودن است.

اجازه بدهید یک مثال بزنیم و ببینیم که چه اتفاقی می‌افتد:
c1.processMultiple(new Multiple(95)) // Multiple of 5: 95  

اولین handler در زنجیره که c1 است، یک multiple (95) را دریافت می‌کند و چک می‌کند که آیا مضربی از 2 است یا نه. جواب خیر است؛ بنابراین multiple به دومین handler  در زنجیره پاس داده میشود؛ یعنی به handler بررسی کننده‌ی مضربی از 3 . در اینجا دوباره بررسی میشود که آیا عدد مورد نظر، مضربی از 3 است یا نه؟ که جواب خیر است. در ادامه multiple به handler سوم در زنجیره که بررسی کننده‌ی مضربی از 5 است، پاس داده میشود. در اینجا، شرط درست است و عدد 95 به عنوان مضربی از 5 چاپ میشود. 


چه زمانی از این الگو استفاده کنیم؟
به منظور مدیریت کردن انواع درخواست‌ها، به روش‌های متفاوت، بدون دانستن ترتیب و نوع درخواست از قبل، از این الگو استفاده می‌کنیم. این الگو به ما اجازه می‌دهد که زنجیره‌ای از چند handler را داشته باشیم. تمامی handler‌ها، یک شانس را جهت پردازش درخواست دارند.  
مطالب
نوشتن Middleware سفارشی در ASP.NET Core
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 3 - Middleware چیست؟» با اصول مقدماتی Middlewareها آشنا شدیم. همچنین در مطلب «آشنایی با OWIN و بررسی نقش آن در ASP.NET Core» یک مثال سفارشی از آن‌ها، بررسی شد. در اینجا می‌خواهیم نکات بیشتری را در مورد تهیه‌ی Middlewareهای سفارشی بررسی کنیم.


تفاوت بین متدهای app.Use  و  app.Run در چیست؟

Middlewareها به همان ترتیبی که در متد Configure کلاس آغازین برنامه معرفی می‌شوند، اجرا خواهند شد؛ اما نکته‌ی مهم اینجا است که middleware ایی که توسط متد app.Use تعریف می‌شود، می‌تواند middleware بعدی ثبت شده‌را، فراخوانی کند؛ اما app.Run خیر. برای درک بهتر این مفهوم، به مثال زیر دقت کنید:
using Microsoft.AspNetCore.Http;

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, before next()</div>");
 
            await next();
 
            await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, after next()</div>");
        });
 
        app.Run(async context =>
        {
            await context.Response.WriteAsync("<div>Inside middleware-2 defined using app.Run</div>");
        });
 
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, before next()</div>");
 
            await next();
 
            await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, after next()</div>");
        });
اگر در این حالت برنامه را اجرا کنیم، چنین خروجی را مشاهده خواهیم کرد:


همانطور که در تصویر نیز مشخص است، ابتدا کدهای پیش از فراخوانی دلیگیت next میان‌افزار اول اجرا شده‌است. سپس باتوجه به فراخوانی دلیگیت next، کدهای دومین میان‌افزار ثبت شده، فراخوانی گردیده‌است و سپس کدهای پس از فراخوانی دلیگیت next میان‌افزار اول، اجرا شده‌اند.
این دلیگیت در اصل یک چنین امضایی را دارد:
 public delegate Task RequestDelegate(HttpContext context);
در اینجا چون میان‌افزار دوم از نوع app.Run است و قابلیت فراخوانی دلیگیت next را ندارد، از نوع terminal یا خاتمه دهنده به‌شمار آمده و دیگر میان افزار بعدی ثبت شده، یعنی میان‌افزار سوم، اجرا نخواهد شد و کار پردازش برنامه در همین مرحله به پایان می‌رسد.

باید دقت داشت که فراخوانی دلیگیت next در میان‌افزارهای از نوع app.Use الزامی نبوده و اگر اینکار انجام نشود، بین app.Run و app.Use تفاوتی نخواهد بود و هر دو terminal به حساب می‌آیند.


تفاوت بین متدهایapp.Map  و  app.MapWhen در چیست؟

متد app.Map در صورت برآورده شدن شرطی، سبب اجرای میان‌افزاری مشخص می‌شود (امکان اجرای غیر خطی میان‌افزارها).
فرض کنید قطعه کد زیر را پس از اولین app.Use مثال فوق قرار داده‌ایم:
app.Map("/dnt", appBuilder =>
{
    appBuilder.Run(async context =>
    {
        await context.Response.WriteAsync(@"<div>Inside Map(/dnt) --> Run</div>");
    });
});
در این حالت اگر برنامه را اجرا کنیم، خروجی جدیدی را مشاهده نخواهیم کرد و خروجی حاصل دقیقا مانند تصویر مثال فوق است. اما اگر آدرس ویژه‌ی dnt/ درخواست شود (الگوی تطابق اولین پارامتر متد Map)، آنگاه میان افزار ثبت شده‌ی app.Run ویژه‌ی این حالت خاص، اجرا می‌شود:


در اینجا چون app.Run داخلی فراخوانی شده، از نوع terminal است، دیگر میان افزارهای پس از آن اجرا نشده‌اند. بدیهی است در اینجا نیز می‌توان به هر تعدادی که نیاز است میان افزارهای جدیدی را به appBuilder متد app.Map اضافه کرد.

پارامتر اول متد Map برای تطابق با الگوهایی خاص و مشخص، مناسب است. اما در اگر در اینجا نیاز به اطلاعات بیشتری از HttpContext جاری داشته باشیم، می‌توانیم از متد app.MapWhen استفاده کنیم که اولین پارامتر آن یک دلیگیت است که HttpContext را در اختیار استفاده کننده قرار می‌دهد و اگر در نهایت true را دریافت کند، سبب اجرای میان افزارهای قسمت appBuilder آن خواهد شد:
app.MapWhen(context =>
{
    return context.Request.Query.ContainsKey("dnt");
},
appBuilder =>
{
    appBuilder.Run(async context =>
    {
        await context.Response.WriteAsync(@"<div>Inside MapWhen(?dnt) --> Run</div>");
    });
});
در این مثال، شرط ارائه شده‌ی در پارامتر اول، اندکی پیچید‌ه‌تر است از حالت app.Map. در اینجا مشخص شده‌است که اگر آدرس دریافتی از کاربر، دارای کوئری استرینگی به نام dnt بود، آنگاه میان افزار(های) ارائه شده‌ی در قسمت appBuilder، اجرا شود. برای نمونه درخواست آدرس ذیل، سبب فراخوانی appBuilder.Run ذکر شده می‌شود:
 http://localhost:7742/?dnt=true



نظم بخشیدن به تعاریف میان‌افزارها

متدهای app.Run و app.Use و امثال آن‌ها برای تعریف سریع میان افزارها مناسب هستند. اما اگر بخواهیم کدهای کلاس آغازین برنامه را اندکی خلوت کرده و به تعاریف میان‌افزارها نظم ببخشیم، می‌توان کدهای آن‌ها را به کلاس‌هایی با امضایی خاص منتقل کرد:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public class MyMiddleware1
    {
        private readonly RequestDelegate _next;
 
        public MyMiddleware1(RequestDelegate next)
        {
            _next = next;
        }
 
        public async Task Invoke(HttpContext context)
        {
                    context.Response.ContentType = "text/html";
                    context.Response.StatusCode = 200;
            await context.Response.WriteAsync("<div>Hello from MyMiddleware1.</div>");
            await _next.Invoke(context);
            await context.Response.WriteAsync("<div>End of action.</div>");
        }
    } 
}
در اینجا نحوه‌ی تعریف یک کلاس میان‌افزار سفارشی را مشاهده می‌کنید. دو پارامتر context و next ایی را که در متد app.Use مشاهده کردید، دراینجا به نحو واضح‌تری مشخص شده‌اند. دلیگیت next اشاره کننده‌ی به اجرای میان افزار بعدی، در سازنده‌ی کلاس این میان افزار تزریق شده‌است. همچنین در اینجا می‌توان سرویس‌هایی را که به IoC Container توکار ASP.NET Core معرفی کرده‌ایم نیز تزریق کنیم و از این لحاظ محدودیتی ندارد (و همچنین امضای آن می‌تواند کاملا متغیر باشد). قسمت اصلی اجرایی این میان افزار، متد Invoke آن است که اطلاعات HttpContext جاری را در اختیار مصرف کننده قرار می‌دهد.
در اینجا نیز اگر دلیگیت next_ فراخوانی نشود، این میان‌افزار سبب خاتمه‌ی اجرای پردازش درخواست جاری می‌گردد.

 مرحله‌ی بعد، روش معرفی این میان‌افزار تعریف شده، به لیست میان‌افزارهای موجود است. برای این منظور می‌توان متد app.UseMiddleware را به صورت مستقیم در کلاس آغازین برنامه فراخوانی کرد و یا مرسوم است اگر کتابخانه‌ای را طراحی کرده‌اید، به نحو ذیل متد الحاقی خاصی را برای آن تدارک دید:
using Microsoft.AspNetCore.Builder;

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
    {
        app.UseMiddleware<MyMiddleware1>();
        return app;
    }
}
سپس مصرف کننده تنها باید این متد را در کلاس آغازین برنامه فراخوانی کند:
public void Configure(IApplicationBuilder app)
{
    app.UseMyMiddleware();
مطالب دوره‌ها
ارزیابی و تفسیر مدل در داده کاوی
مقدمه
دانشی که در مرحله یادگیری مدل تولید می‌شود، می‌بایست در مرحله ارزیابی مورد تحلیل قرار گیرد تا بتوان ارزش آن را تعیین نمود و در پی آن کارائی الگوریتم یادگیرنده مدل را نیز مشخص کرد. این معیارها را می‌توان هم برای مجموعه داده‌های آموزشی در مرحله یادگیری و هم برای مجموعه رکوردهای آزمایشی در مرحله ارزیابی محاسبه نمود. همچنین لازمه موفقیت در بهره مندی از علم داده کاوی تفسیر دانش تولید و ارزیابی شده است.

ارزیابی در الگوریتم‌های دسته بندی 
برای سادگی معیارهای ارزیابی الگوریتم‌های دسته بندی، آنها را برای یک مسئله با دو دسته ارائه خواهیم نمود. در ابتدا با مفهوم ماتریس درهم ریختگی (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)، می‌توان اقدام به تعریف معیارهای متنوع ارزیابی روش‌های تولید قوانین انجمنی پرداخت. در عمل معیارهای متعددی برای ارزیابی مجموعه قوانین بدست آمده وجود دارد و لازم است با توجه به تجارب گذشته در مورد میزان مطلوب بودن آنها تصمیم گیری شود. بدین ترتیب که ابتدا معیارهای برتر در مسئله مورد کاوش پس از مشورت با خبرگان حوزه شناسائی شوند، پس از آن قوانین انجمنی بدست آمده از حوزه کاوش، مورد ارزیابی قرار گیرند. 

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


کلاس‌های CSS اعتبارسنجی در Angular

زمانیکه Angular فرمی را تحت نظر قرار می‌دهد، کلاس‌های CSS خاصی را نیز بر اساس حالات عناصر مختلف آن، به آن‌ها متصل خواهد کرد. بر این اساس می‌توان ظاهر این المان‌ها را سفارشی سازی نمود. این کلاس‌ها به شرح زیر هستند:

  کلاس CSS اعتبارسنجی  توضیحات 
  ng-untouched   زمانیکه فرمی برای بار اول رندر می‌شود، تمام فیلدهای آن با کلاس CSS ایی به نام ng-untouched علامتگذاری می‌شوند. 
  ng-touched   همینقدر که کاربر با یک Tab از فیلدی عبور کند، با کلاس ng-touched مزین خواهد شد. بنابراین مهم نیست که حتما داده‌ای وارد شده باشد یا خیر. حتی عبور از یک فیلد نیز به معنای لمس آن نیز می‌باشد. 
 ng-pristine   مربوط به زمانی‌‌است که یک فیلد نه تغییر کرده‌است و نه لمس شده‌است. 
  ng-dirty   همینقدر که کاربر، تغییری را در فیلدی ایجاد کند، آن المان با کلاس ng-dirty مشخص خواهد شد. 
 ng-valid   برای حالت موفقیت آمیز بودن اعتبارسنجی، به آن المان انتساب داده می‌شود. 
  ng-invalid   برای حالت غیر موفقیت آمیز بودن اعتبارسنجی، به آن المان انتساب داده می‌شود. 

برای اینکه بتوانیم این موارد را در عمل مشاهده کنیم، به ابتدای فرم مثال این سری، تغییرات ذیل را اعمال خواهیم کرد:
<div class="form-group">
    <label>First Name</label>
    <input #firstName required type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName">
</div>

<h3>Classes</h3>
<h4>{{ firstName.className }}</h4>
برای اینکه مشخص کنیم چه کلاسی به المان firstName متصل شده‌است، ابتدا نیاز است یک template reference variable را برای آن تعریف کنیم که اینکار را توسط معرفی firstName# انجام داده‌ایم. به این ترتیب است که می‌توان به خاصیت className آن در ادامه دسترسی یافت.


تصویر فوق کلاس‌هایی را نمایش می‌دهد که در اولین بار نمایش فرم، به المان firstName متصل شده‌اند. برای مثال در این حالت کلاس ng-pristine قابل مشاهده‌است و هنوز تغییری در آن حاصل نشده‌است.
در ادامه اگر حرفی را به آن اضافه کنیم:


هنوز هم ng-untouched آن برقرار است؛ اما ng-pristine آن به ng-dirty تبدیل شده‌است. در اینجا حتی اگر کل اطلاعات فیلد را نیز حذف کنیم و آن‌را خالی کنیم یا به حالت اول بازگردانیم نیز کلاس ng-dirty قابل مشاهده‌است. بنابراین اگر حالت فیلدی dirty شد، همواره به همین حالت باقی می‌ماند.
در این لحظه اگر با Tab به فیلد دیگری در فرم مراجعه کنیم:


در اینجا است که کلاس ng-untouched به ng-touched تبدیل می‌شود. بنابراین کلاس‌های مختلف لمس یک فیلد، ارتباطی به افزوده شدن یا حذف کاراکتری از یک فیلد ندارند و فقط به از دست رفتن focus و مراجعه‌ی به فیلد دیگری مرتبط می‌شوند.

اگر به المان تغییر یافته‌ی فوق دقت کنید، ویژگی required نیز به آن اضافه شده‌است (علاوه بر template reference variable ایی که تعریف کردیم). در این حالت کل فیلد را خالی کنید:


همانطور که مشاهده می‌کنید، اکنون کلاس ng-valid به کلاس ng-invalid تغییر یافته‌است.


ارتباط بین کلاس‌های CSS اعتبارسنجی و خواص ngModel

تمام کلاس‌های -ng ایی که در بالا معرفی شدند، معادل‌های خواص ngModel ایی نیز دارند. فقط کافی است -ng آن‌ها را حذف کنید، باقیمانده‌ی آن، نام خاصیت متناظری در ngModel خواهد بود. برای مثال کلاس ng-untouched به خاصیت untouched نگاشت می‌شود و به همین ترتیب برای مابقی.
template reference variable ایی را که تا به اینجا به المان اضافه کردیم (firstName#) به خواص همان المان دسترسی دارد (مانند className آن). اما در ادامه می‌خواهیم این متغیر به ngModel و خواص آن دسترسی داشته باشد و میدان دید آن تغییر کند. به همین جهت تنها کافی است تا ngModel را به این متغیر انتساب دهیم:
<div class="form-group">
    <label>First Name</label>
    <input #firstName="ngModel" required type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName">
</div>

<h4>dirty: {{ firstName.dirty }}</h4>
پس از این تغییر اکنون template reference variable تعریف شده می‌تواند برای نمونه به خاصیت dirty شیء ngModel دسترسی پیدا کند.


یک نکته: در حالت خواص valid و invalid که مرتبط با اعتبارسنجی هستند، خاصیت سومی نیز به نام errors وجود دارد که حاوی اطلاعات بیشتری در مورد خطای اعتبارسنجی رخ داده‌است. بنابراین وجود این خاصیت و نال نبودن آن نیز دلالت بر وجود یک خطای اعتبارسنجی است. از خاصیت errors در ادامه‌ی بحث در قسمت «مدیریت چندین خطای همزمان اعتبارسنجی» استفاده خواهیم کرد.


نمایش بهتر خطاهای اعتبارسنجی با بررسی خواص ngModel

یکی از مزایای کار با خواص ngModel، امکان استفاده‌ی از آن‌ها در عبارات شرطی‌است که نسبت به کلاس‌های CSS معرفی شده‌ی در ابتدای بحث، انعطاف پذیری بیشتری را به همراه خواهند داشت.
<div class="form-group">
    <label>First Name</label>
    <input #firstName="ngModel" #firstNameElement required type="text" class="form-control" 
              name="firstName" [(ngModel)]="model.firstName">
    <div *ngIf="firstName.invalid && firstName.touched" class="alert alert-danger">
        First Name is required.
    </div>      
</div>

<h4>className: {{ firstNameElement.className }}</h4>
<h4>dirty: {{ firstName.dirty }}</h4>
<h4>invalid: {{ firstName.invalid }}</h4>
توسط ngIf می‌توان المانی را به DOM اضافه و یا کلا از آن حذف کرد. در اینجا یک عبارت boolean به آن نسبت داده شده‌است. ابتدا حالت firstName.invalid را بررسی کنید. مشاهده خواهید کرد که اگر فرم برای بار اول و با مقادیر خالی نمایش داده شود، div خطا نیز ظاهر می‌شود که آنچنان خوشایند نیست و بهتر است خطاها را پس از اینکه کاربر مشغول به کار با فرم شد، به او نمایش دهیم؛ تا اینکه از همان ابتدا این خطاها به صورت واضحی نمایش داده شوند. بنابراین && firstName.touched نیز در اینجا اضافه شده‌است. به این ترتیب div نمایش دهنده‌ی alert بوت استرپ، دیگر در اولین بار نمایش یک فرم خالی، ظاهر نخواهد شد. اما اگر کاربر با یک tab از فیلدی خالی رد شد، آنگاه این خطا نمایش داده می‌شود.



نمایش بهتر خطاهای اعتبارسنجی با مزین ساختن المان‌های ورودی

علاوه بر نمایش یک alert بوت استرپی متناظر با یک فیلد غیرمعتبر، می‌توان خود المان‌های ورودی را نیز با شیوه‌نامه‌هایی مزین ساخت.


این کار را در بوت استرپ با افزودن کلاس has-error در کنار form-group انجام می‌دهند. همچنین label نیز باید به کلاس control-label مزین شود تا hass-error بر روی آن نیز تاثیرگذار شود. برای پیاده سازی پویای آن در Angular به روش ذیل عمل می‌شود:
<div class="form-group" [class.has-error]="firstName.invalid && firstName.touched">
   <label class="control-label">First Name</label>
در اینجا روش افزودن شرطی کلاس ویژه‌ی has-error بوت استرپ را به مجموعه کلاس‌های div جاری ملاحظه می‌کنید. هر زمان که شرط ذکر شده برقرار باشد، در عبارت property binding مخصوص class.className، این className را به صورت خودکار به مجموعه کلاس‌های آن المان اضافه می‌کند و برعکس.
بنابراین اگر المان firstName خالی باشد و همچنین با یک Tab از روی آن عبور کرده باشیم، کلاس has-error در کنار کلاس form-group اضافه می‌شود.

روش دوم: همانطور که در ابتدای بحث نیز عنوان شد، Angular بر اساس حالات مختلف یک فیلد، کلاس‌های CSS خاصی را به آن‌ها انتساب می‌دهد. یک چنین کاری را با مقدار دهی این کلاس‌ها در فایل src\styles.css نیز می‌توان انجام داد که دقیقا معادل بررسی خواص invalid و touched با کدنویسی است:
.ng-touched.ng-invalid{
    border: 1px solid red;
}


سایر ویژگی‌های اعتبارسنجی HTML 5

تا اینجا ویژگی استاندارد required را به المان ورودی فرم ثبت اطلاعات کاربران، اضافه کردیم.
<input #firstName="ngModel" #firstNameElement 
required maxlength="3" minlength="2" pattern="^V.*"
type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName">
در اینجا برای مثال اعمال ویژگی‌های maxlength، minlength و pattern را مشاهده می‌کنید. ویژگی pattern برای تعریف عبارات باقاعده بکار می‌رود (برای مثال، نام حتما باید با V شروع شود) و تقریبا در تمام مرورگرها (caniuse.com ) نیز پشتیبانی می‌شود.
برای نمونه minlength همه‌جا پشتیبانی نمی‌شود؛ اما آن‌را می‌توان برای مثال با الگویی مساوی "+..." جایگزین کرد.


مشکل! ذکر چند ویژگی اعتبارسنجی با هم، تداخل ایجاد می‌کنند!

در اینجا چون چهار ویژگی مختلف را به صورت یکجا به یک المان متصل کرده‌ایم، اکنون div ذیل به هر کدام از این ویژگی‌ها به صورت یکسانی واکنش نشان خواهد داد؛ زیرا خاصیت invalid را true می‌کنند:
    <div *ngIf="firstName.invalid && firstName.touched" class="alert alert-danger">
        First Name is required.
    </div>
روش مدیریت این حالت، به صورت ذیل است:
    <div class="form-group" [class.has-error]="firstName.invalid && firstName.touched">
      <label class="control-label">First Name</label>
      <input #firstName="ngModel" #firstNameElement 
             required maxlength="3" minlength="2" pattern="^V.*"
             type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName">
      <div *ngIf="firstName.invalid && firstName.touched">
        <div class="alert alert-info">
           errors: {{ firstName.errors | json }}
        </div> 
        <div class="alert alert-danger" *ngIf="firstName.errors.required">
           Name is required.
        </div>
        <div class="alert alert-danger" *ngIf="firstName.errors.minlength">
           Name should be minimum {{firstName.errors.minlength.requiredLength}} characters.
        </div>
        <div class="alert alert-danger" *ngIf="firstName.errors.maxlength">
           Name should be max {{firstName.errors.maxlength.requiredLength}} characters.
        </div>
        <div class="alert alert-danger" *ngIf="firstName.errors.pattern">
           Name pattern: {{firstName.errors.pattern.requiredPattern}}
        </div>
      </div>      
    </div>
با یک چنین خروجی:


همانطور که در تصویر ملاحظه می‌کنید، محتوای خاصیت errors به صورت JSON، جهت دیباگ نیز درج شده‌است.
بنابراین وجود خاصیت firstName.errors.minlength و یا firstName.errors.pattern به این معنا است که این خطاهای خاص وجود دارند (خاصیت firstName.errors به همراه اضافاتی است) و برعکس نال بودن آن‌ها مؤید عدم وجود خطایی است. به همین جهت می‌توان به ازای هر کدام، یک div جداگانه را تشکیل داد.
مرحله‌ی بعد، استخراج اطلاعات بیشتری از همین شیء و محتوای خاصیت errors است. برای مثال زمانیکه در آن خاصیت minlength ظاهر شد، این خاصیت نیز دارای خاصیتی مانند requiredLength است که از آن می‌توان جهت درج عدد واقعی مورد نیاز این اعتبارسنج استفاده کرد.


بهبود اعتبارسنجی drop down

در حالت فعلی تعریف drop down مثال این سری، نیازی به اعتبارسنجی نیست؛ چون لیست مشخصی از طریق کامپوننت در اختیار این المان قرار می‌گیرد و همواره دقیقا یکی از این عناصر انتخاب خواهند شد. اما اگر گزینه‌ی دیگری را مانند:
<option value="default">Select a Language...</option>
به ابتدای این لیست اضافه کنیم، اینبار نیاز به اعتبارسنجی خواهد بود؛ زیرا اکنون کاربر می‌تواند گزینه‌ای را انتخاب نکند و راهی پیش فرض هم برای اعتبارسنجی این گزینه وجود ندارد.
    <div class="form-group" [class.has-error]="hasPrimaryLanguageError">
      <label class="control-label">Primary Language</label>
      <select class="form-control" name="primaryLanguage" 
                #primaryLanguage
                (blur)="validatePrimaryLanguage(primaryLanguage.value)"
                (change)="validatePrimaryLanguage(primaryLanguage.value)"
                [(ngModel)]="model.primaryLanguage">
          <option value="default">Select a Language...</option>
          <option *ngFor="let lang of languages">
            {{ lang }}
          </option>
      </select>
    </div>
در اینجا اتصال به class.has-error را همانند مثال‌های قبلی مشاهده می‌کنید. اما اینبار به یک خاصیت عمومی تعریف شده‌ی در سطح کامپوننت متصل شده‌است؛ زیرا Angular در مورد این المان خاص، کاری را برای ما انجام نخواهد داد. همچنین پیاده سازی حالت عبور از این کامپوننت با Tab نیز توسط اتصال به رخداد blur صورت گرفته‌است تا هر زمانیکه این فیلد focus را از دست داد، اجرا شود. دسترسی به مقدار جاری انتخابی این select نیز توسط یک template reference variable به نام primaryLanguage# صورت گرفته‌است. به این ترتیب می‌توان به خواص این المان مانند value آن دسترسی پیدا کرد. همچنین چون می‌خواهیم با انتخاب گزینه‌ی دیگری این علامت خطا رفع شود، این متد به رخداد change نیز علاوه بر blur، متصل شده‌است.
export class EmployeeRegisterComponent {
  hasPrimaryLanguageError = false;

  validatePrimaryLanguage(value) {
    if (value === 'default'){
      this.hasPrimaryLanguageError = true;
      }
    else{
      this.hasPrimaryLanguageError = false;
      }
  }
}
کار این متد تغییر مقدار hasPrimaryLanguageError است تا کلاس has-error در فرم نمایش داده شود. در اینجا اگر مقدار primaryLanguage انتخابی همان گزینه‌ی default ابتدایی باشد، خاصیت hasPrimaryLanguageError به true تنظیم می‌شود:



اعتبارسنجی در سطح کل فرم

تا اینجا بررسی‌هایی را که انجام دادیم، در سطح فیلدها بودند. اکنون اگر کاربر به طور کامل تمام این فیلدها را تغییر ندهد و بر روی دکمه‌ی ارسال کلیک کند چطور؟
<form #form="ngForm" novalidate>
زمانیکه فرم جاری را ایجاد کردیم، یک template reference variable را به نام form# نیز به آن نسبت دادیم و چون به ngForm متصل شده‌است، می‌توان به خواص این شیء نیز دسترسی یافت. برای نمونه پس از دکمه‌ی ثبت، عبارت ذیل را تعریف کنید:
<h3> form.valid: {{ form.valid }}</h3>
همانطور که ملاحظه می‌کنید یکی از خواص شیء ngForm که توسط متغیر form قابل دسترسی شده‌است، خاصیت valid می‌باشد که حاصل اعتبارسنجی تمام عناصر داخل فرم است. برای مثال می‌توان از این خاصیت جهت فعال یا غیرفعال کردن دکمه‌ی ثبت اطلاعات نیز استفاده کرد:
<button class="btn btn-primary" type="submit"
            [disabled]="form.invalid">Ok</button>
در اینجا مقدار form.invalid به خاصیت disabled المان متصل شده‌است و اگر اعتبارسنجی فرم با شکست مواجه شود، این دکمه در حالت غیرفعال ظاهر می‌شود.


در قسمت بعد نحوه‌ی ارسال این فرم را به سرور، بررسی می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-04.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
نظرات اشتراک‌ها
مروری بر کاربردهای Anonymous Types
سلام
مواقعی که یه تابع یه anonymous type رو به عنوان ورودی دریافت میکنه از کجا میفهمه من چه پروپرتی هایی رو براش ست کردم؟ 
مثلا: html.actionlink تو mvc یه پیاده سازی داره که html attribute رو به صورت anonymous type میگیره ولی من درک نکردم داخلش چطوری میفهمه من چیا رو فرستادم؟
اشتراک‌ها
بازی 2048 برای تلگرام

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

مهمترین نکته اینکه شما دو تا API تلگرام یکی SetScore  برای ثبت رکورد و دیگری GetHighScores را برای دریافت آخرین رکوردها را پیاده سازی کنید.

بازی 2048 برای تلگرام