پاسخ به پرسش‌ها
ساخت یک دیتابیس ترکیبی از SQL و فایل های XML
  • آیا سرعت ثبت و به‌روز رسانی اطلاعات، در حالت متداول کار با بانک‌های اطلاعاتی رابطه‌ای بیشتر است از کار با فیلدهای XML؟ بله. چون حداقل یک قسمت serialize و deserialize فیلدهای XML ای را به همراه ندارند؛ به همراه امکان ایندکس‌گذاری، کوئری‌های سریع و تمام مزایای دیگر به همراه این نوع بانک‌های اطلاعاتی که تمام آن‌ها در مورد فیلدهای NoSQL آن‌ها صادق نیست.
  • آیا حجم بانک اطلاعاتی که به صورت متداولی، رابطه‌ای است، کمتر است از نمونه‌ی مشابه NoSQL آن؟ بله. هدف ابتدایی طراحی این نوع بانک‌های اطلاعاتی رابطه‌ای دقیقا همین مورد بوده تا بتوانند اطلاعات بیشتری را در حجم کمتری ذخیره کنند و هزینه‌های به شدت بالای سخت‌افزاری آن زمان را کاهش دهند.
  • برای سیستم‌های بزرگ و داده‌های قابل توجه، شما به مباحثی مانند مقیاس‌پذیری و یا حتی روش‌های دیگری برای کار نیاز خواهید داشت.
نظرات اشتراک‌ها
SQLite و EF6 و Code First
این روش قابل اجرا نیست من تست کردم، ولی وقتی به صورت جدا یعنی SQLite رو به صورت یک dll به پروژه اضافه میکنم و بعد EF رو هم نصب میکنم، برنامه به خوبی کار میکنه. ظاهراً در این حالت پروایدرش برای EF مشکلی داره.
مطالب
NHibernate و سطح اول cache آن

این روزها هیچکدام از فناوری‌های دسترسی به داده بدون امکان یکپارچگی آن‌ها با سیستم‌ها و روش‌های متفاوت caching ، مطلوب شمرده نمی‌شوند. ایده اصلی caching هم به زبان ساده به این صورت است :‌ فراهم آوردن روش‌هایی جهت میسر ساختن دسترسی سریعتر به داده‌هایی که به صورت متناوب در برنامه مورد استفاده قرار می‌گیرند، بجای مراجعه مستقیم به بانک اطلاعاتی و خواندن اطلاعات از دیسک سخت.
یکی از تفاوت‌های مهم NHibernate با اکثر ORM های موجود داشتن دو سطح متفاوت cache است : first level cache & second level cache .
برای نمونه Entity framework (در زمان نگارش این مطلب) تنها first level caching را پشتیبانی می‌کند و پروایدر توکار و یکپارچه‌ای را جهت second level caching ارائه نمی‌دهد.
در این قسمت قصد داریم First Level Cache را بررسی کنیم.

سطح اول caching در NHibernate چیست؟

سطح اول caching در تمام ORM هایی که آن‌را پشتیبانی می‌کنند مانند NHibernate ، در طول عمر یک تراکنش تعریف می‌گردد. در این حالت در طی یک تراکنش و طول عمر یک سشن، دریافت اطلاعات هر رکورد از بانک اطلاعاتی، تنها یکبار انجام خواهد شد؛ صرفنظر از اینکه کوئری دریافت اطلاعات آن چندبار فراخوانی می‌‌گردد. یکی از دلایل این روش هم آن است که هیچ دو شیء متفاوتی که هم اکنون در حافظه قرار دارند نباید بیانگر یک رکورد واحد از بانک اطلاعاتی باشند.
در NHibernate به صورت پیش فرض هر زمانیکه از شیء استاندارد session استفاده می‌کنید، سطح اول caching نیز فعال است. درست در زمانیکه سشن خاتمه می‌یابد، این سطح از caching نیز به صورت خودکار تخلیه خواهد گردید.
به first level caching اصطلاحا thought-out cache system یا Cache Through pattern و یا identity map هم گفته می‌شود.

مثال:

روش متداول و استاندارد کار با NHibernate عموما به صورت زیر است:

الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد.
د) سپس مجددا سعی در دریافت رکوردی با ID مساوی یک. اینبار اطلاعات این شیء مستقیما از cache خوانده می‌شود و رفت و برگشتی به بانک اطلاعاتی نخواهیم داشت. به همین جهت به این روش identity map هم گفته می‌شود، زیرا NHibernate بر اساس ID منحصربفرد این اشیاء ، identity map خود را تشکیل می‌دهد.
ه) خاتمه‌ی سشن با فراخوانی متد Close آن
بلافاصله
الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد (زیرا در یک سشن جدید قرار داریم و همچنین سشن قبلی بسته شده و کش آن تخلیه گشته است).
د) خاتمه‌ی سشن با فراخوانی متد Close آن


سؤال: آیا استفاده از یک سشن سراسری در برنامه صحیح است؟
پاسخ: خیر!
توضیحات: زمانیکه از یک سشن سراسری استفاده می‌کنید، کش NHibernate را در اختیار تمام کاربران همزمان سیستم قرار داده‌اید. در طی یک سشن، همانطور که عنوان شد، بر اساس IDهای اشیاء، یک identity map تشکیل می‌شود و در این حالت به ازای هر رکورد بانک اطلاعاتی فقط و فقط یک شیء در حافظه وجود خواهد داشت که این روش در محیط‌های چندکاربره مانند برنامه‌های وب به زودی تبدیل به نشت اطلاعات و یا تخریب اطلاعات می‌گردد. به همین جهت در این نوع برنامه‌ها روش session-per-request بهترین حالت کاری است.

سؤال: حین به روز رسانی اشیاء جدید، به خطا بر می‌خورم. مشکل در کجاست؟
فرض کنید شیء مفروض Customer را توسط متد session.Get از بانک اطلاعاتی دریافت و تعدادی از خواص آن‌را جهت ساخت شیء جدیدی از کلاس Customer استفاده کرده‌ایم. اکنون اگر بخواهیم این شیء جدید را در بانک اطلاعاتی ذخیره یا به روز رسانی کنیم، NHibernate این اجازه را نمی‌دهد! چرا؟
پاسخ:
خطای متداول این حالت عموما به صورت زیر است:
a different object with the same identifier value was already associated with the session
اگر شخصی با مکانیزم سطح اول caching در NHibernate آشنایی نداشته باشد، شاید ساعاتی را در انجمن‌های مرتبط، جهت یافتن روش حل خطای فوق سپری کند.
همانطور که عنوان شد، در طول یک سشن، نمی‌توان دو شیء با یک ID را به عنوان یک رکورد بانک اطلاعاتی مورد استفاده قرار داد. اولین فراخوانی Get ، سبب کش شدن آن شیء در identity map سطح اول caching می‌گردد.
راه حل:
الف) از چندین و چند شیء استفاده نکنید. هر رکورد باید تنها با یک وهله از شیء‌ایی متناظر باشد.
ب) می‌توان پیش از update‌، کش سطح اول را به صورت دستی خالی کرد. برای این منظور از متد Clear شیء سشن استفاده کنید.
ج) بجای استفاده از متد saveOrUpdate شیء سشن، از متد Merge آن استفاده کنید. به این صورت شیء جدید ایجاد شده با شیء موجود در کش یکی خواهد شد.
د) می‌توان بجای تخلیه کل کش (حالت ب)، کش مرتبط با شیء Customer را به صورت دستی خالی کرد. برای این منظور از متد Evict شیء سشن استفاده نمائید.

و لازم به ذکر است که متد Flush سبب تخلیه کش نمی‌گردد. کار این متد اعمال کلیه تغییرات اعمالی موجود در کش به بانک اطلاعاتی است و بیشتر جهت هماهنگ سازی این دو مورد استفاده قرار می‌گیرد.

سؤال: آیا می‌توان سطح اول caching را غیرفعال کرد؟
پاسخ:بله.
توضیحات:
عموما کلیه ORMs جهت Batching یا Bulk data operations (برای مثال ثبت تعداد زیادی رکورد یا به روز رسانی تعداد بالایی از آن‌ها، یا نمایش فقط خواندنی تعداد زیادی رکورد و گزارشگیری از آن‌ها) کارآیی مطلوبی ندارند. نمونه‌ای از آن‌را در مبحث جاری ملاحظه کرده‌اید. هر شیءایی که به نحوی به سشن جاری وارد می‌شود تحت نظر قرار می‌گیرد و این مورد در تعداد بالای ثبت یا به روز رسانی رکوردها، یعنی کاهش سرعت و کارآیی، به علاوه مصرف بالای حافظه. به همین جهت باید به خاطر داشت که ORMs جهت سناریوهای OLTP مناسب هستند و کسانی که سرعت و کارآیی ORMs را با Batch processing اندازه گیری می‌کنند، کلا درکی از فلسفه‌ی وجودی ORMs و ساختار درونی آن‌ها ندارند!
خوشبختانه NHibernate با معرفی Stateless Sessions بر این مشکل فائق آمده است. در اینجا بجای ISession تنها کافی است از IStatelessSession استفاده گردد:
using (IStatelessSession statelessSession = sessionFactory.OpenStatelessSession())
using (ITransaction transaction = statelessSession.BeginTransaction())
{
//now insert 1,000,000 records!
}
در این حالت سیستم دو مزیت عمده را تجربه خواهد کرد: سرعت بالای ثبت اطلاعات با تعداد زیاد رکورد و همچنین مصرف پایین حافظه از آنجائیکه یک IStatelessSession ارجاعی را به اشیایی که بارگذاری می‌کند، در خود نگهداری نخواهد کرد.
تنها باید به خاطر داشت که در این حالت lazy loading پشتیبانی نمی‌شود و همچنین رخدادهای درونی NHibernate نیز لغو خواهند شد.

نظرات مطالب
تبدیل html به pdf با کیفیت بالا
بله. platform target رو باید روی X86 قرار بدید تا همه جا بدون مشکل اجرا بشه.
یا اینکه نسخه 64 بیتی اون رو هم پیدا یا کامپایل کنید و نهایتا مانند خیلی از کارهای native باید دو نسخه 64 بیتی و 32 بیتی از برنامه خودتون رو منتشر کنید.
نظرات مطالب
چند نکته کوتاه و عمومی در مورد قبل و بعد از نصب ویندوز 7
سلام
نه. بدون مشکل نصب می‌شود و از آن دارم استفاده می‌کنم. فقط برای ویندوز 64 بیتی حتما نسخه‌ی 64 بیتی را لازم دارد و نسخه‌ی 32 بیتی آن نصب نمی‌شود:
http://www.microsoft.com/downloads/details.aspx?FamilyId=28C97D22-6EB8-4A09-A7F7-F6C7A1F000B5&displaylang=en
مطالب
نحوه‌ی استفاده از کتابخانه‌ی 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 ورودی است.

نظرات مطالب
استفاده از اسمبلی‌های دات نت 2 در یک پروژه دات نت 4
جهت تکمیل بحث در مورد mixed-mode assemblies به این آدرس مراجعه کنید : (+)
به عبارتی اسمبلی‌هایی هستند که حاوی کدهای managed و unmanaged می‌باشند مانند اسمبلی ساخته شده برای SQLite که هم کدهای دات نتی دارند و هم کدهای اصل مرتبط با خود SQLite که با زبان C نوشته شده.
مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
با به پایان رسیدن مرحله‌ی توسعه‌ی ASP.NET Identity 2.x مخصوص نگارش‌های ASP.NETایی که از Full .NET Framework استفاده می‌کنند، نگارش جدید آن صرفا بر پایه‌ی ASP.NET Core تهیه شده‌است و در طی یک سری، نحوه‌ی سفارشی سازی تقریبا تمام اجزای آن‌را بررسی خواهیم کرد. جهت سهولت پیگیری این سری، پروژه‌ی کامل سفارشی سازی شده‌ی ASP.NET Core Identity را از مخزن کد DNT Identity می‌توانید دریافت کنید.


پیشنیازهای اجرای پروژه‌ی DNT Identity

 - ابتدا نیاز است حداقل ASP.NET Core Identity 1.1 را نصب کرده باشید.
 - همچنین بانک اطلاعاتی پایه‌ی آن که به صورت خودکار در اولین بار اجرای برنامه تشکیل می‌شود، مبتنی بر LocalDB است. بنابراین اگر قصد تغییری را در تنظیمات Context آن ندارید، بهتر است LocalDB را نیز بر روی سیستم نصب کنید. هرچند با تغییر تنظیم ActiveDatabase به SqlServer در فایل appsettings.json، برنامه به صورت خودکار از نگارش کامل SqlServer استفاده خواهد کرد. رشته‌ی اتصالی آن نیز در مدخل ConnectionStrings فایل appsettings.json ذکر شده‌است و قابل تغییر است. برای شروع به کار، نیازی به اجرای مراحل Migrations را نیز ندارید و همینقدر که برنامه را اجرا کنید، بانک اطلاعاتی آن نیز تشکیل خواهد شد.
 - کاربر پیش فرض Admin سیستم و کلمه‌ی عبور آن از مدخل AdminUserSeed فایل appsettings.json خوانده می‌شوند.
 - تنظیمات ایمیل پیش فرض برنامه به استفاده‌ی از PickupFolder در مدخل Smtp فایل appsettings.json تنظیم شده‌است. بنابراین تمام ایمیل‌های برنامه را جهت آزمایش محلی می‌توانید در مسیر PickupFolder آن یا همان c:\smtppickup مشاهده کنید. محتوای این ایمیل‌ها را نیز توسط مرورگر (drag&drop بر روی یک tab جدید) و یا برنامه‌ی Outlook می‌توان مشاهده کرد.


سفارشی سازی کلید اصلی موجودیت‌های ASP.NET Core Identity

ASP.NET Core Identity به همراه دو سری موجودیت است. یک سری ساده‌ی آن که از یک string به عنوان نوع کلید اصلی استفاده می‌کنند و سری دوم، حالت جنریک که در آن می‌توان نوع کلید اصلی را به صورت صریحی قید کرد و تغییر داد. در اینجا نیز قصد داریم از حالت جنریک استفاده کرده و نوع کلید اصلی جداول را تغییر دهیم. تمام این موجودیت‌های تغییر یافته را در پوشه‌ی src\ASPNETCoreIdentitySample.Entities\Identity نیز می‌توانید مشاهده کنید و شامل موارد ذیل هستند:

جدول نقش‌های سیستم
    public class Role : IdentityRole<int, UserRole, RoleClaim>, IAuditableEntity
    {
        public Role()
        {
        }

        public Role(string name)
            : this()
        {
            Name = name;
        }

        public Role(string name, string description)
            : this(name)
        {
            Description = description;
        }

        public string Description { get; set; }
    }
کار با ارث بری از نگارش جنریک کلاس IdentityRole شروع می‌شود. این کلاس پایه، حاوی تعاریف اصلی فیلدهای جدول نقش‌های سیستم است که اولین آرگومان جنریک آن، نوع کلید اصلی جدول مرتبط را نیز مشخص می‌کند و در اینجا به int تنظیم شده‌است. همچنین یک اینترفیس جدید IAuditableEntity را نیز در انتهای این تعریف‌ها مشاهده می‌کنید. در مورد این اینترفیس و Shadow properties متناظر با آن، در ادامه‌ی بحث با سفارشی سازی DbContext برنامه بیشتر توضیح داده خواهد شد.


در اولین بار اجرای برنامه، نقش Admin در این جدول ثبت خواهد شد.

جدول کاربران منتسب به نقش‌ها
    public class UserRole : IdentityUserRole<int>, IAuditableEntity
    {
        public virtual User User { get; set; }

        public virtual Role Role { get; set; }
    }
کلاس پایه‌ی جدول کاربران منتسب به نقش‌ها، کلاس جنریک IdentityUserRole است که در اینجا با تغییر آرگومان جنریک آن به int، نوع فیلدهای UserId و RoleId آن به int تنظیم می‌شوند. در کلاس سفارشی سازی شده‌ی فوق، دو خاصیت اضافه‌تر User و Role نیز را مشاهده می‌کنید. مزیت تعریف آن‌ها، دسترسی ساده‌تر به اطلاعات کاربران و نقش‌ها توسط EF Core است.


در اولین بار اجرای برنامه، کاربر شماره 1 یا همان Admin به نقش شماره 1 یا همان Admin، انتساب داده می‌شود.


جدول جدید  IdentityRoleClaim سیستم
public class RoleClaim : IdentityRoleClaim<int>, IAuditableEntity
{
   public virtual Role Role { get; set; }
}
در ASP.NET Core Identity، جدول جدیدی به نام RoleClaim نیز اضافه شده‌است. در این سری از آن برای پیاده سازی سطوح دسترسی پویای به صفحات استفاده خواهیم کرد. ابتدا یک سری نقش ثابت در جدول Roles ثبت خواهند شد. سپس تعدادی کاربر به هر نقش نسبت داده می‌شوند. اکنون می‌توان به هر نقش نیز تعدادی Claim را انتساب داد. برای مثال یک Claim سفارشی که شامل ID سفارشی area:controller:action باشد. به این ترتیب و با بررسی سفارشی آن می‌توان سطوح دسترسی پویا را نیز پیاده سازی کرد و مزیت آن این است که تمام این Claims به صورت خودکار به کوکی شخص نیز اضافه شده و دسترسی به اطلاعات آن بسیار سریع است و نیازی به مراجعه‌ی به بانک اطلاعاتی را ندارد.

جدول UserClaim سیستم
public class UserClaim : IdentityUserClaim<int>, IAuditableEntity
{
   public virtual User User { get; set; }
}
می‌توان به هر کاربر یک سری Claim مخصوص را نیز انتساب داد. برای مثال مسیر عکس ذخیره شده‌ی او در سرور، چه موردی است و این اطلاعات به صورت خودکار به کوکی او نیز توسط ASP.NET Core Identity اضافه می‌شوند. البته ما در این سری روش دیگری را برای سفارشی سازی Claims عمومی کاربران بکار خواهیم گرفت (با سفارشی سازی کلاس ApplicationClaimsPrincipalFactory آن).

جداول توکن و لاگین‌های کاربران
public class UserToken : IdentityUserToken<int>, IAuditableEntity
{
   public virtual User User { get; set; }
}

public class UserLogin : IdentityUserLogin<int>, IAuditableEntity
{
   public virtual User User { get; set; }
}
دراینجا نیز نحوه‌ی سفارشی سازی و تغییر جداول لاگین‌های کاربران و توکن‌های مرتبط با آن‌ها را مشاهده می‌کنید. این جداول بیشتر جهت دسترسی به حالت‌هایی مانند لاگین با حساب کاربری جی‌میل مورد استفاده قرار می‌گیرند و کاربرد پیش فرضی ندارند (اگر از تامین کننده‌های لاگین خارجی نمی‌خواهید استفاده کنید).

جدول کاربران سیستم
    public class User : IdentityUser<int, UserClaim, UserRole, UserLogin>, IAuditableEntity
    {
        public User()
        {
            UserUsedPasswords = new HashSet<UserUsedPassword>();
            UserTokens = new HashSet<UserToken>();
        }

        [StringLength(450)]
        public string FirstName { get; set; }

        [StringLength(450)]
        public string LastName { get; set; }

        [NotMapped]
        public string DisplayName
        {
            get
            {
                var displayName = $"{FirstName} {LastName}";
                return string.IsNullOrWhiteSpace(displayName) ? UserName : displayName;
            }
        }

        [StringLength(450)]
        public string PhotoFileName { get; set; }

        public DateTimeOffset? BirthDate { get; set; }

        public DateTimeOffset? CreatedDateTime { get; set; }

        public DateTimeOffset? LastVisitDateTime { get; set; }

        public bool IsEmailPublic { get; set; }

        public string Location { set; get; }

        public bool IsActive { get; set; } = true;

        public virtual ICollection<UserUsedPassword> UserUsedPasswords { get; set; }

        public virtual ICollection<UserToken> UserTokens { get; set; }
    }

    public class UserUsedPassword : IAuditableEntity
    {
        public int Id { get; set; }

        public string HashedPassword { get; set; }

        public virtual User User { get; set; }
        public int UserId { get; set; }
    }
در اینجا علاوه بر نحوه‌ی تغییر نوع کلید اصلی جدول کاربران سیستم، نحوه‌ی افزودن خواص اضافه‌تری مانند نام، تاریخ تولد، مکان، تصویر و غیره را نیز مشاهده می‌کنید. به علاوه جدولی نیز جهت ثبت سابقه‌ی کلمات عبور هش شده‌ی کاربران نیز تدارک دیده شده‌است تا کاربران نتوانند از 5 کلمه‌ی عبور اخیر خود (تنظیم NotAllowedPreviouslyUsedPasswords در فایل appsettings.json) استفاده کنند.
فیلد IsActive نیز از این جهت اضافه شده‌است تا بجای حذف فیزیکی یک کاربر، بتوان اکانت او را غیرفعال کرد.


تعریف Shadow properties ثبت تغییرات رکوردها

در #C ارث‌بری چندگانه‌ی کلاس‌ها ممنوع است؛ مگر اینکه از اینترفیس‌ها استفاده شود. برای مثال IdentityUser یک کلاس است و در اینجا دیگر نمی‌توان کلاس دومی را به نام BaseEntity جهت اعمال خواص اضافه‌تری اعمال کرد. به همین جهت است که اعمال اینترفیس خالی IAuditableEntity را در اینجا مشاهده می‌کنید. این اینترفیس کار علامت‌گذاری کلاس‌هایی را انجام می‌دهد که قصد داریم به آن‌ها به صورت خودکار، خواصی مانند تاریخ ثبت رکورد، تاریخ ویرایش آن و غیره را اعمال کنیم.
در Context برنامه، به اطلاعات src\ASPNETCoreIdentitySample.Entities\AuditableEntity مراجعه شده و متد AddAuditableShadowProperties بر روی تمام کلاس‌هایی از نوع IAuditableEntity اعمال می‌شود. این متد خواص مدنظر ما را مانند ModifiedDateTime به صورت Shadow properties به موجودیت‌های علامت‌گذاری شده اضافه می‌کند.
همچنین متد SetAuditableEntityPropertyValues، کار مقدار دهی خودکار این خواص را انجام خواهد داد. بنابراین دیگر نیازی نیست در برنامه برای مثال IP شخص ثبت کننده یا ویرایش کننده را به صورت دستی مقدار دهی کرد. هم تعریف و هم مقدار دهی آن توسط Change tracker سیستم به صورت خودکار انجام خواهند شد.


تاثیر افزودن Shadow properties را بر روی کلاس نقش‌های سیستم، در تصویر فوق ملاحظه می‌کنید. خواصی که به صورت معمول در کلاس‌های برنامه ظاهر نمی‌شوند و صرفا هدف بازبینی سیستم را برآورده می‌کنند و مدیریت آن‌ها نیز در اینجا کاملا خودکار است.


سفارشی سازی DbContext برنامه

نحوه‌ی سفارشی سازی DbContext برنامه را در پوشه‌ی src\ASPNETCoreIdentitySample.DataLayer\Context و src\ASPNETCoreIdentitySample.DataLayer\Mappings ملاحظه می‌کنید. پوشه‌ی Context حاوی کلاس ApplicationDbContextBase است که تمام سفارشی سازی‌های لازم بر روی آن انجام شده‌است؛ شامل:
 - تغییر نوع کلید اصلی موجودیت‌ها به همراه معرفی موجودیت‌های تغییر یافته:
 public abstract class ApplicationDbContextBase :
  IdentityDbContext<User, Role, int, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>,
  IUnitOfWork
ما در ابتدای بحث، برای مثال کلاس Role را سفارشی سازی کردیم. اما برنامه از وجود آن بی‌اطلاع است. با ارث بری از IdentityDbContext و ذکر این کلاس‌های سفارشی به همراه نوع int کلید اصلی مورد استفاده، کار معرفی موجودیت‌های سفارشی سازی شده انجام می‌شود.

 - اعمال متد BeforeSaveTriggers به تمام نگارش‌های مختلف SaveChanges
protected void BeforeSaveTriggers()
{
  ValidateEntities();
  SetShadowProperties();
  this.ApplyCorrectYeKe();
}
در اینجا پیش از ذخیره‌ی اطلاعات، ابتدا موجودیت‌ها اعتبارسنجی می‌شوند. سپس مقادیر Shadow properties تنظیم شده و دست آخر، ی و ک فارسی نیز به اطلاعات ثبت شده، جهت یک دست سازی اطلاعات سیستم، اعمال می‌شوند.

- انتخاب نوع بانک اطلاعاتی مورد استفاده در متد OnConfiguring
در اینجا است که خاصیت ActiveDatabase تنظیم شده‌ی در فایل appsettings.json خوانده شده و اعمال می‌شوند. تعریف متد GetDbConnectionString را در کلاس SiteSettingsExtesnsions مشاهده می‌کنید. کار آن استفاده‌ی از بانک اطلاعاتی درون حافظه‌ای، جهت انجام آزمون‌های واحد و یا استفاده‌ی از LocalDb و یا نگارش کامل SQL Server می‌باشد. اگر علاقمند بودید تا بانک اطلاعاتی دیگری (مثلا SQLite) را نیز اضافه کنید، ابتدا enum ایی به نام ActiveDatabase را تغییر داده و سپس متد GetDbConnectionString و متد OnConfiguring را جهت درج اطلاعات این بانک اطلاعاتی جدید، اصلاح کنید.

پس از تعریف این DbContext پایه‌ی سفارشی سازی شده، کلاس جدید ApplicationDbContext را مشاهده می‌کنید. این کلاس ‍Context ایی است که در برنامه از آن استفاده می‌شود و از کلاس پایه ApplicationDbContextBase مشتق شده‌است:
 public class ApplicationDbContext : ApplicationDbContextBase
تعاریف موجودیت‌های جدید خود را به این کلاس اضافه کنید.
تنظیمات mapping آن‌ها نیز به متد OnModelCreating این کلاس اضافه خواهند شد. فقط نحوه‌ی استفاده‌ی از آن را به‌خاطر داشته باشید:
        protected override void OnModelCreating(ModelBuilder builder)
        {
            // it should be placed here, otherwise it will rewrite the following settings!
            base.OnModelCreating(builder);

            // Adds all of the ASP.NET Core Identity related mappings at once.
            builder.AddCustomIdentityMappings(SiteSettings.Value);

            // Custom application mappings


            // This should be placed here, at the end.
            builder.AddAuditableShadowProperties();
        }
ابتدا باید base.OnModelCreating را ذکر کنید. در غیراینصورت تمام سفارشی سازی‌های شما بازنویسی می‌شوند.
سپس متد AddCustomIdentityMappings ذکر شده‌است. این متد اطلاعات src\ASPNETCoreIdentitySample.DataLayer\Mappings را به صورت خودکار و یکجا اضافه می‌کند که در آن برای مثال نام جداول پیش فرض Identity سفارشی سازی شده‌اند.


در آخر باید AddAuditableShadowProperties فراخوانی شود تا خواص سایه‌ای که پیشتر در مورد آن‌ها بحث شد، به سیستم به صورت خودکار اضافه شوند.
تمام نگاشت‌های سفارشی شما باید در این میان و در قسمت «Custom application mappings» درج شوند.

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


کدهای کامل این سری را در مخزن کد DNT Identity می‌توانید ملاحظه کنید.
مطالب
OpenCVSharp #1
معرفی OpenCV

پردازش تصاویر علمی است برای پیاده سازی الگوریتم‌های مختلفی بر روی تصاویر دیجیتال؛ برای مثال تشخیص خودکار شماره‌ی پلاک خودروهای وارد شده‌ی به محدوده‌ی طرح ترافیک، تا تشخیص چهره‌ی افراد، در گوشی‌های همراه. پردازش تصاویر، در صنایع مختلف، علوم پزشکی و همچنین نظامی، کاربردهای بسیاری دارند.
برای انجام این کار، کتابخانه‌های بسیار زیادی طراحی شده‌اند؛ اما در این بین OpenCV جایگاه خاصی دارد. این کتابخانه‌ی بسیار مشهور سورس باز، جهت پردازش تصاویر در سیستم عامل‌های مختلفی مانند Windows, Mac, Linux, Android و iOS بکار می‌رود.


محصور کننده‌های OpenCV مخصوص دات نت

تا امروز محصور کننده‌های زیادی جهت استفاده‌ی از کتابخانه‌ی OpenCV در دات نت طراحی شده‌اند که تعدادی از مهم‌ترین‌های آن‌ها به شرح زیر هستند:

الف) Emgu CV
این کتابخانه، یکی از مشهورترین محصور کننده‌های OpenCV است و دارای مجوزی دوگانه می‌باشد. برای کارهای سورس باز، مجوز GPL دارد (یعنی باید کارتان را سورس باز کنید) و برای کارهای تجاری باید مجوز آن‌را بخرید. البته باید توجه داشت که مجوز کتابخانه‌ی اصلی OpenCV از نوع BSD است و این محدودیت‌ها را ندارد.

ب) OpenCvSharp
کتابخانه‌ی OpenCvSharp دارای مجوز BSD است (همانند کتابخانه‌ی اصلی OpenCV) و محدودیتی برای استفاده ندارد. هر دو نوع مدل برنامه نویسی OpenCV را که شامل متدهای C و ++C آن‌است، پشتیبانی می‌کند و در طراحی آن سعی شده‌است که بیشترین نزدیکی به طراحی اصلی OpenCV وجود داشته باشد. همچنین این کتابخانه چندسکویی بوده و با Mono لینوکسی نیز سازگار است و از دات نت 2 به بعد را نیز پشتیبانی می‌کند. جامعه‌ی کاربری آن فعال است و مدام به روز می‌شود.

ج) SharperCV
دیگر نگهداری نمی‌شود.

د) OpenCVDotNet 
آخرین تاریخ به روز رسانی آن سال 2007 است.

ه) DirectCV
آخرین تاریخ به روز رسانی آن سال 2011 است.


در این بین یکی از بهترین انتخاب‌ها، کتابخانه‌ی OpenCvSharp ژاپنی است. مجوز استفاده‌ی از آن محدود نیست. به روز رسانی مرتب و منظمی دارد و API آن طوری طراحی شده‌است که به سادگی بتوانید مثال‌های C و ++C کتابخانه‌ی OpenCV را تبدیل به معادل‌های #C کنید.


نصب OpenCvSharp

برای نصب کتابخانه‌ی OpenCvSharp می‌توان از بسته‌های نیوگت آن کمک گرفت. این کتابخانه به همراه دو بسته‌ی نیوگت ارائه می‌شود.
اگر فرمان ذیل را صادر کنید
 PM> Install-Package OpenCvSharp-AnyCPU
علاوه بر اسمبلی‌های دات نتی OpenCVSharp، کتابخانه‌ی native مربوط به OpenCV سازگار با نگارش ارائه شده را نیز دریافت خواهید کرد.
و اگر دستور ذیل را اجرا کنید:
 PM> Install-Package OpenCvSharp-WithoutDll
به این معنا است که تنها اسمبلی‌های دات نتی OpenCVSharp را دریافت می‌کنید. در این حالت نیاز است به سایت OpenCV مراجعه و بسته‌های کامپایل شده‌ی آن‌را دریافت کنید. سپس فایل‌های dll موجود در پوشه‌ی opencv\build\x64\vc12\bin را برای مثال به پوشه‌ی bin پروژه‌ی خود کپی نمائید.

روش توصیه شده‌ی در اینجا، همان نصب بسته‌ی نیوگت OpenCvSharp-AnyCPU است. به این ترتیب نگارش‌های X86 و X64 کتابخانه‌ی OpenCV سازگار با OpenCvSharp را نیز دریافت خواهید کرد.


نکته‌ای در مورد ارائه‌ی نهایی پروژه‌های مبتنی بر OpenCV

OpenCV یک کتابخانه‌ی native ویندوز است و دات نتی نیست . بنابراین DLL‌های آن باید بسته به معماری CPU جاری، انتخاب شوند. یعنی اگر برنامه‌ی دات نتی خود را در حالت Any CPU کامپایل می‌کنید، این برنامه در یک سیستم 64 بیتی، 64 بیتی رفتار می‌کند و در یک سیستم 32 بیتی، 32 بیتی. بنابراین باید دقت داشت که اگر سیستم جاری 64 بیتی است و می‌خواهید از اسمبلی‌های X86 مربوط به OpenCV استفاده کنید، برنامه با پیام استثنای یافت نشدن OpenCV و BadImageFormatException کرش خواهد کرد. بسته‌ی نیوگت OpenCvSharp-AnyCPU  شامل هر دو معماری X86 و X64 است و هر دو سری DLLهای OpenCV را به همراه دارد.
همچنین OpenCV تحت ویندوز، توسط کامپایلر ویژوال ++C، کامپایل شده‌است. به همین جهت در این حالت، علاوه بر نصب دات نت، نیاز است VC++ redistributable packages را نیز بر روی کامپیوتر کلاینت نصب کرد.
پس از نصب بسته‌ی نیوگت OpenCvSharp-AnyCPU اگر به پوشه‌ی bin برنامه‌ی خود مراجعه کنید، پوشه‌ی جدید dll را نیز می‌توان مشاهده کرد. داخل این پوشه، دو پوشه‌ی X86 و X64 وجود دارند که حاوی DLLهای اصلی OpenCV می‌باشند. در این پوشه‌ها اگر برای مثال فایلی به نام msvcp120.dll را یافتید، یعنی این نگارش از OpenCV نیاز به بسته‌های مخصوص VC++ 12 دارد.

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


اولین برنامه‌ی OpenCVSharp


پس از نصب بسته‌ی نیوگت OpenCvSharp-AnyCPU، مقدمات نصب OpenCV به پایان می‌رسد. در ادامه یک برنامه‌ی کنسول جدید را ایجاد کرده و کدهای ذیل را به آن اضافه کنید:
using OpenCvSharp;
 
namespace OpenCVSharpSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            var img = Cv.CreateImage(new CvSize(128, 128), BitDepth.U8, 1);
 
            for (var y = 0; y < img.Height; y++)
            {
                for (var x = 0; x < img.Width; x++)
                {
                    Cv.Set2D(img, y, x, x + y);
                }
            }
 
            Cv.NamedWindow("window");
            Cv.ShowImage("window", img);
            Cv.WaitKey();
            Cv.DestroyWindow("window");
 
            Cv.ReleaseImage(img);
        }
    }
}
این خروجی را دریافت خواهید کرد:


در این مثال یک تصویر 128*128 ایجاد شده و سپس با گرادیانی از رنگ خاکستری پر می‌شود. در ادامه یک پنجره‌ی native مخصوص OpenCV ایجاد شده و این تصویر در آن نمایش داده می‌شود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
پاسخ به بازخورد‌های پروژه‌ها
نشان دادن گزارش قبل از ذخیره یا چاپ
الان یک ماهی میشه که ویندوز 8 - 64 bit نصب کردم و Adobe Reader هم ندارم ولی وقتی ویندوز سون 64 بیتی داشتم (Adobe Reader هم بود) باز هم همین مشکل رو داشتم.