مطالب
نحوه‌ی استفاده از کتابخانه‌ی OpenSSL در ویندوز

سؤالی شده به این مضمون : "یه الگوریتم دارم که بر طبق اون باید اعداد تصادفی خیلی بزرگ تولید کنم، اونها رو جمع و ضرب کنم. اینکه چطوری باید از dll یا lib استفاده کنم رو بلد نیستم. از VS2008 استفاده میکنم..."

سؤال در مورد زبان CPP است. کتابخانه‌ی استاندارد انجام اینگونه عملیات برای زبان‌های C و CPP ، کتابخانه‌ی OpenSSL است. البته شاید الان 100 کتابخانه دیگر را هم لیست کنید، اما کسانی که با مباحث رمزنگاری اطلاعات مدتی کار کرده باشند، بعید است سر و کارشان به این کتابخانه نیفتاده باشد و یک استاندارد در این زمینه به شمار می‌رود؛ همچنین به دلیل سورس باز بودن در اکثر سکوهای کاری موجود نیز قابل استفاده است. بنابراین فراگیری نحوه‌ی کار کردن با آن یک مزیت به شمار می‌رود. قسمتی از این کتابخانه‌ی معظم مرتبط است به کار با اعداد بزرگ. این مورد را هم جهت استفاده در الگوریتم RSA نیاز دارد.
برای استفاده از آن در ویندوز ابتدا باید OpenSSL را کامپایل کنید. کار پر دردسری است. به همین جهت یک سایت فقط به این موضوع اختصاص یافته و هربار آخرین نسخه‌ی OpenSSL را برای ویندوز کامپایل می‌کند و در اختیار علاقمندان قرار می‌دهد : +
در حال حاضر یا باید Win32 OpenSSL v1.0.0a و یا Win64 OpenSSL v1.0.0a را دریافت کنید (برنامه‌ی شما اگر 64 بیتی کامپایل شود، dll های 32 بیتی را نمی‌تواند بارگذاری کند و برعکس).

روش استفاده از کتابخانه‌ی OpenSSL در ویژوال CPP :

الف) ابتدا فایل‌های کامپایل شده‌ی فوق را دریافت و نصب کنید. اکنون برای مثال یک پوشه‌ی OpenSSL-Win32 در کامپیوتر شما با محتویات این کتابخانه باید ایجاد شده باشد(اگر نسخه‌ی 32 بیتی را دریافت کرده‌اید).
سپس به پوشه‌ی OpenSSL-Win32\lib\VC آن مراجعه کنید. در اینجا فایل‌های کتابخانه‌ای جهت استفاده در ویژوال CPP قرار گرفته‌اند. اگر از محتویات پوشه OpenSSL-Win32\lib\VC\static استفاده کنید، نیازی به توزیع فایل‌های DLL این کتابخانه نخواهید داشت و اگر از کتابخانه‌های OpenSSL-Win32\lib\VC استفاده کنید، فایل‌های dll را نیز حتما باید به همراه برنامه‌ی خود توزیع نمائید.
سه نوع فایل در آن وجود دارند. ختم شده به MD ، MT و MDd که معانی آن‌ها در مورد چند ریسمانی بودن یا خیر است (برگرفته شده از فایل faq.txt دریافتی):

Single Threaded /ML - MS VC++ often defaults to this for the release version of a new project.
Debug Single Threaded /MLd - MS VC++ often defaults to this for the debug version of a new project.
Multithreaded /MT
Debug Multithreaded /MTd
Multithreaded DLL /MD - OpenSSL defaults to this.
Debug Multithreaded DLL /MDd

ب) جهت سهولت کار، پوشه‌ی OpenSSL قرار گرفته در مسیر OpenSSL-Win32\include را در آدرس زیر کپی نمائید:
C:\Program Files\Microsoft Visual Studio 10.0\VC\include
به این صورت حین استفاده از این کتابخانه نیازی به مشخص سازی محل قرارگیری فایل‌های include نخواهد بود.

ج) اکنون یک پروژه‌ی جدید Visual C++\Win32\Win32 console application را در VS.NET آغاز کنید؛ برای مثال به نام OpenSSLTest .

د) سپس به منوی پروژه، گزینه‌ی خواص پروژه مراجعه کرده و مطابق تصاویر زیر، این فایل‌های کتابخانه‌ای را معرفی کنید (انتخاب MD یا MT یا MDd بر اساس runtime library انتخاب شده است که در تصاویر مشخص گردیده):









ه) اکنون یک مثال ساده در مورد ضرب دو عدد بزرگ به صورت زیر می‌تواند باشد:

#include "stdafx.h"
#include <openssl/bn.h>
#include <string.h>


void RotateBytes(unsigned char *in, int n)
{
unsigned char *e=in+n-1;
do {
unsigned char temp=*in;
*in++=*e;
*e-- =temp;
} while(in<e);
}

int _tmain(int argc, _TCHAR* argv[])
{
//دو عدد بزرگ جهت آزمایش
unsigned char testP[] = {0xD1,0x31,0x85,0x4D,0x00,0xD6,0x31,0x97,0x3A,0xFC,0xD2,0x27,0x02,0xEF,0xC2,0xA7};
unsigned char testA[] = {0xC7,0x1B,0x25,0x72,0x03,0xCB,0x72,0x03,0xCF,0x23,0x27,0x2D,0x00,0xD6,0x31,0x98};

//تبدیل آرایه‌های فوق به فرمت اعداد بزرگ
BIGNUM *a = BN_new();
//it should be in "big-endian" form
RotateBytes(testA, 16);
BN_bin2bn(testA, 16, a);

BIGNUM *p = BN_new();
//it should be in "big-endian" form
RotateBytes(testP, 16);
BN_bin2bn(testP, 16, p);


//ضرب این دو عدد در هم
BIGNUM *result = BN_new();
BN_CTX *ctx = BN_CTX_new();

BN_mul(result, a, p, ctx);

//نمایش نتیجه
//حاصل از چند بایت تشکیل شده؟
int num = BN_num_bytes(result);
if(num>0)
{
unsigned char *tmpdata;
if((tmpdata=(unsigned char *)malloc(num)))
memset(tmpdata, 0, num);

//تبدیل عدد با فرمت اعداد بزرگ به آرایه‌ای از بایت‌ها
BN_bn2bin(result, tmpdata);
RotateBytes(tmpdata, num);

for(int i=0; i<num; i++)
{
if(i%16==0) printf("\n");
printf("%02X ",tmpdata[i]);
}

if(tmpdata) free(tmpdata);
}


//آزاد سازی منابع
BN_free(a);
BN_free(p);
BN_CTX_free(ctx);

return 0;
}


در مورد شرح توابع کتابخانه OpenSSL به اینجا مراجع کنید : +
علت استفاده از تابع RotateBytes ، تغییر endian ورودی است.

مطالب
کارهایی جهت بالابردن کارآیی Entity Framework #3

در قسمت‌های قبلی (^ و ^) راهکارهایی جهت بالا بردن کارآیی، ارائه شد. در ادامه، به آخرین قسمت این سری اشاره خواهم کرد.

فراخوانی متد شناسایی تغییرات

یادآوری: قبل از هر چیز با توجه به این مقاله دانستن این نکته الزامی است که فراخوانی برخی متدها مانند DbSet.Add سبب فراخوانی DataContext.ChangeTracker.DetectChanges خواهند شد.

فرض کنید قصد افزودن 2000 موجودیت دانش آموز را دارید:

for (int i = 0; i < 2000; i++)
{
    Pupil pupil = GetNewPupil();
    db.Pupils.Add(pupil);
}
db.SaveChanges();
در کد بالا بدلیل فراخوانی متد DbSet.Add و به دنبال آن فراخوانی متد DetectChanges در هر بار اجرای حلقه (2000بار) مدت زمان اجرای کد بالا بسیار زیاد است و اگر به پروفایلر نگاهی بیاندازید، اشغال CPU توسط کوئری بالا، بیش از حد متعارف است.

اگر به تصویر بالا دقت کنید بیش از 34 ثانیه (خط 193 قسمت سوم شکل) جهت افزودن 2000 موجودیت به کانتکست سپری شده است. در حالی که درج این 2000 موجودیت کمی بیش از 1 ثانیه (خط 195 قسمت سوم شکل) که 379 میلی ثانیه (قسمت دوم شکل) آن مربوط به اجرای کوئری اختصاص یافته  طول کشیده است.
بیشترین زمان صرف شده‌ی برای درج 2000 موجودیت، در کد برنامه سپری شده است که با بررسی بیشتر در پروفایلر، متوجه زمان بر بودن فراخوانی متد ()DetectChanges که در فضای نام Data.Entity.Core وجود دارد خواهید شد. این متد 2000 بار به تعداد موجودیت هایی که قصد داریم به بانک اطلاعاتی اضافه نماییم، فراخوانی شده است.

همانطور که در شکل بالا مشخص است همان 34 ثانیه جهت ردیابی تغییرات صرف شده است. EF ردیابی تغییرات را بصورت پیش فرض هر زمانی که قصد افزودن یا ویرایش موجودیتی را داشته باشید، انجام خواهد داد و هر چه موجودیت‌های بیشتری را بخواهید ویرایش یا اضافه نمایید، این زمان نیز بیشتر خواهد شد. در حقیقت زمان لازم برای الگوریتم ردیابی تغییرات بصورت نمایی با رشد موجودیت‌ها افزوده می‌شود. به عبارت دیگر اگر این تعداد موجودیت‌ها را به 4000 عدد برسانید، مدت زمان لازم بیش از 2 برابر افزوده خواهد شد.

راه حل اول:
استفاده از متد ()AddRange ارائه شده در EF6 که جهت درج دسته‌ای (Bulk Insert) ارائه شده است:
var list = new List<Pupil>();

for (int i = 0; i < 2000; i++)
{
    Pupil pupil = GetNewPupil();
    list.Add(pupil);
}

db.Pupils.AddRange(list);
db.SaveChanges();

راه حل دوم:

در سناریوهای پیچیده، مانند درج دسته‌ای چندین موجودیت، شاید مجبور به خاموش نمودن این قابلیت شوید:
db.Configuration.AutoDetectChangesEnabled = false;
توجه داشته باشید اگر قصد دارید از امکان ردیابی تغییرات مجددا بهره‌مند شوید، باید این قابلیت را نیز فعال نمایید. با خاموش نمودن ردیابی تغییرات بار دیگر کوئری ابتدایی را اجرا نمایید. مدت زمان لازم جهت افزودن 2000 موجودیت به کانتکست از بیش از 34 ثانیه به 85 میلی ثانیه کاهش یافته است؛ ولی اعمال تغییرات به بانک اطلاعاتی همانند مرتبه اول بیش از 1 ثانیه طول خواهد کشید.


ردیابی تغییرات

هنگامی که موجودیتی را از بانک اطلاعاتی دریافت نمایید، می‌توانید آن را ویرایش نمایید و مجددا به بانک اطلاعاتی اعمال نمایید. چون EF اطلاعی از قصد شما برای موجودیت نمی‌داند، مجبور است تغییرات شما را زیر نظر بگیرد که این زیر نظر گرفتن، هزینه و سربار دارد و این سربار و هزینه برای داده‌های زیاد، بیشتر خواهد شد. بنابراین اگر قصد دارید اطلاعاتی فقط خواندنی را از بانک اطلاعاتی دریافت نمایید، بهتر است صراحتا به EF بگویید این موجودیت را تحت ردیابی قرار ندهد.
string city = "New York";

List<School> schools = db.Schools
    .AsNoTracking()
    .Where(s => s.City == city)
    .Take(100)
    .ToList();

استفاده از متد AsNoTracking  در کد بالا سبب خواهد شد 100 مدرسه که در شهر نیویورک وجود دارد توسط EF، بدون تحت نظر گرفتن آن‌ها از بانک اطلاعاتی دریافت شوند و سرباری نیز تحمیل نشود.

ویوهای از قبل کامپایل شده

معمولا، هنگامی که از EF برای اولین بار استفاده می‌نمایید، ویوهایی ایجاد می‌گردد که برای ایجاد کوئری‌ها مورد استفاده قرار می‌گیرند. این ویوها در طول حیات برنامه فقط یکبار ایجاد می‌شوند. ولی همین یکبار هم زمانبر هستند. خوشبختانه راه‌هایی وجود دارد که ایجاد این ویوها را در زمان runtime انجام نداد و آن راه، استفاده از ویوهای از پیش کامپایل شده است. یکی از راههای ایجاد این ویوها استفاده از Entity Framework Power Tools است. بعد از نصب اکستنشن، بر روی فایل کانتکست راست کلیک کرده و سپس گزینه‌ی Generate Views را از منوی Entity Framework انتخاب کنید.

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


حذف کوئری‌های ابتدایی غیر ضروری

در هنگام شروع به کار با EF، چندین کوئری آغازین بر روی دیتابیس اجرا می‌شوند. یکی از کوئری‌های آغازین جهت تشخیص نسخه‌ی دیتابیس است که همانطور در تصویر زیر مشاهده می‌کنید، در حدود چند میلی ثانیه می‌باشد.

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

 public class CustomManifestTokenResolver : IManifestTokenResolver
{
    public string ResolveManifestToken(DbConnection connection)
    {
        return "2014";
    }
}
و توسط کلاسی که از کلاس DbConfiguration ارث بری کرده است آن را استفاده نماییم.
 public class CustomDbConfiguration : DbConfiguration
{
    public CustomDbConfiguration()
    {
        SetManifestTokenResolver(new CustomManifestTokenResolver());
    }
}


تخریب کانتکست

تخریب و از بین بردن کانتکست هنگامی که به آن نیاز نداریم بسیار ضروری است. یکی از روشهای اصولی برای Disposing کانتکست، محصور کردن آن بوسیله دستور Using است (البته فرض بر این است که قرار نیست از الگوهای اشاره شده استفاده نماییم). در صورت عدم تخریب صحیح کانتکست باید منتظر آسیب جدی به کارایی Garbage Collector جهت آزاد سازی منابع مورد استفاده کانتکست و هم چنین باز نمودن اتصالات جدید به دیتابیس باشید.


پاسخگویی به چندین درخواست بر روی یک کانکشن

EF از قابلیتی بنام Multiple Result Sets می‌تواند بهره ببرد که این قابلیت باعث می‌شود بر روی یک کانکشن ایجاد شده، یک یا چند درخواست از دیتابیس ارسال و یا دریافت شود که سبب کاهش تعداد رفت و برگشت به دیتابیس می‌شود. کاربرد دیگر این قابلیت، زمانی است که تاخیر زیادی (latency) بین اپلیکیشن و دیتابیس وجود دارد.

برای فعالسازی کافی است مقدار زیر را در کانکشن استرینگ اضافه نمایید:

MultipleActiveResultSets=True;


استفاده از متدهای ناهمگام

در C#5 و EF6 پشتیبانی خوبی از متدهای ناهمگام فراهم شده است و اکثر متدهایی مانند ToListAsync, CountAsync, FirstAsync, SaveChangesAsync و غیره که باعث رفت و برگشت به دیتابیس می‌شوند امکان پشتیبانی ناهمگام را نیز دارند. البته این قابلیت برای برنامه‌های یک درخواست در یک زمان شاید مفید نباشد؛ ولی برای برنامه‌های وبی برعکس. در برنامه وب جهت پشتیبانی از بارگذاری همزمان (concurrent) قابلیت ناهمگام (Async) سبب خواهد شد منابع تا زمان اجرای کوئری به ThreadPool بازگردانده شود و برای سرویس دهی مهیا باشند که باعث افزایش scalability خواهد شد.

بررسی و آزمایش با داده‌های واقعی

در اکثر مواقع کارآیی با حجیم شدن داده‌ها کاهش پیدا می‌کند (البته در صورت عدم رعایت اصول استاندارد). بنابراین بررسی کارآیی در محیط هایی با حجم داده‌های بالا ضروری است. هیچ چیز بدتر از آن نیست که همه چیز در محیط توسعه خوب و بی نقص باشد ولی در محیط عملیاتی به شکست بیانجامد. به همین جهت سعی کنید از ابزارهای تولید داده (^ و ^ و ^) برای ایجاد داده‌های آزمایشی استفاده نمایید. سپس کارآیی کوئری خود را مورد بررسی و آزمایش قرار دهید.

مطالب
آپلود فایل‌ها توسط برنامه‌های React به یک سرور ASP.NET Core به همراه نمایش درصد پیشرفت
قصد داریم اطلاعات یک فرم React را به همراه دو فایل الصاقی به آن، به سمت یک سرور ASP.NET Core ارسال کنیم؛ بطوریکه درصد پیشرفت ارسال فایل‌ها، زمان سپری شده، زمان باقی مانده و سرعت آپلود نیز گزارش داده شوند:



پیشنیازها
«بررسی روش آپلود فایل‌ها در ASP.NET Core»
«ارسال فایل و تصویر به همراه داده‌های دیگر از طریق jQuery Ajax »

- در مطلب اول، روش دریافت فایل‌ها از کلاینت، در سمت سرور و ذخیره سازی آن‌ها در یک برنامه‌ی ASP.NET Core بررسی شده‌است که کلیات آن در اینجا نیز صادق است.
- در مطلب دوم، روش کار با FormData استاندارد بررسی شده‌است. هرچند در مطلب جاری از jQuery استفاده نمی‌شود، اما نکات نحوه‌ی کار با شیء FormData استاندارد، در اینجا نیز یکی است.


برپایی پروژه‌های مورد نیاز

ابتدا یک پوشه‌ی جدید مانند UploadFilesSample را ایجاد کرده و در داخل آن دستور زیر را اجرا می‌کنیم:
 dotnet new react
در مورد این قالب که امکان تجربه‌ی توسعه‌ی یکپارچه‌ی ASP.NET Core و React را میسر می‌کند، در مطلب «روش یکی کردن پروژه‌های React و ASP.NET Core» بیشتر بحث کرده‌ایم.
سپس در این پوشه، پوشه‌ی ClientApp پیش‌فرض آن‌را حذف می‌کنیم؛ چون کمی قدیمی است. همچنین فایل‌های کنترلر و سرویس آب و هوای پیش‌فرض آن‌را به همراه پوشه‌ی صفحات Razor آن، حذف و پوشه‌ی خالی wwwroot را نیز به آن اضافه می‌کنیم.
همچنین بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
spa.UseReactDevelopmentServer(npmScript: "start");
از تنظیم زیر استفاده کرده‌ایم تا با هر بار تغییری در کدهای پروژه‌ی ASP.NET، یکبار دیگر از صفر npm start اجرا نشود:
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
بدیهی است در این حالت باید از طریق خط فرمان به پوشه‌ی clientApp وارد شد و دستور npm start را یکبار به صورت دستی اجرا کرد، تا این وب سرور بر روی پورت 3000، راه اندازی شود. البته ما برنامه را به صورت یکپارچه بر روی پورت 5001 وب سرور ASP.NET Core، مرور می‌کنیم.

اکنون در ریشه‌ی پروژه‌ی ASP.NET Core ایجاد شده، دستور زیر را صادر می‌کنیم تا پروژه‌ی کلاینت React را با فرمت جدید آن ایجاد کند:
> create-react-app clientapp
سپس وارد این پوشه‌ی جدید شده و بسته‌های زیر را نصب می‌کنیم:
> cd clientapp
> npm install --save bootstrap axios react-toastify
توضیحات:
- برای استفاده از شیوه‌نامه‌های بوت استرپ، بسته‌ی bootstrap نیز در اینجا نصب می‌شود که برای افزودن فایل bootstrap.css آن به پروژه‌ی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.
- برای نمایش پیام‌های برنامه از کامپوننت react-toastify استفاده می‌کنیم که پس از نصب آن، با مراجعه به فایل app.js نیاز است importهای لازم آن‌را اضافه کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render آن نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />
- برای ارسال فایل‌ها به سمت سرور از کتابخانه‌ی معروف axios استفاده خواهیم کرد.


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

پس از این مقدمات، فایل جدید clientapp\src\components\UploadFileSimple.jsx را ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
import React, { useState } from "react";
import axios from "axios";
import { toast } from "react-toastify";

export default function UploadFileSimple() {

  const [description, setDescription] = useState("");
  const [selectedFile1, setSelectedFile1] = useState();
  const [selectedFile2, setSelectedFile2] = useState();


  return (
    <form>
      <fieldset className="form-group">
        <legend>Support Form</legend>

        <div className="form-group row">
          <label className="form-control-label" htmlFor="description">
            Description
          </label>
          <input
            type="text"
            className="form-control"
            name="description"
            onChange={event => setDescription(event.target.value)}
            value={description}
          />
        </div>

        <div className="form-group row">
          <label className="form-control-label" htmlFor="file1">
            File 1
          </label>
          <input
            type="file"
            className="form-control"
            name="file1"
            onChange={event => setSelectedFile1(event.target.files[0])}
          />
        </div>

        <div className="form-group row">
          <label className="form-control-label" htmlFor="file2">
            File 2
          </label>
          <input
            type="file"
            className="form-control"
            name="file2"
            onChange={event => setSelectedFile2(event.target.files[0])}
          />
        </div>

        <div className="form-group row">
          <button
            className="btn btn-primary"
            type="submit"
          >
            Submit
          </button>
        </div>
      </fieldset>
    </form>
  );
}
کاری که تا این مرحله انجام شده، بازگشت UI فرم برنامه توسط یک functional component است.
- توسط آن یک textbox به همراه دو فیلد ارسال فایل، به فرم اضافه شده‌اند.
- مرحله‌ی بعد، دسترسی به فایل‌های انتخابی کاربر و همچنین مقدار توضیحات وارد شده‌است. به همین جهت با استفاده از useState Hook، روش دریافت و تنظیم این مقادیر را مشخص کرده‌ایم:
  const [description, setDescription] = useState("");
  const [selectedFile1, setSelectedFile1] = useState();
  const [selectedFile2, setSelectedFile2] = useState();
با React Hooks، بجای تعریف یک state، به صورت خاصیت، آن‌را صرفا use می‌کنیم و یا همان useState، که یک تابع است و باید در ابتدای کامپوننت، مورد استفاده قرار گیرد. این متد برای شروع به کار، نیاز به یک state آغازین را دارد؛ مانند انتساب یک رشته‌ی خالی به description. سپس اولین خروجی متد useState که داخل یک آرایه مشخص شده‌است، همان متغیر description است که توسط state ردیابی خواهد شد. اینبار بجای متد this.setState قبلی که یک متد عمومی بود، متدی اختصاصی را صرفا جهت تغییر مقدار همین متغیر description به نام setDescription به عنوان دومین خروجی متد useState، تعریف می‌کنیم. بنابراین متد useState، یک initialState را دریافت می‌کند و سپس یک مقدار را به همراه یک متد، جهت تغییر state آن، بازگشت می‌دهد. همین کار را برای دو فیلد دیگر نیز تکرار کرده‌ایم. بنابراین selectedFile1، فایلی است که توسط متد setSelectedFile1 تنظیم خواهد شد و این تنظیم، سبب رندر مجدد UI نیز خواهد گردید.
- پس از طراحی state این فرم، مرحله‌ی بعدی، استفاده از متدهای set تمام useStateهای فوق است. برای مثال در مورد یک textbox معمولی، می‌توان آن‌را به صورت inline تعریف کرد و با هر بار تغییری در محتوای آن، این رخ‌داد را به متد setDescription ارسال نمود تا مقدار وارد شده را به متغیر حالت description انتساب دهد:
          <input
            type="text"
            className="form-control"
            name="description"
            onChange={event => setDescription(event.target.value)}
            value={description}
          />
در مورد فیلدهای دریافت فایل‌ها، روش انجام اینکار به صورت زیر است:
          <input
            type="file"
            className="form-control"
            name="file1"
            onChange={event => setSelectedFile1(event.target.files[0])}
          />
چون المان‌های دریافت فایل می‌توانند بیش از یک فایل را نیز دریافت کنند (اگر ویژگی multiple، به تعریف تگ آن‌ها اضافه شود)، به همین جهت خاصیت files بر روی آن‌ها قابل دسترسی شده‌است. اما چون در اینجا ویژگی multiple ذکر نشده‌است، بنابراین تنها یک فایل توسط آن‌ها قابل دریافت است و به همین جهت دسترسی به اولین فایل و یا files[0] را در اینجا مشاهده می‌کنید. بنابراین با فراخوانی متد setSelectedFile1، اکنون متغیر حالت selectedFile1، مقدار دهی شده و قابل استفاده است.


تشکیل مدل ارسال داده‌ها به سمت سرور

در فرم‌های معمولی، عموما داده‌ها به صورت یک شیء JSON به سمت سرور ارسال می‌شوند؛ اما در اینجا وضع متفاوت است و به همراه توضیحات وارد شده، دو فایل باینری نیز وجود دارند.
در حالت ارسال متداول فرم‌هایی که به همراه المان‌های دریافت فایل هستند، ابتدا یک ویژگی enctype با مقدار multipart/form-data به المان فرم اضافه می‌شود و سپس این فرم به سادگی قابلیت post-back به سمت سرور را پیدا می‌کند:
<form enctype="multipart/form-data" action="/upload" method="post">
   <input id="file-input" type="file" />
</form>
اما اگر قرار باشد همین فرم را توسط جاوا اسکریپت به سمت سرور ارسال کنیم، روش کار به صورت زیر است:
let file = document.getElementById("file-input").files[0];
let formData = new FormData();
 
formData.append("file", file);
fetch('/upload/image', {method: "POST", body: formData});
ابتدا به خاصیت files و اولین فایل آن دسترسی پیدا کرده و سپس شیء استاندارد FormData را بر اساس آن و تمام فیلدهای فرم تشکیل می‌دهیم. FormData ساختاری شبیه به یک دیکشنری را دارد و از کلیدهایی که متناظر با Id المان‌های فرم و مقادیری متناظر با مقادیر آن المان‌ها هستند، تشکیل می‌شود که توسط متد append آن، به این دیکشنری اضافه خواهند شد. در آخر هم شیء formData را به سمت سرور ارسال می‌کنیم.
در یک برنامه‌ی React نیز باید دقیقا چنین مراحلی طی شوند. تا اینجا کار دسترسی به مقدار files[0] و تشکیل متغیرهای حالت فرم را انجام داده‌ایم. در مرحله‌ی بعد، شیء FormData را تشکیل خواهیم داد:
  // ...

export default function UploadFileSimple() {
  // ...

  const handleSubmit = async event => {
    event.preventDefault();

    const formData = new FormData();
    formData.append("description", description);
    formData.append("file1", selectedFile1);
    formData.append("file2", selectedFile2);


      toast.success("Form has been submitted successfully!");

      setDescription("");
  };

  return (
    <form onSubmit={handleSubmit}>
    </form>
  );
}
به همین جهت، ابتدا کار مدیریت رخ‌داد onSubmit فرم را انجام داده و توسط آن با استفاده از متد preventDefault، از post-back متداول فرم به سمت سرور جلوگیری می‌کنیم. سپس شیء FormData را بر اساس مقادیر حالت متناظر با المان‌های فرم، تشکیل می‌دهیم. کلیدهایی که در اینجا ذکر می‌شوند، نام خواص مدل متناظر سمت سرور را نیز تشکیل خواهند داد.


ارسال مدل داده‌های فرم React به سمت سرور

پس از تشکیل شیء FormData در متد مدیریت کننده‌ی handleSubmit، اکنون با استفاده از کتابخانه‌ی axios، کار ارسال این اطلاعات را به سمت سرور انجام خواهیم داد:
  // ...

export default function UploadFileSimple() {
  const apiUrl = "https://localhost:5001/api/SimpleUpload/SaveTicket";

  // ...
  const [isUploading, setIsUploading] = useState(false);

  const handleSubmit = async event => {
    event.preventDefault();

    const formData = new FormData();
    formData.append("description", description);
    formData.append("file1", selectedFile1);
    formData.append("file2", selectedFile2);

    try {
      setIsUploading(true);

      const { data } = await axios.post(apiUrl, formData, {
        headers: {
          "Content-Type": "multipart/form-data"
        }}
      });

      toast.success("Form has been submitted successfully!");

      console.log("uploadResult", data);

      setIsUploading(false);
      setDescription("");
    } catch (error) {
      setIsUploading(false);
      toast.error(error);
    }
  };


  return (
  // ...
  );
}
در اینجا نحوه‌ی ارسال شیء FormData را توسط کتابخانه‌ی axios به سمت سرور مشاهده می‌کنید. با استفاده از متد post آن، به سمت مسیر api/SimpleUpload/SaveTicket که آن‌را در ادامه تکمیل خواهیم کرد، شیء formData متناظر با اطلاعات فرم، به صورت async، ارسال شده‌است. همچنین headers آن نیز به همان «"enctype="multipart/form-data» که پیشتر توضیح داده شد، تنظیم شده‌است.
در قطعه کد فوق، متغیر جدید حالت isLoading را نیز مشاهده می‌کنید. از آن می‌توان برای فعال و غیرفعال کردن دکمه‌ی submit فرم در زمان ارسال اطلاعات به سمت سرور، استفاده کرد:
<button
   disabled={ isUploading }
   className="btn btn-primary"
   type="submit"
>
  Submit
</button>
به این ترتیب اگر فراخوانی await axios.post هنوز به پایان نرسیده باشد، مقدار isUploading مساوی true بوده و سبب غیرفعال شدن دکمه‌ی submit می‌شود.


اعتبارسنجی سمت کلاینت فایل‌های ارسالی به سمت سرور

در اینجا شاید نیاز باشد نوع و یا اندازه‌ی فایل‌های انتخابی توسط کاربر را تعیین اعتبار کرد. به همین جهت متدی را برای اینکار به صورت زیر تهیه می‌کنیم:
  const isFileValid = selectedFile => {
    if (!selectedFile) {
      // toast.error("Please select a file.");
      return false;
    }

    const allowedMimeTypes = [
      "image/png",
      "image/jpeg",
      "image/gif",
      "image/svg+xml"
    ];
    if (!allowedMimeTypes.includes(selectedFile.type)) {
      toast.error(`Invalid file type: ${selectedFile.type}`);
      return false;
    }

    const maxFileSize = 1024 * 500;
    const fileSize = selectedFile.size;
    if (fileSize > maxFileSize) {
      toast.error(
        `File size ${(fileSize / 1024).toFixed(
          2
        )} KB must be less than ${maxFileSize / 1024} KB`
      );
      return false;
    }

    return true;
  };
در اینجا ابتدا بررسی می‌شود که آیا فایلی انتخاب شده‌است یا خیر؟ سپس فایل انتخاب شده، باید دارای یکی از MimeTypeهای تعریف شده باشد. همچنین اندازه‌ی آن نیز نباید بیشتر از 500 کیلوبایت باشد. در هر کدام از این موارد، یک خطا توسط react-toastify به کاربر نمایش داده خواهد شد.

اکنون برای استفاده‌ی از این متد دو راه وجود دارد:
الف) استفاده از آن در متد مدیریت کننده‌ی submit اطلاعات:
  const handleSubmit = async event => {
    event.preventDefault();

    if (!isFileValid(selectedFile1) || !isFileValid(selectedFile2)) {
      return;
    }
در ابتدای متد مدیریت کننده‌ی handleSubmit، متد isFileValid را بر روی دو متغیر حالتی که حاوی اطلاعات فایل‌های انتخابی توسط کاربر هستند، فراخوانی می‌کنیم.

ب) استفاده‌ی از آن جهت غیرفعال کردن دکمه‌ی submit:
<button
            disabled={
              isUploading ||
              !isFileValid(selectedFile1) ||
              !isFileValid(selectedFile2)
            }
            className="btn btn-primary"
            type="submit"
>
   Submit
</button>
می‌توان دقیقا در همان زمانیکه کاربر فایلی را انتخاب می‌کند نیز به انتخاب او واکنش نشان داد. چون مقدار دهی‌های متغیرهای حالت، همواره سبب رندر مجدد فرم می‌شوند و در این حالت مقدار ویژگی disabled نیز محاسبه‌ی مجدد خواهد شد، بنابراین در همان زمانیکه کاربر فایلی را انتخاب می‌کند، متد isFileValid نیز بر روی آن فراخوانی شده و در صورت نیاز، خطایی به او نمایش داده می‌شود.


نمایش درصد پیشرفت آپلود فایل‌ها

کتابخانه‌ی axios، امکان دسترسی به میزان اطلاعات آپلود شده‌ی به سمت سرور را به صورت یک رخ‌داد فراهم کرده‌است که در ادامه از آن برای نمایش درصد پیشرفت آپلود فایل‌ها استفاده می‌کنیم:
      const startTime = Date.now();

      const { data } = await axios.post(apiUrl, formData, {
        headers: {
          "Content-Type": "multipart/form-data"
        },
        onUploadProgress: progressEvent => {
          const { loaded, total } = progressEvent;

          const timeElapsed = Date.now() - startTime;
          const uploadSpeed = loaded / (timeElapsed / 1000);

          setUploadProgress({
            queueProgress: Math.round((loaded / total) * 100),
            uploadTimeRemaining: Math.ceil((total - loaded) / uploadSpeed),
            uploadTimeElapsed: Math.ceil(timeElapsed / 1000),
            uploadSpeed: (uploadSpeed / 1024).toFixed(2)
          });
        }
      });
هر بار که متد رویدادگردان onUploadProgress فراخوانی می‌شود، به همراه اطلاعات شیء progressEvent است که خواص loaded آن به معنای میزان اطلاعات آپلود شده و total هم جمع کل اندازه‌ی اطلاعات در حال ارسال است. بر این اساس و همچنین زمان شروع عملیات، می‌توان اطلاعاتی مانند درصد پیشرفت عملیات، مدت زمان باقیمانده، مدت زمان سپری شده و سرعت آپلود اطلاعات را محاسبه کرد و سپس توسط آن، شیء state ویژه‌ای را به روز رسانی کرد که به صورت زیر تعریف می‌شود:
  const [uploadProgress, setUploadProgress] = useState({
    queueProgress: 0,
    uploadTimeRemaining: 0,
    uploadTimeElapsed: 0,
    uploadSpeed: 0
  });
هر بار به روز رسانی state، سبب رندر مجدد UI می‌شود. به همین جهت متدی را برای رندر جدولی که اطلاعات شیء state فوق را نمایش می‌دهد، به صورت زیر تهیه می‌کنیم:
  const showUploadProgress = () => {
    const {
      queueProgress,
      uploadTimeRemaining,
      uploadTimeElapsed,
      uploadSpeed
    } = uploadProgress;

    if (queueProgress <= 0) {
      return <></>;
    }

    return (
      <table className="table">
        <thead>
          <tr>
            <th width="15%">Event</th>
            <th>Status</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>
              <strong>Elapsed time</strong>
            </td>
            <td>{uploadTimeElapsed} second(s)</td>
          </tr>
          <tr>
            <td>
              <strong>Remaining time</strong>
            </td>
            <td>{uploadTimeRemaining} second(s)</td>
          </tr>
          <tr>
            <td>
              <strong>Upload speed</strong>
            </td>
            <td>{uploadSpeed} KB/s</td>
          </tr>
          <tr>
            <td>
              <strong>Queue progress</strong>
            </td>
            <td>
              <div
                className="progress-bar progress-bar-info progress-bar-striped"
                role="progressbar"
                aria-valuemin="0"
                aria-valuemax="100"
                aria-valuenow={queueProgress}
                style={{ width: queueProgress + "%" }}
              >
                {queueProgress}%
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    );
  };
و این متد را به این شکل در ذیل المان fieldset فرم، اضافه می‌کنیم تا کار رندر نهایی را انجام دهد:
{showUploadProgress()}
هربار که state به روز می‌شود، مقدار شیء uploadProgress دریافت شده و بر اساس آن، 4 سطر جدول نمایش پیشرفت آپلود، تکمیل می‌شوند.
در اینجا از کامپوننت progress-bar خود بوت استرپ برای نمایش درصد آپلود فایل‌ها استفاده شده‌است. اگر style آن‌را هر بار با مقدار جدید queueProgress به روز رسانی کنیم، سبب نمایش پویای این progress-bar خواهد شد.

یک نکته: اگر می‌خواهید درصد پیشرفت آپلود را در حالت آزمایش local بهتر مشاهده کنید، دربرگه‌ی network، سرعت را بر روی 3G تنظیم کنید (مانند تصویر ابتدای بحث)؛ در غیراینصورت همان ابتدای کار به علت بالا بودن سرعت ارسال فایل‌ها، 100 درصد را مشاهده خواهید کرد.


دریافت فرم React درخواست پشتیبانی، در سمت سرور و ذخیره‌ی فایل‌های آن‌

بر اساس نحوه‌ی تشکیل FormData سمت کلاینت:
const formData = new FormData();
formData.append("description", description);
formData.append("file1", selectedFile1);
formData.append("file2", selectedFile2);
مدل سمت سرور معادل با آن به صورت زیر خواهد بود:
using Microsoft.AspNetCore.Http;

namespace UploadFilesSample.Models
{
    public class Ticket
    {
        public int Id { set; get; }

        public string Description { set; get; }

        public IFormFile File1 { set; get; }

        public IFormFile File2 { set; get; }
    }
}
که در اینجا هر selectedFile سمت کلاینت، به یک IFormFile سمت سرور نگاشت می‌شود. نام این خواص نیز باید با نام کلیدهای اضافه شده‌ی به دیکشنری FormData، یکی باشند.
پس از آن کنترلر ذخیره سازی اطلاعات Ticket را مشاهده می‌کنید:
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using UploadFilesSample.Models;

namespace UploadFilesSample.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SimpleUploadController : Controller
    {
        private readonly IWebHostEnvironment _environment;

        public SimpleUploadController(IWebHostEnvironment environment)
        {
            _environment = environment;
        }

        [HttpPost("[action]")]
        public async Task<IActionResult> SaveTicket([FromForm]Ticket ticket)
        {
            var file1Path = await saveFileAsync(ticket.File1);
            var file2Path = await saveFileAsync(ticket.File2);

            //TODO: save the ticket ... get id

            return Created("", new { id = 1001 });
        }

        private async Task<string> saveFileAsync(IFormFile file)
        {
            const string uploadsFolder = "uploads";
            var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
            if (!Directory.Exists(uploadsRootFolder))
            {
                Directory.CreateDirectory(uploadsRootFolder);
            }

            //TODO: Do security checks ...!

            if (file == null || file.Length == 0)
            {
                return string.Empty;
            }

            var filePath = Path.Combine(uploadsRootFolder, file.FileName);
            using (var fileStream = new FileStream(filePath, FileMode.Create))
            {
                await file.CopyToAsync(fileStream);
            }

            return $"/{uploadsFolder}/{file.Name}";
        }
    }
}
توضیحات تکمیلی:
- تزریق IWebHostEnvironment در سازنده‌ی کلاس کنترلر، سبب می‌شود تا از طریق خاصیت WebRootPath آن، به wwwroot دسترسی پیدا کنیم و فایل‌های نهایی را در آنجا ذخیره سازی کنیم.
- همانطور که ملاحظه می‌کنید، هنوز هم model binding کار کرده و می‌توان شیء Ticket را به نحو متداولی دریافت کرد:
public async Task<IActionResult> SaveTicket([FromForm]Ticket ticket)
ویژگی FromForm نیز مرتبط است به هدر multipart/form-data ارسالی از سمت کلاینت:
      const { data } = await axios.post(apiUrl, formData, {
        headers: {
          "Content-Type": "multipart/form-data"
        }}
      });


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: UploadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگی‌های سمت کلاینت نیز می‌شود، ابتدا به پوشه‌ی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار می‌کند. سپس به پوشه‌ی اصلی برنامه‌ی ASP.NET Core بازگشت شده و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائه‌ی برنامه‌ی React بر روی پورت 5001 می‌شود.
مطالب
چگونه Regex سریعتری داشته باشیم؟

نکاتی را در هنگام کار با عبارات با قاعده در دات نت باید رعایت نمود تا بتوان به حداکثر کارآیی و سرعت دست یافت:
  • 1- ایجاد اشیاء Regex هزینه بر هستند. برای مثال اگر متد شما که در آن شیء Regex را ایجاد کرده‌اید مرتبا فراخوانی می‌شود، این شیء را به صورت یک متغیر محلی خارج از بدنه تابع تعریف کنید. یا به همین صورت هرگز در یک حلقه اشیاء Regex را بارها و بارها ایجاد نکنید.
  • 2-از گزینه RegexOptions.Compiled استفاده کنید. با اینکار زمانیکه برنامه شما اجرا می‌شود، عبارت باقاعده در حافظه کامپایل شده و به بهبود کارآیی 30 درصدی دست خواهید یافت. اگر از این گزینه استفاده نشود، هر بار که شیء Regex مورد استفاده قرار می‌گیرد، عبارت باقاعده شما همانند یک اسکریپت باید مجددا تفسیر شود.
  • 3- اشیاء Regex را از نوع static readonly تعریف کنید تا بازهم کارآیی را افزایش دهید (اشیایی ثابت در زمان اجرا و همچنین اشاره‌گری هستند به آن شیء و نه مقدار آن).

خلاصه موارد فوق:

private static readonly Regex _valueFormatMatch = new Regex(@"[0-9]", RegexOptions.Compiled);

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

مطالب
Roslyn #6
معرفی Analyzers

پیشنیاز این بحث نصب مواردی است که در مطلب «شروع به کار با Roslyn » در قسمت دوم عنوان شدند:
الف) نصب SDK ویژوال استودیوی 2015
ب) نصب قالب‌های ایجاد پروژه‌های مخصوص Roslyn

البته این قالب‌ها چیزی بیشتر از ایجاد یک پروژه‌ی کلاس Library جدید و افزودن ارجاعاتی به بسته‌ی نیوگت Microsoft.CodeAnalysis، نیستند. اما درکل زمان ایجاد و تنظیم این نوع پروژه‌ها را خیلی کاهش می‌دهند و همچنین یک پروژه‌ی تست را ایجاد کرده و تولید بسته‌ی نیوگت و فایل VSIX را نیز بسیار ساده می‌کنند.


هدف از تولید Analyzers

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


بررسی مثال معتبری که می‌تواند بهتر باشد

در اینجا یک کلاس نمونه را مشاهده می‌کنید که در آن فیلدهای کلاس به صورت public تعریف شده‌اند.
    public class Student
    {
        public string FirstName;
        public string LastName;
        public int TotalPointsEarned;

        public void TakeExam(int pointsForExam)
        {
            TotalPointsEarned += pointsForExam;
        }

        public void ExtraCredit(int extraPoints)
        {
            TotalPointsEarned += extraPoints;
        }


        public int PointsEarned { get { return TotalPointsEarned; } }
    }
هرچند این کلاس از دید کامپایلر بدون مشکل است و کامپایل می‌شود، اما از لحاظ اصول کپسوله سازی اطلاعات دارای مشکل است و نباید جمع امتیازات کسب شده‌ی یک دانش آموز به صورت مستقیم و بدون مراجعه‌ی به متدهای معرفی شده، از طریق فیلدهای عمومی آن قابل تغییر باشد.
بنابراین در ادامه هدف ما این است که یک Roslyn Analyzer جدید را طراحی کنیم تا از طریق آن هشدارهایی را جهت تبدیل فیلدهای عمومی به خصوصی، به برنامه نویس نمایش دهیم.


با اجرای افزونه‌ی View->Other windows->Syntax visualizer، تصویر فوق نمایان خواهد شد. بنابراین در اینجا نیاز است FieldDeclaration‌ها را یافته و سپس tokenهای آن‌ها را بررسی کنیم و مشخص کنیم که آیا نوع یا Kind آن‌ها public است (PublicKeyword) یا خیر؟ اگر بلی، آن مورد را به صورت یک Diagnostic جدید گزارش می‌دهیم.


ایجاد اولین Roslyn Analyzer

پس از نصب پیشنیازهای بحث، به شاخه‌ی قالب‌های extensibility در ویژوال استودیو مراجعه کرده و یک پروژه‌ی جدید از نوع Analyzer with code fix را آغاز کنید.


قالب Stand-alone code analysis tool آن دقیقا همان برنامه‌های کنسول بحث شده‌ی در قسمت‌های قبل است که تنها ارجاعی را به بسته‌ی نیوگت Microsoft.CodeAnalysis به صورت خودکار دارد.
قالب پروژه‌ی Analyzer with code fix علاوه بر ایجاد پروژه‌های Test و VSIX جهت بسته بندی آنالایزر تولید شده، دارای دو فایل DiagnosticAnalyzer.cs و CodeFixProvider.cs پیش فرض نیز هست. این دو فایل قالب‌هایی را جهت شروع به کار تهیه‌ی آنالیز کننده‌های مبتنی بر Roslyn ارائه می‌دهند. کار DiagnosticAnalyzer آنالیز کد و ارائه‌ی خطاهایی جهت نمایش به ویژوال استودیو است و CodeFixProvider این امکان را مهیا می‌کند که این خطای جدید عنوان شده‌ی توسط آنالایزر، چگونه باید برطرف شود و راه‌کار بازنویسی Syntax tree آن‌را ارائه می‌دهد.
همین پروژه‌ی پیش فرض ایجاد شده نیز قابل اجرا است. اگر بر روی F5 کلیک کنید، یک کپی جدید و محصور شده‌ی ویژوال استودیو را باز می‌کند که در آن افزونه‌ی در حال تولید به صورت پیش فرض و محدود نصب شده‌است. اکنون اگر پروژه‌ی جدیدی را جهت آزمایش، در این وهله‌ی محصور شده‌ی ویژوال استودیو باز کنیم، قابلیت اجرای خودکار آنالایزر در حال توسعه را فراهم می‌کند. به این ترتیب کار تست و دیباگ آنالایزرها با سهولت بیشتری قابل انجام است.
این پروژه‌ی پیش فرض، کار تبدیل نام فضاهای نام را به upper case، به صورت خودکار انجام می‌دهد (که البته بی‌معنا است و صرفا جهت نمایش و ارائه‌ی قالب‌های شروع به کار مفید است).
نکته‌ی دیگر آن، تعریف تمام رشته‌های مورد نیاز آنالایزر در یک فایل resource به نام Resources.resx است که در جهت بومی سازی پیام‌های خطای آن می‌تواند بسیار مفید باشد.

در ادامه کدهای فایل DiagnosticAnalyzer.cs را به صورت ذیل تغییر دهید:
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace CodingStandards
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class CodingStandardsAnalyzer : DiagnosticAnalyzer
    {
        public const string DiagnosticId = "CodingStandards";

        // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
        internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
        internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
        internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
        internal const string Category = "Naming";

        internal static DiagnosticDescriptor Rule = 
            new DiagnosticDescriptor(
                DiagnosticId, 
                Title, 
                MessageFormat, 
                Category, 
                DiagnosticSeverity.Error, 
                isEnabledByDefault: true, 
                description: Description);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        {
            get { return ImmutableArray.Create(Rule); }
        }

        public override void Initialize(AnalysisContext context)
        {
            // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols
            context.RegisterSyntaxNodeAction(analyzeFieldDeclaration, SyntaxKind.FieldDeclaration);
        }

        static void analyzeFieldDeclaration(SyntaxNodeAnalysisContext context)
        {
            var fieldDeclaration = context.Node as FieldDeclarationSyntax;
            if (fieldDeclaration == null) return;
            var accessToken = fieldDeclaration
                                .ChildTokens()
                                .SingleOrDefault(token => token.Kind() == SyntaxKind.PublicKeyword);

            // Note: Not finding protected or internal
            if (accessToken.Kind() != SyntaxKind.None)
            {
                // Find the name of the field:
                var name = fieldDeclaration.DescendantTokens()
                              .SingleOrDefault(token => token.IsKind(SyntaxKind.IdentifierToken)).Value;
                var diagnostic = Diagnostic.Create(Rule, fieldDeclaration.GetLocation(), name, accessToken.Value);
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
}
توضیحات:

اولین کاری که در این کلاس انجام شده، خواندن سه رشته‌ی AnalyzerDescription (توضیحی در مورد آنالایزر)، AnalyzerMessageFormat (پیامی که به کاربر نمایش داده می‌شود) و AnalyzerTitle (عنوان پیام) از فایل Resources.resx است. این فایل را گشوده و محتوای آن‌را مطابق تنظیمات ذیل تغییر دهید:


سپس کار به متد Initialize می‌رسد. در اینجا برخلاف مثال‌های قسمت‌های قبل، context مورد نیاز، توسط پارامترهای override شده‌ی کلاس پایه DiagnosticAnalyzer فراهم می‌شوند. برای مثال در متد Initialize، این فرصت را خواهیم داشت تا به ویژوال استودیو اعلام کنیم، قصد آنالیز فیلدها یا FieldDeclaration را داریم. پارامتر اول متد RegisterSyntaxNodeAction یک delegate یا Action است. این Action کار فراهم آوردن context کاری را برعهده دارد که نحوه‌ی استفاده‌ی از آن‌را در متد analyzeFieldDeclaration می‌توانید ملاحظه کنید.
سپس در اینجا نوع نود در حال آنالیز (همان نودی که کاربر در ویژوال استودیو انتخاب کرده‌است یا در حال کار با آن است)، به نوع تعریف فیلد تبدیل می‌شود. سپس توکن‌های آن استخراج شده و بررسی می‌شود که آیا یکی از این توکن‌ها کلمه‌ی کلیدی public هست یا خیر؟ اگر این فیلد عمومی تعریف شده بود، نام آن‌را یافته و به عنوان یک Diagnostic جدید بازگشت و گزارش می‌دهیم.


ایجاد اولین Code fixer

در ادامه فایل CodeFixProvider.cs پیش فرض را گشوده و تغییرات ذیل را به آن اعمال کنید. در اینجا مهم‌ترین تغییر صورت گرفته نسبت به قالب پیش فرض، اضافه شدن متد makePrivateDeclarationAsync بجای متد MakeUppercaseAsync از پیش موجود آن است:
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace CodingStandards
{
    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodingStandardsCodeFixProvider)), Shared]
    public class CodingStandardsCodeFixProvider : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds
        {
            get { return ImmutableArray.Create(CodingStandardsAnalyzer.DiagnosticId); }
        }

        public sealed override FixAllProvider GetFixAllProvider()
        {
            return WellKnownFixAllProviders.BatchFixer;
        }

        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

            // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
            var diagnostic = context.Diagnostics.First();
            var diagnosticSpan = diagnostic.Location.SourceSpan;

            // Find the type declaration identified by the diagnostic.
            var declaration = root.FindToken(diagnosticSpan.Start)
                                   .Parent.AncestorsAndSelf().OfType<FieldDeclarationSyntax>()
                                   .First();

            // Register a code action that will invoke the fix.
            context.RegisterCodeFix(
                CodeAction.Create("Make Private", 
                c => makePrivateDeclarationAsync(context.Document, declaration, c)),
                diagnostic);
        }

        async Task<Document> makePrivateDeclarationAsync(Document document, FieldDeclarationSyntax declaration, CancellationToken c)
        {
            var accessToken = declaration.ChildTokens()
                .SingleOrDefault(token => token.Kind() == SyntaxKind.PublicKeyword);

            var privateAccessToken = SyntaxFactory.Token(SyntaxKind.PrivateKeyword);

            var root = await document.GetSyntaxRootAsync(c);
            var newRoot = root.ReplaceToken(accessToken, privateAccessToken);

            return document.WithSyntaxRoot(newRoot);
        }
    }
}
اولین کاری که در یک code fixer باید مشخص شود، تعیین FixableDiagnosticIds آن است. یعنی کدام آنالایزرهای از پیش تعیین شده‌ای قرار است توسط این code fixer مدیریت شوند که در اینجا همان Id آنالایزر قسمت قبل را مشخص کرده‌ایم. به این ترتیب ویژوال استودیو تشخیص می‌دهد که خطای گزارش شده‌ی توسط CodingStandardsAnalyzer قسمت قبل، توسط کدام code fixer موجود قابل رفع است.
کاری که در متد RegisterCodeFixesAsync انجام می‌شود، مشخص کردن اولین مکانی است که مشکلی در آن گزارش شده‌است. سپس به این مکان منوی Make Private با متد متناظر با آن معرفی می‌شود. در این متد، اولین توکن public، مشخص شده و سپس با یک توکن private جایگزین می‌شود. اکنون این syntax tree بازنویسی شده بازگشت داده می‌شود. با Syntax Factory در قسمت سوم آشنا شدیم.

خوب، تا اینجا یک analyzer و یک code fixer را تهیه کرده‌ایم. برای آزمایش آن دکمه‌ی F5 را فشار دهید تا وهله‌ای جدید از ویژوال استودیو که این آنالایزر جدید در آن نصب شده‌است، آغاز شود. البته باید دقت داشت که در اینجا باید پروژه‌ی CodingStandards.Vsix را به عنوان پروژه‌ی آغازین ویژوال استودیو معرفی کنید؛ چون پروژه‌ی class library آنالایزرها را نمی‌توان مستقیما اجرا کرد. همچنین یکبار کل solution را نیز build کنید.
پس از اینکه وهله‌ی جدید ویژوال استودیو شروع به کار کرد (بار اول اجرای آن کمی زمانبر است؛ زیرا باید تنظیمات وهله‌ی ویژه‌ی اجرای افزونه‌ها را از ابتدا اعمال کند)، همان پروژه‌ی Student ابتدای بحث را در آن باز کنید.


نتیجه‌ی اعمال این افزونه‌ی جدید را در تصویر فوق ملاحظه می‌کنید. زیر سطرهای دارای فیلد عمومی، خط قرمز کشیده شده‌است (به علت تعریف DiagnosticSeverity.Error). همچنین حالت فعلی و حالت برطرف شده را نیز با رنگ‌های قرمز و سبز می‌توان مشاهده کرد. کلیک بر روی گزینه‌ی make private، سبب اصلاح خودکار آن سطر می‌گردد.


روش دوم آزمایش یک Roslyn Analyzer

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


نود references را باز کرده و سپس بر روی گزینه‌ی analyzers کلیک راست نمائید. در اینجا گزینه‌ی Add analyzer را انتخاب کنید. در صفحه‌ی باز شده بر روی دکمه‌ی browse کلیک کنید. در اینجا می‌توان فایل اسمبلی موجود در پوشه‌ی CodingStandards\bin\Debug را به آن معرفی کرد.


بلافاصله پس از معرفی این اسمبلی، آنالایزر آن شناسایی شده و همچنین فعال می‌گردد.


در این حالت اگر برنامه را کامپایل کنیم، با خطاهای جدید فوق متوقف خواهیم شد و برنامه کامپایل نمی‌شود (به علت تعریف DiagnosticSeverity.Error).
مطالب
OpenCVSharp #16
در قسمت قبل با نحوه‌ی استفاده از یک trained data از پیش آماده شده‌ی تشخیص چهره‌، آشنا شدیم. در این قسمت قصد داریم با نحوه‌ی تولید این فایل‌های XML آشنا شویم و یک تشخیص دهنده‌ی سفارشی را طراحی کنیم.


طراحی classifier سفارشی تشخیص خودروها

برای طراحی یک تشخیص دهنده‌ی سفارشی مبتنی بر الگوریتم‌های Machine learning، نیاز به تعداد زیادی تصویر داریم. در اینجا از بانک تصاویر خودروهای «UIUC Image Database for Car Detection» استفاده خواهیم کرد. در  این بسته، یک سری تصویر positive و negative را می‌توان ملاحظه کرد. تصاویر مثبت، تصاویر انواع و اقسام خودروها هستند (550 عدد) و تصاویر منفی، تصاویر غیر خودرویی (500 عدد)؛ یا به عبارتی، هر تصویری، منهای تصاویر خودرو می‌تواند تصویر منفی باشد.


ایجاد فایل برداری از تصاویر خودروها

در ادامه یک فایل متنی را به نام carImages.txt ایجاد می‌کنیم. هر سطر این فایل چنین فرمتی را خواهد داشت:
 pos/pos-177.pgm 1 0 0 100 40
ابتدا مسیر تصویر مشخص می‌شود. سپس عدد 1 به این معنا است که در این تصویر فقط یک عدد خودرو وجود دارد. 4 عدد بعدی، ابعاد مستطیلی تصویر هستند.
در ادامه فایل متنی دیگری را به نام negativeImages.txt جهت درج اطلاعات تصاویر منفی، ایجاد می‌کنیم. اینبار هر سطر این فایل تنها حاوی مسیر تصویر مدنظر است:
 neg/neg-274.pgm
این دو فایل را می‌توان با استفاده از دو متد ذیل، به سرعت تولید کرد:
private static void createCarImagesFile()
{
    var sb = new StringBuilder();
    foreach (var file in new DirectoryInfo(@"..\..\CarData\CarData\TrainImages").GetFiles("*pos-*.pgm"))
    {
        sb.AppendFormat("{0} {1} {2} {3} {4} {5}{6}", file.FullName, 1, 0, 0, 100, 40, Environment.NewLine);
    }
    File.WriteAllText(@"..\..\CarsInfo\carImages.txt", sb.ToString());
}
 
private static void createNegativeImagesFile()
{
    var sb = new StringBuilder();
    foreach (var file in new DirectoryInfo(@"..\..\CarData\CarData\TrainImages").GetFiles("*neg-*.pgm"))
    {
        sb.AppendFormat("{0}{1}", file.FullName,Environment.NewLine);
    }
    File.WriteAllText(@"..\..\CarsInfo\negativeImages.txt", sb.ToString());
}
برای کامپایل اطلاعات فایل‌های تولید شده، نیاز به فایل opencv_createsamples.exe است. این فایل را در پوشه‌ی opencv\build\x86\vc12\bin بسته‌ی اصلی OpenCV می‌توانید پیدا کنید.
 opencv_createsamples.exe -info carImages.txt -num 550 -w 48 -h 24 -vec cars.vec
پارامترهای این دستور شامل سوئیچ info است؛ به معنای مشخص سازی فایل اطلاعات تصاویر مثبت. سوئیچ num تعداد تصاویر آن‌را تعیین می‌کند و سوئیچ‌های w و h، طول و عرض تصاویر هستند. سوئیچ vec نیز جهت تولید یک فایل vector از این اطلاعات بکار می‌رود.
پس از اجرای این دستور، فایل cars.vec تولید خواهد شد؛ با این خروجی:
 Info file name: carImages.txt
Img file name: (NULL)
Vec file name: cars.vec
BG  file name: (NULL)
Num: 550
BG color: 0
BG threshold: 80
Invert: FALSE
Max intensity deviation: 40
Max x angle: 1.1
Max y angle: 1.1
Max z angle: 0.5
Show samples: FALSE
Original image will be scaled to:
  Width: $backgroundWidth / 48
  Height: $backgroundHeight / 24
Create training samples from images collection...
Done. Created 550 samples
اگر علاقمند هستید که محتویات فایل باینری cars.vec را مشاهده کنید، دستور ذیل را صادر نمائید:
 "c:\opencv\build\x86\vc12\bin\opencv_createsamples.exe" -vec cars.vec -w 48 -h 24


در این پنجره‌ی باز شده، تصاویر بعدی و قبلی را می‌توان با دکمه‌های arrow صفحه کلید، مشاهده کرد.


تبدیل فایل برداری تصاویر خودروها به trained data

تا اینجا موفق شدیم بیش از 500 تصویر خودرو را تبدیل به یک فایل برداری سازگار با OpenCV کنیم. اکنون نیاز است، این اطلاعات پردازش شده و trained data مخصوص الگوریتم‌های machine learning تولید شود. این‌کار را توسط برنامه‌ی opencv_traincascade.exe انجام خواهیم داد. این فایل نیز در پوشه‌ی opencv\build\x86\vc12\bin بسته‌ی اصلی OpenCV موجود است.
دستور ذیل در پوشه‌ی data، بر اساس اطلاعات برداری cars.vec و همچنین تصاویر منفی مشخص شده‌ی در فایل negativeImages.txt، با تعداد هر کدام 500 عدد (این عدد را توصیه شده‌است که اندکی کمتر از تعداد max موجود مشخص کنیم) و تعداد مراحل 2  (هر چقدر این تعداد مراحل بیشتر باشد، فایل نهایی تولید شده دقت بالاتری خواهد داشت؛ اما تولید آن به زمان بیشتری نیاز دارد) اجرا می‌شود. در اینجا featureType به LBP یا Local binary Pattern، تنظیم شده‌است. این الگوریتم از Haar cascade سریعتر است.
 "E:\opencv\bin\opencv_traincascade.exe" -data data -vec cars.vec -bg negativeImages.txt -numPos 500 -numNeg 500 -numStages 2 -w 48 -h 24 -featureType LBP
خروجی اجرای این دستور را می‌توانید در پوشه‌ی data با نام cascade.xml، پیدا کنید. پس از آن، روش استفاده‌ی از این فایل، با مطلب تشخیص چهره تفاوتی ندارد.



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
آموزش فایرباگ - #3 - JavaScript Development
توابع توسعه جاوا اسکریپت در فایرباگ به دو بخش تقسیم می‌شوند :
  • توابع خط فرمان - Command Line API
  • توابع کنسول - Console API
توابع خط فرمان توابعی هستند که فقط در خط فرمان قابل استفاده هستند و توابع کنسول هم توابعی هستند که خارج از محیط خط فرمان ( ، در بین کدهای جاوا اسکریپت برنامه ) هم قابل استفاده هستند .
در این قسمت توابع خط فرمان را بررسی خواهیم کرد و در قسمت بعدی با توابع کنسول آشنا خواهیم شد .

توابع خط فرمان - Command Line API :

این توابع حدود 14 تا هستند که بوسیله آنها می‌توانیم در حین اجرای برنامه تست‌های مختلفی انجام داده یا اطلاعاتی از قسمت‌های مختلف بدست آوریم .
توجه : برای همراه شدن با تست‌های انجام شده در این مقاله می‌توانید کد صفحه‌ی زیر را ذخیره کنید و برای اجرای کدها ، آن‌ها را در قسمت خط فرمان ( در تب کنسول ) قرار بدهید و دکمه‌ی Run ( یا Ctrl + Enter ) را بزنید .
<div id="first" class="content">Content1 with css class and id</div>
<div class="content">
    Content2 with css class
    <a class="links" href="#">Link1</a>
    <a href="#">Link2</a>
</div>
<div>
    Content3 without css class and id
</div>
<input type="button" onclick="myFunc()" value="Run myFunc" />
<input type="text" id="myInput" />

<script type="text/javascript">
    function myFunc() {
        loop(1000);
        loop(50000);
    }
    function loop(number) {
        for (var i = 0; i < number; i++) { }
    }
</script>
قبل از توضیح این توابع ، یک مثال ساده می‌زنیم :
فرض کنید می‌خواهید همه‌ی المنت هایی که با یک selector مطابقت دارند را مشاهده کنید . ( آرایه ای از المنت‌ها دریافت کنید )
$$("div.content");
نتیجه :

می خواهید یکی از توابع جاوا اسکریپت برنامه را اجرا و آن را از لحاظ سرعت ، تعداد فراخوانی شدن و ... بررسی کنید .
profile("myFunc Testing");
myFunc();
profileEnd();
نتیجه :

اکنون با همه‌ی توابع خط فرمان آشنا می‌شویم :

  • $(id)

    معادل دستور document.getElementById است که یک المنت با id داده شده بر می‌گرداند .
    $("first");
    نتیجه :

  • $$(selector)


    آرایه ای از المنت‌های مطابق با selector داده شده بر می‌گرداند .
    $$("div.content")
    نتیجه :

    به تفاوت دو دستور توجه کنید . خروجی دستور اول ، یک المنت است و خروجی دستور دوم یک آرایه از المنت که بین [ و ] قرار گرفته اند .

    برای آشنایی بیشتر با CSS Seletor‌ها به این لینک مراجعه کنید : http://www.w3.org/TR/css3-selectors

  • $x(xPathExpression)

    آرایه ای از المنت هایی را بر می‌گرداند که با xPath داده شده مطابقت داشته باشند .
    var objects = $x("html/body/div[2]/a")
    
    for(var i = 0; i < objects.length; i++) {
       console.log(objects[i]);
    }
    نتیجه :

    برای آشنایی بیشتر با عبارات xPath به این لینک مراجعه کنید : http://www.w3schools.com/xpath

  • dir(object)

    تمام خصوصیات شیء ارسال شده را لیست می‌کند .
    var objects = $x("html/body/div[2]/a")
    
    dir(objects);
    نتیجه :

  • dirxml(node)

    سورس یک المنت را بصورت درختواره ( tree ) پرینت می‌کند . همچنین با کلیک بروی هر node ، فایرباگ آن node را در تب html نمایش می‌دهد .
    var node = $("first");
    dirxml(node);
    نتیجه :

    توجه کنید که این دستور فقط یک node دریافت می‌کند . برای همین اگر از دستور $$("#first") استفاده می‌کنید ، چون این دستور یک آرایه بر می‌گرداند ، باید اولین عضو آرایه را دریافت و ارسال کنید .
    یعنی :
    var node = $$("#first")[0];
    dirxml(node);
  • clear()

    این دستور محیط console را خالی می‌کند . عملکرد این دستور معادل کلیک دکمه‌ی Clear ( در بالا - چپ تب کنسول ) است .

  • inspect(object[,tabName])

    توسط این دستور می‌توانید یک شیء را در مناسب‌ترین تب فایرباگ یا یکی از تب‌های مورد نظر خود ، Inspect کنید .
    var node = $("first");
    
    inspect(node); // inspect in html tab
    inspect(node,'dom'); // inspect in dom tab
  • keys(object)

    آرایه ای از "نام" تمام خصوصیات شیء ارسال شده بر می‌گرداند .
    var obj = $("first");
    keys(obj)
  • values(object)

    آرایه ای از "مقدار" تمام خصوصیات شیء ارسال شده بر می‌گرداند .
    var obj = $("first");
    values(obj)
  • debug(fn) and undebug(fn)

    این متدها یک BreakPoint در ابتدای تابع مشخص شده اضافه/حذف می‌کنند . ( در تب Script ) . به همین ترتیب هنگامی که تابع مورد نظر فراخوانی شود ، در نقطه ای که BreakPoint قرار داده شده توقف خواهد کرد .
    البته می‌شود BreakPoint را دستی هم قرار داد . در اصل این تابع ، این عملیات را ساده‌تر می‌کند .
    debug(myFunc);
    myFunc();
    undebug(myFunc);
  • monitor(fn) and unmonitor(fn)

    این متدها برای فعال/غیرفعال کردن Logging فراخوانی‌های یک تابع استفاده می‌شوند .
    در حالت عادی برای پی بردن به اینکه یک تابع اجرا می‌شود یا نه ، در تابع مورد نظر یک alert قرار می‌دهیم و تست می‌کنیم . که این روش در برنامه برنامه‌های بزرگ صحیح نیست . زیرا در این حالت باید بین حجم زیادی کد به دنبال تابع مورد نظر بگردیم  و سپس alert را قرار بدهیم و بعد اطمینان از صحت عملکرد تابع مجدد آن را حذف کرد ، که با اتلاف زمان و به خطر انداختن کدها همراه است .
    اما با استفاده از این متدها ، تنها نیاز به داشتن اسم تابع داریم ( و نه مکان تابع در کدهای برنامه ) .
    تست monitor :
    monitor(myFunc);
    // now click on "Run myFunc" button
    تست unmonitor :
    unmonitor(myFunc);
    // now click on "Run myFunc" button
  • monitorEvents(object[, types]) and unmonitorEvents(object[, types])

    این متدها عملیات Event Logging برای یک شیء را فعال/غیرفعال می‌کنند . در کنار شیء مورد نظر ، می‌توان نوع رویداد را هم به متد ارسال کرد . در این صورت عملیات Logging فقط برای همان گروه رویداد/رویداد ، فعال/غیرفعال می‌شود .
    منظور از گروه رویداد ، مجموعه رویداد‌های یک شیء است . مثلا mousemove , moseover , mousedown , ... در گروه mouse قرار می‌گیرند . یعنی می‌توانید با ارسال کلمه‌ی mouse فقط رویدادهای mouse را تحت نظر بگیرید یا اینکه فقط یک رویداد را مشخص کنید ، مثل mousedown .
    راه ساده‌تر فعال کردن Event Logging ، رفتن به تب Html ، راست کلیک کردن بروی المنت مورد نظر و فعال کردن گزینه‌ی Log Events می‌باشد .
    var obj = $("myInput");
    monitorEvents(obj,'keypress');

    نتیجه پس از فشردن چند دکمه‌ی کیبورد در myInput :

    توضیحات بیشتر : http://getfirebug.com/wiki/index.php/MonitorEvents

  • profile([title]) and profileEnd()

    این متدها ، JavaScript Profiler را فعال/غیرفعال می‌کنند . هنگام فعال کردن می‌توانید یک عنوان هم برای پروفایل مشخص کنید . در قسمت قبلی مقاله در مورد این قابلیت توضیحاتی ارائه شد .
    سه را برای اجرای Profiler وجود دارد :
    1 - کلیک بروی دکمه‌ی Profiler در بالای تب کنسول .
    2 - استفاده از کد console.profile("ProfileTitle") در کدهای جاوا اسکریپت .
    3 - استفاده از متد profile("Profile Title") در خط فرمان .

    profile("myFunc Testing");
    myFunc();
    profileEnd();
    نتیجه :



    ستون‌های Profiler :

    Function : نام تابع اجرا شده .
    Calls : تعداد دفعات فراخوانی تابع .
    Percent : زمان اجرای تابع در زمان کل ، به درصد .
    Own Time : زمان اجرای تابع به تنهایی . برای مثال در کد ما ، زمان اجرای تابع myFunc به تنهایی تقریبا صفر است زیرا عمیاتی در خود انجام نمی‌دهد و زمان صرف شده در این تابع ، برای اجرای 2 با تابع loop است . این زمان ( Own Time ) زمان اجرای تابع ، منهای زمان صرف شده برای فراخوانی توابع دیگر است .
    Time : زمان اجرای تابع از نقطه‌ی آغاز تا پایان . مجموع زمان اجرای خود تابع به همراه زمان اجرای توابع فراخوانی شده . در کد ما ، این زمان ، مجموع زمان اجرای خود تابع به همراه دو بار فراخوانی تابع loop است .
    Avg : میانگین زمان اجرای هربار تابع . فرمول : Avg = Time / Calls
    Min & Max : حداقل و حداکثر زمان اجرای تابع .
    File : نام فایل و شماره خطی که تابع در آن قرار دارد .

در قسمت بعد با توابع کنسول آشنا خواهیم شد .

منابع :
Packtpub.Firebug.1.5.Editing.Debugging.and.Monitoring.Web.Pages.Apr.2010
مطالب
روش یکی کردن پروژه‌های React و ASP.NET Core
یک روش کار کردن با پروژه‌های SPA، توسعه‌ی مجزای قسمت‌های front-end و back-end است. برای مثال پروژه‌ی React را به صورت جداگانه‌ای توسعه می‌دهیم، پروژه‌ی ASP.NET Core را نیز به همین صورت. هنگام آزمایش برنامه، در یکی دستور npm start را اجرا می‌کنیم تا وب سرور آزمایشی React، آن‌را در آدرس http://localhost:3000 قابل دسترسی کند و در دیگری دستور dotnet watch run را صادر می‌کنیم تا برنامه‌ی وب ASP.NET Core را بر روی آدرس https://localhost:5001 مهیا کند. سپس برای اینکه از پورت 3000 بتوان با پورت 5001 کار کرد، نیاز خواهد بود تا CORS را در برنامه‌ی ASP.NET Core فعالسازی کنیم. در حین ارائه‌ی نهایی برنامه نیز هر کدام را به صورت مجزا publish کرده و بعد هم خروجی نهایی پروژه‌ی SPA را در پوشه‌ی wwwroot برنامه‌ی وب کپی می‌کنیم تا قابل دسترسی و استفاده شود. روش دیگری نیز برای یکی/ساده سازی این تجربه وجود دارد که در این مطلب به آن خواهیم پرداخت.


پیشنیاز: ایجاد یک برنامه‌ی خالی React و ASP.NET Core

یک پوشه‌ی خالی را ایجاد کرده و در آن دستور dotnet new react را صادر کنید، تا قالب خاص پروژه‌های React یکی سازی شده‌ی با پروژه‌های ASP.NET Core، یک پروژه‌ی جدید را ایجاد کند.


همانطور که در تصویر فوق نیز مشاهده می‌کنید، این پروژه از دو برنامه تشکیل شده‌است:
الف) برنامه‌ی SPA که در پوشه‌ی ClientApp قرار گرفته‌است و شامل کدهای کامل یک برنامه‌ی React است.
ب) برنامه‌ی سمت سرور ASP.NET Core که یک برنامه‌ی متداول وب، به همراه فایل Startup.cs و سایر فایل‌های مورد نیاز آن است.

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


تجربه‌ی توسعه‌ی برنامه‌ها توسط این قالب ویژه

اکنون اگر این پروژه‌ی وب را برای مثال با فشردن دکمه‌ی F5 و یا اجرای دستور dotnet run، اجرا کنیم، چه اتفاقی رخ می‌دهد؟
- به صورت خلاصه برنامه‌ی ASP.NET Core شروع به کار کرده و سبب ارائه همزمان برنامه‌ی SPA نیز خواهد شد.
- پورتی که برنامه‌ی وب بر روی آن قرار دارد، با پورتی که برنامه‌ی React بر روی روی آن ارائه می‌شود، یکی است. یعنی نیازی به تنظیمات CORS را ندارد.
- در این حالت اگر در برنامه‌ی React تغییری را ایجاد کنیم (در هر قسمتی از آن)، hot reloading آن هنوز هم برقرار است و سبب بارگذاری مجدد برنامه‌ی SPA در مرورگر خواهد شد و برای اینکار نیازی به توقف و راه اندازی مجدد برنامه‌ی ASP.NET Core نیست.

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


بررسی ساختار فایل Startup.cs یک پروژه‌ی مبتنی بر dotnet new react

برای درک نحوه‌ی عملکرد این قالب ویژه، نیاز است از فایل Startup.cs آن شروع کرد.
// ...
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;

namespace dotnet_template_sample
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllersWithViews();

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/build";
            });
        }
در ابتدا تعریف فضای نام SpaServices را مشاهده می‌کنید. بسته‌ی متناظر با آن در فایل csproj برنامه به صورت زیر ثبت شده‌است:
<ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.2" />
</ItemGroup>
این بسته، همان بسته‌ی جدید SpaServices است و در NET 5x. نیز پشتیبانی خواهد شد .

در متد ConfigureServices، ثبت سرویس‌های مرتبط با فایل‌های استاتیک پروژه‌ی SPA، توسط متد AddSpaStaticFiles صورت گرفته‌است. در اینجا RootPath آن، به پوشه‌ی ClientApp/build اشاره می‌کند. البته این پوشه هنوز در این ساختار، قابل مشاهده نیست؛ اما زمانیکه پروژه‌ی ASP.NET Core را برای ارائه‌ی نهایی، publish کردیم، به صورت خودکار ایجاد شده و حاوی فایل‌های قابل ارائه‌ی برنامه‌ی React نیز خواهد بود.

قسمت مهم دیگر کلاس آغازین برنامه، متد Configure آن است:
// ...
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;

namespace dotnet_template_sample
{
    public class Startup
    {
        // ...

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // ...
            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });
        }
    }
}
در اینجا ثبت سه میان افزار جدید را مشاهده می‌کنید:
- متد UseSpaStaticFiles، سبب ثبت میان‌افزاری می‌شود که امکان دسترسی به فایل‌های استاتیک پوشه‌ی ClientApp حاوی برنامه‌ی React را میسر می‌کند؛ مسیر این پوشه را در متد ConfigureServices تنظیم کردیم.
- متد UseSpa، سبب ثبت میان‌افزاری می‌شود که دو کار مهم را انجام می‌دهد:
1- کار اصلی آن، ثبت مسیریابی معروف catch all است تا مسیریابی‌هایی را که توسط کنترلرهای برنامه‌ی ASP.NET Core مدیریت نمی‌شوند، به سمت برنامه‌ی React هدایت کند. برای مثال مسیر https://localhost:5001/api/users به یک کنترلر API برنامه‌ی سمت سرور ختم می‌شود، اما سایر مسیرها مانند https://localhost:5001/login قرار است صفحه‌ی login برنامه‌ی سمت کلاینت SPA را نمایش دهند و متناظر با اکشن متد خاصی در کنترلرهای برنامه‌ی وب ما نیستند. در این حالت، کار این مسیریابی catch all، نمایش صفحه‌ی پیش‌فرض برنامه‌ی SPA است.
2- بررسی می‌کند که آیا شرایط IsDevelopment برقرار است؟ آیا در حال توسعه‌ی برنامه هستیم؟ اگر بله، میان‌افزار دیگری را به نام UseReactDevelopmentServer، اجرا و ثبت می‌کند.

برای درک عملکرد میان‌افزار ReactDevelopmentServer نیاز است به سورس آن مراجعه کرد. این میان‌افزار بر اساس پارامتر start ای که دریافت می‌کند، سبب اجرای npm run start خواهد شد. به این ترتیب دیگر نیازی به اجرای جداگانه‌ی این دستور نخواهد بود و همچنین این اجرا، به همراه تنظیمات proxy مخصوصی نیز هست تا پورت اجرایی برنامه‌ی React و برنامه‌ی ASP.NET Core یکی شده و دیگر نیازی به تنظیمات CORS مخصوص برنامه‌های React نباشد. بنابراین hot reloading ای که از آن صحبت شد، توسط ASP.NET Core مدیریت نمی‌شود. در پشت صحنه همان npm run start اصلی برنامه‌های React، در حال اجرای وب سرور آزمایشی React است که از hot reloading پشتیبانی می‌کند.

یک مشکل: با این تنظیم، هربار که برنامه‌ی ASP.NET Core اجرا می‌شود (به علت تغییرات در کدها و فایل‌های پروژه)، سبب اجرای مجدد و پشت صحنه‌ی react development server نیز خواهد شد که ... آغاز برنامه را در حالت توسعه، کند می‌کند. برای رفع این مشکل می‌توان این وب سرور توسعه‌ی برنامه‌های React را به صورت جداگانه‌ای اجرا کرد و فقط تنظیمات پروکسی آن‌را در اینجا ذکر نمود:
// replace
spa.UseReactDevelopmentServer(npmScript: "start");
// with
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
در اینجا فقط کافی است سطر UseReactDevelopmentServer را با تنظیم UseProxyToSpaDevelopmentServer که به آدرس وب سرور توسعه‌ی برنامه‌های React اشاره می‌کند، تنظیم کنیم. بدیهی است در اینجا حالت باید از طریق خط فرمان به پوشه‌ی clientApp وارد شد و دستور npm start را یکبار به صورت دستی اجرا کرد، تا این وب سرور، راه اندازی شود.


تغییرات ویژه‌ی فایل csproj برنامه

اگر به فایل csproj برنامه دقت کنیم، دو تغییر جدید نیز در آن قابل مشاهده هستند:
الف) نصب خودکار وابستگی‌های برنامه‌ی client
  <Target Name="DebugEnsureNodeEnv"
     BeforeTargets="Build" 
     Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
  </Target>
در این تنظیم، در حالت build و debug، ابتدا بررسی می‌کند که آیا پوشه‌ی node_modules برنامه‌ی SPA وجود دارد؟ اگر خیر، ابتدا مطمئن می‌شود که node.js بر روی سیستم نصب است و سپس دستور npm install را صادر می‌کند تا تمام وابستگی‌های برنامه‌ی client، دریافت و نصب شوند.

ب) یکی کردن تجربه‌ی publish برنامه‌ی ASP.NET Core با publish پروژه‌های React
  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)build\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>
میان‌افزار ReactDevelopmentServer کار اجرا و پروکسی دستور npm run start را در حالت توسعه انجام می‌دهد. اما در حالت ارائه‌ی نهایی چطور؟ در اینجا نیاز است دستور npm run build اجرا شده و فایل‌های مخصوص ارائه‌ی نهایی برنامه‌ی React تولید و سپس به پوشه‌ی wwwroot، کپی شوند. تنظیم فوق، دقیقا همین کار را در حین publish برنامه‌ی ASP.NET Core، به صورت خودکار انجام می‌دهد و شامل این مراحل است:
-  ابتدا npm install را جهت اطمینان از به روز بودن وابستگی‌های برنامه مجددا اجرا می‌کند.
- سپس npm run build را برای تولید فایل‌های قابل ارائه‌ی برنامه‌ی React اجرا می‌کند.
- در آخر تمام فایل‌های پوشه‌ی ClientApp/build تولیدی را به بسته‌ی نهایی توزیعی برنامه‌ی ASP.NET Core، اضافه می‌کند.
مطالب
آموزش فایرباگ - #5 - HTML Panel
در این سری از مقالات آموزش FireBug ، به صورت ترتیبی پیش رفتیم و ابتدا توضیحات تقریبا مفصلی در مورد پنل Console دادیم.
اکنون به پرکاربردترین بخش آن ، یعنی پنل HTML می‌رسیم.

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

نگاهی به کدهای ارسال شده به مرورگر در آدرس Google.com و نحوه‌ی نمایش آن در فایرباگ را ملاحظه بفرمایید.
( کدهای ارسال شده سمت چپ - نمایش در فایرباگ سمت راست )


این پنل به سه بخش اصلی تقسیم می‌شود :
  • بخش اصلی یا NodeView که محتوای صفحه را بصورت درختی و مرتب و رنگی نمایش می‌دهد.
  •  Panel Toolbar که در بالای پنل قرار دارد.
  •  Side Panels که شامل پنل‌های Style , Computed , Layout , DOM می‌شود.
    که به ترتیب برای نمایش و ویرایش استایل‌ها ، مشاهده استایل‌های محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView است.

در این مقاله با دو بخش NodeView و Panel Toolbar ، و در مقاله‌ی بعد با پنل‌های سمت راست یعنی Side Panels آشنا می‌شویم.

ویرایش HTML

هر تگ HTML از یک سری Attribute تشکیل می‌شود که در فایرباگ نام ویژگی بصورت آبی تیره و مقدار آن با رنگ قرمز مشخص شده است. برای ویرایش هریک از آن‌ها کافیست برویش کلیک کنید تا آن مقدار در یک باکس ویرایش به نمایش دربیاید.
با ویرایش مقدار ، این تغییر در لحظه بروی صفحه اعمال می‌شود.


برای اضافه کردن یک Attribute جدید به تگ هم بروی تگ مورد نظر راست کلیک می‌کنید و سپس گزینه‌ی New Attribute را انتخاب می‌کنید. ابتدا نام ویژگی ، یک Enter ، سپس مقدار را وارد می‌کنید. با زدن کلیدهای Enter متوالی ، می‌توانید به وارد کردن ویژگی‌ها ادامه دهید.
برای ویرایش کردن یک تگ و تگ‌های فرزندش ، بروی تگ موردنظر کلیک کنید تا به حالت انتخاب دربیاید ، سپس بروی دکمه‌ی Edit در بالای پنل کلیک کنید. در نهایت بعد از انجام ویرایش ، مجددا بروی دکمه‌ی Edit کلیک کنید.


 Node View
NodeView نام بخش اصلی پنل HTML است که محتویات صفحه را بصورت مرتب شده و درختی نمایش می‌دهد.
تگ (Node) هایی که دارای فرزند می‌باشند ، یک علامت + یا - در کنارشان وجود دارد که با کلیک بروی آن می‌توانید آن تگ را باز/بسته کنید. همچنین این کار با کلید‌های + و - یا Right Arrow و Left Arrow از روی کیبورد هم قابل انجام است.
برای باز کردن یک لینک در این قسمت هم از ترکیب Ctrl و کلیک موس کمک بگیرید.
در نهایت زمانی که موس را بروی یک تگ قرار می‌دهید ، محیطی که توسط آن تگ در صفحه اشغال شده است بصورت رنگی مشخص می‌شود.
که رنگ زرد به معنی محیط margin ، رنگ آبی تیره به معنی محیط padding و رنگ آبی روشن هم به معنی محیط محتوا است.

 Panel Toolbar
این قسمت در بالای پنل HTML قرار دارد که گزینه‌های زیر را دارا می‌باشد:



  •  Break On Mutate
    این دکمه امکان توقف کد JavaScript ای که سعی در ویرایش محتوای صفحه را دارد ، می‌دهد.
    زمانی که FireBug تشخیص دهد که کدی سعی در ویرایش محتوا دارد ، شما را به خط مورد نظر از کد ، در پنل Script منتقل می‌کند.

  • Edit
    این دکمه برای ویرایش مستقیم محتوای یک تگ بکار می‌رود
    نکته‌ی جالب در ویرایش محتوا در فایرباگ این است که تغییرات در لحظه اعمال می‌شوند و نیاز به عمل بروزرسانیِ جداگانه نیست. برای مثال در همین قسمت Edit ، با هر ویرایش محتوا ، تغییرات در لحظه اعمال می‌شوند.

  •  Element Path
    زمانی که یک تگ را در FireBug انتخاب می‌کنید ، لیستی از تگ‌ها که از تگ جاری شروع و به تگ ریشه ختم می‌شود ، نمایش داده می‌شود که با کلیک بروی هرکدام ، همان به عنوان تگ فعلی یا انتخاب شده تعیین می‌شود.
    نتیجه‌ی عملیاتی که بروی این تگ‌های انجام می‌دهید (حرکت موس و راست کلیک کردن) معادل همان عملیات بروی تگ‌های نمایش داده شده در قسمت اصلی (NodeView) است.



Options Menu

هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب HTML هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر می‌شود.


  • Show Full Text
    در صورت فعال بودن ، متون بصورت کامل نمایش داده می‌شوند ، در غیراینصورت بصورت خلاصه شده نمایش داده می‌شوند.

  • Show White Space
    در صورت فعال بودن ، فضاهای خالی ، کرکترهای خط جدید و ... را نمایش می‌دهد.


  • Show Comments
    در صورت فعال بودن ، کامنت‌ها را هم نمایش می‌دهد


  • سه گزینه ی Show Entities As Symbols ، Show Entities As Names و Show Entities As Unicode ، نوع نمایش کرکترهای ویژه را تعیین می‌کنند.

  • Highlight Changes
    در صورت فعال بودن ، تگ تغییر یافته توسط JavaScript (یا تگ والدی که قابل مشاهده باشد) Highlight می‌شود

  • Expand Changes
    در صورت فعال بودن ، زمان تغییر دادن یک تگ توسط JavaScript ، والدهای آن تگ باز (Expand) می‌شوند

  • Scroll Changes Into View
    در صورت فعال بودن این گزینه ، هنگام تغییر یک تگ در صفحه توسط JavaScript ، قسمت NodeView به قسمت تغییر بافته حرکت می‌کند.
    (فعال بودن این گزینه هنگام بررسی سایت هایی که اسلایدر یا سیستم هایی مشابه دارند ، باعث میشه که نتوانید بروی قسمت‌های سایت تمرکز کنید و مدام اسکرول به قسمت تغییرات منتقل می‌شود.)

  • Shade Box Model
    در صورت فعال بودن ، فضای margin , padding و content را به شکلی که در بالا گفته شد نمایش می‌دهد ، در غیر اینصورت فقط یک خط دور تگ نشان می‌دهد.

  • Show Quick Info Box
    در صورت فعال بودن ، یک پاپ‌آپ به همراه اطلاعات مختصری از تگ در صفحه نمایش می‌دهد.




Context Menu
اگر بروی یک تگ راست کلیک کنید ، یک منو نمایش داده می‌شود ، در این قسمت با گزینه‌های این منو آشنا می‌شویم.

  • Copy HTML
    خود تگ و محتوایش را بصورت HTML در حافظه کپی می‌کند.

  • Copy innerHTML
    محتوای تگ را در حافظه کپی می‌کند.

  • Copy XPath
    آدرس XPath تگ را در حافظه کپی می‌کند.

  • Copy CSS Path
    CSS Selector تگ را در حافظه کپی می‌کند.

  • Log Events
    رویدادهای تگ را در پنل Console ثبت می‌کند. (کلیک موس ، فشردن کلید ، ...)
    برای لغو لاگ کردن ، مجددا بروی تگ راست کلیک کرده و این گزینه را از حالت انتخاب خارج کنید.

  • Scroll Into View
    صفحه را به جایی که تگ قابل نمایش است منتقل می‌کند.

  • New Attribute...
    یک attribute جدید به تگ اضافه می‌کند.
    برای لغو عملیات ، دکمه‌ی Esc را بزنید.

  • Edit Attribute "<attribute name>"...
    اگر بروی یک attribute راست کلیک کرده باشید ، این گزینه و گزینه‌ی بعدی را مشاهده خواهید کرد.
    معادل کلیک بروی نام attribute است ، نام را به حالت ویرایش درمی آورد.

  • Delete Attribute "<attribute name>"
    attribute را حذف می‌کند.

  • Edit HTML...
    تگ را به حالت ویرایش می‌برد.
    معادل انتخاب تگ ، زدن کلید Edit است.

  • Delete Element
    تگ را حذف می‌کند.
    راه دیگر حذف یک تگ ، انتخاب تگ و فشردن کلید Del از کیبورد است.

  • Expand/Contract All
    تگ و Childهایش را باز/بسته می‌کند. (بجز تگ های <script> , <style> , <link>)
    می‌توان با ترکیب کلیدShift + * هم این کار را انجام داد که در این حالت تگ‌های فوق هم شامل باز/بسته شدن می‌شوند.

  • Break On Attribute Change
    مانع اجرای کد JavaScript ای که attribute تگ را تغییر می‌دهد می‌شود و فایرباگ به خطی که باعث این عمل شده است در پنل Script منتقل می‌شود.
    به عبارتی دیگر یک Break Point در خط JavaScript ای که باعث ویرایش attribute می‌شود قرار می‌دهد.

  • Break On Child Addition or Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث اضافه/حذف شدن تگِ Child شده است قرار می‌دهد.

  • Break On Element Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث حذف شدن تگ شده است قرار می‌دهد.

  • Inspect in DOM Tab
    تگ فعلی را در پنل DOM ، برای بررسی باز می‌کند.


در قسمت بعدی با پنل‌های سمت راست (Side Panels) آشنا می‌شویم.
مطالب
روش استفاده‌ی از jQuery در برنامه‌های AngularJS 2.0
استفاده‌ی گسترده‌ی از jQuery به همراه برنامه‌های AngularJS 2.0 ایده‌ی خوبی نیست؛ زیرا jQuery و کدهای آن، ارتباط تنگاتنگی را بین DOM و JavaScript برقرار می‌کنند که برخلاف رویه‌ی فریم‌ورک‌های MVC است. اما با این حال استفاده‌ی از افزونه‌های بی‌شمار jQuery در برنامه‌های AngularJS 2.0، جهت غنا بخشیدن به ظاهر و همچنین کاربردپذیری برنامه، یکی از استفاده‌های پذیرفته شده‌ی آن است.
روش استفاده‌ی از jQuery نیز در حالت کلی همانند مطلب «استفاده از کتابخانه‌های ثالث جاوا اسکریپتی در برنامه‌های AngularJS 2.0» است؛ اما به همراه چند نکته‌ی اضافه‌تر مانند محل فراخوانی و دسترسی به DOM، در کدهای یک کامپوننت.


هدف: استفاده از کتابخانه‌ی Chosen

می‌خواهیم جهت غنی‌تر کردن ظاهر یک دراپ داون در برنامه‌های AngularJS 2.0، از یک افزونه‌ی بسیار معروف jQuery به نام Chosen استفاده کنیم.


تامین پیشنیازهای اولیه

می‌توان فایل‌های این کتابخانه را مستقیما از GitHub دریافت و به پروژه اضافه کرد. اما بهتر است این‌کار را توسط bower مدیریت کنیم. این کتابخانه هنوز دارای بسته‌ی رسمی npm نیست (و بسته‌ی chosen-npm که در مخزن npm وجود دارد، توسط این تیم ایجاد نشده‌است). اما همانطور که در مستندات آن نیز آمده‌است، توسط دستور ذیل نصب می‌شود:
bower install chosen
برای مدیریت این مساله در ویژوال استودیو، به منوی project و گزینه‌ی add new item مراجعه کرده و bower را جستجو کنید:


در اینجا نام پیش فرض bower.json را پذیرفته و سپس محتوای فایل ایجاد شده را به نحو ذیل تغییر دهید:
{
    "name": "asp-net-mvc5x-angular2x",
    "version": "1.0.0",
    "authors": [
        "DNT"
    ],
    "license": "MIT",
    "ignore": [
        "node_modules",
        "bower_components"
    ],
    "dependencies": {
        "chosen": "1.4.2"
    },
    "devDependencies": {
    }
}
ساختار این فایل هم بسیار شبیه‌است به ساختار package.json مربوط به npm. در اینجا کتابخانه‌ی chosen در قسمت لیست وابستگی‌ها اضافه شده‌است. با ذخیره کردن این فایل، کار دریافت خودکار بسته‌های تعریف شده، آغاز می‌شود و یا کلیک راست بر روی فایل bower.json دارای گزینه‌ی restore packages نیز هست.
پس از دریافت خودکار chosen، بسته‌ی آن‌را در مسیر bower_components\chosen واقع در ریشه‌ی پروژه می‌توانید مشاهده کنید.


استفاده از jQuery و chosen به صورت untyped

ساده‌ترین و متداول‌ترین روش استفاده از jQuery و افزونه‌های آن شامل موارد زیر هستند:
الف) تعریف مداخل مرتبط با آن‌ها در فایل index.html
<script src="~/node_modules/jquery/dist/jquery.min.js"></script>
<script src="~/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>

<script src="~/bower_components/chosen/chosen.jquery.min.js"></script>
<link href="~/bower_components/chosen/chosen.min.css" rel="stylesheet" type="text/css" />
که در اینجا jQuery و بوت استرپ و chosen به همراه فایل css آن اضافه شده‌اند.
ب) تعریف jQuery به صورت untyped
declare var jQuery: any;
همینقدر که این یک سطر را به ابتدای ماژول خود اضافه کنید، امکان دسترسی به تمام امکانات جی‌کوئری را خواهید یافت. البته بدون intellisense و بررسی نوع‌های پارامترها؛ چون نوع آن، any یا همان حالت پیش فرض جاوا اسکریپت تعریف شده‌است.


محل صحیح فراخوانی متدهای مرتبط با jQuery

در تصویر ذیل، چرخه‌ی حیات یک کامپوننت را مشاهده می‌کنید که با تعدادی از آن‌ها پیشتر آشنا شده‌‌ایم:

روش متداول استفاده از jQuery، فراخوانی آن پس از رخداد document ready است. در اینجا معادل این رخداد، hook ویژ‌ه‌ای به نام ngAfterViewInit است. بنابراین تمام فراخوانی‌های jQuery را باید در این متد انجام داد.
همچنین جی‌کوئری نیاز دارد تا بداند هم اکنون قرار است با چه المانی کار کنیم و کامپوننت بارگذاری شده کدام است. برای این منظور، یکی از سرویس‌های توکار AngularJS 2.0 را به نام ElementRef، به سازنده‌ی کلاس تزریق می‌کنیم. توسط خاصیت this._el.nativeElement آن می‌توان به المان ریشه‌ی کامپوننت جاری دسترسی یافت.
constructor(private _el: ElementRef) {
}


تهیه‌ی کامپوننت نمایش یک دراپ داون مزین شده با chosen

در ادامه قصد داریم نکاتی را که تاکنون مرور کردیم، به صورت یک مثال پیاده سازی کنیم. به همین جهت فایل جدید using-jquery-addons.component.ts را به پروژه اضافه کنید به همراه فایل قالب آن به نام using-jquery-addons.component.html؛ با این محتوا:
الف) کامپوننت using-jquery-addons.component.ts
import { Component, ElementRef, AfterViewInit } from "@angular/core";

declare var jQuery: any; // untyped

@Component({
    templateUrl: "app/using-jquery-addons/using-jquery-addons.component.html"
})
export class UsingJQueryAddonsComponent implements AfterViewInit {

    dropDownItems = ["First", "Second", "Third"];
    selectedValue = "Second";

    constructor(private _el: ElementRef) {
    }

    ngAfterViewInit() {
        jQuery(this._el.nativeElement)
            .find("select")
            .chosen()
            .on("change", (e, args) => {
                this.selectedValue = args.selected;
            });
    }
}

ب) قالب using-jquery-addons.component.html
<select>
    <option *ngFor="let item of dropDownItems"
            [value]="item"
            [selected]="item == selectedValue">
        {{item}} option
    </option>
</select>

<h4> {{selectedValue}}</h4>
با این خروجی


توضیحات

کلاس UsingJQueryAddonsComponent، اینترفیس AfterViewInit را پیاده سازی کرده‌است؛ تا توسط متد ngAfterViewInit بتوانیم با عناصر DOM کار کنیم. هرچند در کل اینکار باید صرفا محدود شود به مواردی مانند مثال جاری و در حد آغاز یک افزونه‌ی jQuery و اگر قرار است تغییراتی دیگری صورت گیرند بهتر است از همان روش binding توکار AngularJS 2.0 استفاده کرد.
در سازنده‌ی کلاس، سرویس ElementRef تزریق شده‌است تا توسط خاصیت this._el.nativeElement آن بتوان به المان ریشه‌ی کامپوننت جاری دسترسی یافت. به همین جهت است که پس از آن از متد find، برای یافتن دراپ داون استفاده شده‌است و سپس chosen به نحو متداولی به آن اعمال گردیده‌است.
در اینجا هر زمانیکه یکی از آیتم‌های دراپ داون انتخاب شوند، مقدار آن به خاصیت selectedValue انتساب داده شده و این انتساب سبب فعال سازی binding و نمایش مقدار آن در ذیل دراپ داون می‌گردد.
در قالب این کامپوننت هم با استفاده از ngFor، عناصر دراپ داون از آرایه‌ی dropDownItems تعریف شده در کلاس کامپوننت، تامین می‌شوند. متغیر محلی item تعریف شده‌ی در اینجا، در محدودی همین المان قابل دسترسی است. برای مثال از آن جهت تنظیم دومین آیتم لیست به صورت انتخاب شده، در حین اولین بار نمایش view استفاده شده‌است.


استفاده از jQuery و chosen به صورت typed

کتابخانه‌ی jQuery در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/jquery دارای فایل d.ts. خاص خود است. برای نصب آن می‌توان از روش ذیل استفاده کرد:
npm install -g typings
typings install jquery --save --ambient
پس از افزودن فایل typings این کتابخانه، در ابتدای فایل main.ts یک سطر ذیل را اضافه کنید:
 /// <reference path="../typings/browser/ambient/jquery/index.d.ts" />
اینکار سبب خواهد شد تا intellisense درون ویژوال استودیو بتواند مداخل متناظر را یافته و راهنمای مناسبی را ارائه دهد.

در ادامه به فایل using-jquery-addons.component.ts مراجعه کرده و تغییر ذیل را اعمال کنید:
// declare var jQuery: any; // untyped
declare var jQuery: JQueryStatic; // typed
فایل d.ts. اضافه شده، امکان استفاده‌ی از نوع جدید JQueryStatic را مهیا می‌کند. پس از آن در کدهای تعریف شده، chosen به رنگ قرمز در می‌آید، چون در تعاریف نوع دار jQuery، این افزونه وجود خارجی ندارد. برای رفع این مشکل، فایل typings/browser/ambient/jquery/index.d.ts را گشوده و سپس به انتهای آن، تعریف chosen را به صورت دستی اضافه کنید:
interface JQuery {
   //...
   chosen(options?:any):JQuery;
}
declare module "jquery" {
   export = $;
}
به این ترتیب این متد به عنوان جزئی از متدهای زنجیروار jQuery، شناسایی شده و قابل استفاده خواهد شد.


کدهای کامل این پروژه را از اینجا می‌توانید دریافت کنید.