نظرات مطالب
تولید SiteMap استاندارد و ایجاد یک ActionResult اختصاصی برای Return کردن SiteMap تولید شده

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

در مورد خروجی با پسوند xml هم باید مسیریابی جدید اضافه کنی که قبلا در نظرات همین بحث مطرح شده (اولین نظر به علاوه مورد robots.txt که در نظرات بعدی هست).

مطالب
نحوه‌ی استفاده از کتابخانه‌ی 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 ورودی است.

نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها
- احتمالا مرتبط است به حالت Transient که در نظر بعدی به آن اشاره کردید. یک نمونه پروژه که از الگوی جاری استفاده می‌کند DNTIdentity است که جزئیات و توضیحات بیشتر آن‌را می‌توانید در گروه ASP.NET Core Identity پیگیری نمائید.
- همچنین مطالب « کار با کلیدهای اصلی و خارجی در EF Code first» و «شروع به کار با EF Core 1.0 - قسمت 13 - بررسی سیستم ردیابی تغییرات» را نیز مطالعه کنید .  
مطالب
آموزش ایجاد برنامه های چند زبانه در WPF
با گسترش استفاده از کامپیوتر در بسیاری از امور روزمره انسان‌ها سازگار بودن برنامه‌ها با سلیقه کاربران به یکی از نیاز‌های اصلی برنامه‌های کامپیوتری تبدیل شده است. بدون شک زبان و فرهنگ یکی از مهم‌ترین عوامل در ایجاد ارتباط نزدیک بین برنامه و کاربر به شمار می‌رود و نقشی غیر قابل انکار در میزان موفقیت یک برنامه به عهده دارد. از این رو در این نوشته تلاش بر آن است تا یکی از ساده‌ترین و در عین حال کارا‌ترین راه‌های ممکن برای ایجاد برنامه‌های چند زبانه با استفاده از تکنولوژی WPF آموزش داده شود.

مروری بر روش‌های موجود
همواره روش‌های مختلفی برای پیاده سازی یک ایده در دنیای نرم افزار وجود دارد که هر روش را می‌توان بر حسب نیاز مورد استفاده قرار داد. در برنامه‌های مبتنی بر WPF معمولا از دو روش عمده برای این منظور استفاده می‌شود:

1-استفاده از فایل‌های resx
در این روش که برای Win App نیز استفاده می‌شود، اطلاعات مورد نیاز برای هر زبان به شکل جدول هایی دارای کلید و مقدار در داخل یک فایل .resx نگهداری می‌شود و در زمان اجرای برنامه بر اساس انتخاب کاربر اطلاعات زبان مورد نظر از داخل فایل  resx خوانده شده و نمایش داده می‌شود. یکی از ضعف هایی که این روش در عین ساده بودن دارد این است که همه اطلاعات مورد نیاز داخل assembly اصلی برنامه قرار می‌گیرد و امکان افزودن زبان‌های جدید بدون تغییر دادن برنامه اصلی ممکن نخواهد بود.

2-استفاده از فایل‌های csv که به فایل‌های dll تبدیل می‌شوند
در این روش با استفاده از ابزار‌های موجود در کامپایلر WPF برای هر کنترل یک property به نام Uid ایجاد شده و مقدار دهی می‌شود. سپس با ابزار دیگری ( که جزو ابزار‌های کامپایلر محسوب نمی‌شود ) از فایل csproj پروژه یک خروجی اکسل با فرمت csv ایجاد می‌شود که شامل Uid‌های کنترل‌ها و مقادیر آن‌ها است. پس از ترجمه متون مورد نظر به زبان مقصد با کمک ابزار دیگری فایل اکسل مورد نظر به یک net assembly تبدیل می‌شود و داخل پوشه ای با نام culture استاندارد ذخیره می‌شود. ( مثلا برای زبان فارسی نام پوشه fa-IR خواهد بود ). زمانی که برنامه اجرا می‌شود بر اساس culture ای که در سیستم عامل انتخاب شده است و در صورتی که برای آن culture فایل dll ای موجود باشد، زبان مربوط به آن culture را load خواهد کرد. با وجود این که این روش مشکل روش قبلی را ندارد و بیشتر با ویژگی‌های WPF سازگار است اما پروسه ای طولانی برای انجام کار‌ها دارد و به ازای هر تغییری باید کل مراحل هر بار تکرار شوند. همچنین مشکلاتی در نمایش برخی زبان‌ها ( از جمله فارسی ) در این روش مشاهده شده است.

روش سوم!
روش سوم اما کاملا بر پایه WPF و در اصطلاح WPF-Native می‌باشد. ایده از آنجا ناشی شده است که برای ایجاد skin در برنامه‌های WPF استفاده می‌شود. در ایجاد برنامه‌های Skin-Based به این شیوه عمل می‌شود که skin‌های مورد نظر به صورت style هایی در داخل ResourceDictionary ‌ها قرار می‌گیرند. سپس آن ResourceDictionary به شکل dll کامپایل می‌شود. در برنامه اصلی نیز همه کنترل‌ها style هایشان را به شکل dynamic resource از داخل یک ResourceDictionary مشخص شده load می‌کنند. حال کافی است برای تغییر skin فعلی، ResourceDictionary  مورد نظر از dll مشخص load شود و ResourceDictionary ای که در حال حاضر در برنامه از آن استفاده می‌شود با ResourceDictionary ای که load شده جایگزین شود. کنترل‌ها مقادیر جدید را از ResourceDictionary جدید به شکل کاملا خودکار دریافت خواهند کرد.
به سادگی می‌توان از این روش برای تغییر زبان برنامه نیز استفاده کرد با این تفاوت که این بار، به جای Style ها، String‌های زبان‌های مختلف را درون resource‌ها نگهداری خواهیم کرد.

یک مثال ساده
در این قسمت نحوه پیاده سازی این روش با ایجاد یک نمونه برنامه ساده که دارای دو زبان انگلیسی و فارسی خواهد بود آموزش داده می‌شود.
ابتدا یک پروژه WPF Application در Visual Studio 2010 ایجاد کنید. در MainWindow سه کنترل Button قرار دهید و یک ComboBox که قرار است زبان‌های موجود را نمایش دهد و با انتخاب یک زبان، نوشته‌های درون Button‌ها متناسب با آن تغییر خواهند کرد.

توجه داشته باشید که برای Button‌ها نباید به صورت مستقیم مقداری به Content شان داده شود. زیرا مقدار مورد نظر از داخل ResourceDictionary که خواهیم ساخت به شکل dynamic گرفته خواهد شد. پس در این مرحله یک ResourceDictionary به پروژه اضافه کرده و در آن resource هایی به شکل string ایجاد می‌کنیم. هر resource دارای یک Key می‌باشد که بر اساس آن، Button مورد نظر، مقدار آن Resource را load خواهد کرد. فایل ResourceDictionary را
Culture_en-US.xaml نامگذاری کنید و مقادیر مورد نظر را به آن اضافه نمایید.  

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:system="clr-namespace:System;assembly=mscorlib">
    <system:String x:Key="button1">Hello!</system:String>
    <system:String x:Key="button2">How Are You?</system:String>
    <system:String x:Key="button3">Are You OK?</system:String>
 
</ResourceDictionary>

دقت کنید که namespace ای که کلاس string در آن قرار دارد به فایل xaml اضافه شده است و پیشوند system به آن نسبت داده شده است.

با افزودن یک ResourceDictionary به پروژه، آن ResourceDictionary به MergedDictionary کلاس App اضافه می‌شود. بنابراین فایل App.xaml به شکل زیر خواهد بود:

<Application x:Class="BeRMOoDA.WPF.LocalizationSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
 
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Culture_en-US.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
 
    </Application.Resources>
</Application>

برای اینکه بتوانیم محتوای Button‌های موجود را به صورت داینامیک و در زمان اجرای برنامه، از داخل Resource‌ها بگیریم، از DynamicResource استفاده می‌کنیم.

<Button Content="{DynamicResource ResourceKey=button1}" />
<Button Content="{DynamicResource ResourceKey=button2}" />
<Button Content="{DynamicResource ResourceKey=button3}" />

بسیار خوب! اکنون باید شروع به ایجاد یک ResourceDictionary برای زبان فارسی کنیم و آن را به صورت یک فایل dll کامپایل نماییم.
برای این کار یک پروژه جدید در قسمت WPF از نوع User control ایجاد می‌کنیم و نام آن را Culture_fa-IR_Farsi قرار می‌دهیم. لطفا شیوه نامگذاری را رعایت کنید چرا که در ادامه به آن نیاز خواهیم داشت.
پس از ایجاد پروژه فایل UserControl1.xaml را از پروژه حذف کنید و یک ResourceDictionary با نام Culture_fa-IR.xaml اضافه کنید. محتوای آن را پاک کنید و محتوای فایل Culture_en-US.xaml را از پروژه قبلی به صورت کامل در فایل جدید کپی کنید. دو فایل باید ساختار کاملا یکسانی از نظر key برای Resource‌های موجود داشته باشند. حالا زمان ترجمه فرا رسیده است! رشته‌های دلخواه را ترجمه کنید و پروژه را build نمایید. 
پس از ترجمه فایل Culture_fa-IR.xaml به شکل زیر خواهد بود:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:system="clr-namespace:System;assembly=mscorlib">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Culture_fa-IR_Farsi.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <system:String x:Key="button1">سلام!</system:String>
    <system:String x:Key="button2">حالت چطوره؟</system:String>
    <system:String x:Key="button3">خوبی؟</system:String>
</ResourceDictionary>
خروجی این پروژه یک فایل با نام Culture_fa-IR_Farsi.dll خواهد بود که حاوی یک ResourceDictionary برای زبان فارسی می‌باشد.

در ادامه میخواهیم راهکاری ارئه دهیم تا بتوان فایل‌های dll مربوط به زبان‌ها را در زمان اجرای برنامه اصلی، load کرده و نام زبان‌ها را در داخل ComboBox ای که داریم نشان دهیم. سپس با انتخاب هر زبان در ComboBox، محتوای Button‌ها بر اساس زبان انتخاب شده تغییر کند.
برای سهولت کار، نام فایل‌ها را به گونه ای انتخاب کردیم که بتوانیم ساده‌تر به این هدف برسیم. نام هر فایل از سه بخش تشکیل شده است:
Culture_[standard culture notation]_[display name for this culture].dll
یعنی اگر فایل Culture_fa-IR_Farsi.dll را در نظر بگیریم، Culture نشان دهنده این است که این فایل مربوط به یک culture می‌باشد. fa-IR نمایش استاندارد culture برای کشور ایران و زبان فارسی است و Farsi هم مقداری است که می‌خواهیم در ComboBox برای این زبان نمایش داده شود.
پوشه ای با نام Languages در کنار فایل اجرایی برنامه اصلی ایجاد کنید و فایل Culture_fa-IR_Farsi.dll را درون آن کپی کنید. تصمیم داریم همه dll‌های مربوط به زبان‌ها را داخل این پوشه قرار دهیم تا مدیریت آن‌ها ساده‌تر شود. 
برای مدیریت بهتر فایل‌های مربوط به زبان‌ها یک کلاس با نام CultureAssemblyModel خواهیم ساخت که هر instance از آن نشانگر یک فایل زبان خواهد بود. یک کلاس با این نام به پروژه اضافه کنید و property‌های زیر را در آن تعریف نمایید:

public class CultureAssemblyModel
    {
        //the text will be displayed to user as language name (like Farsi)
        public string DisplayText { get; set; }
        //name of .dll file (like Culture_fa-IR_Farsi.dll)
        public string Name { get; set; }
        //standar notation of this culture (like fa-IR)
        public string Culture { get; set; }
        //name of resource dictionary file inside the loaded .dll (like Culture_fa-IR.xaml)
        public string XamlFileName { get; set; }
    }
اکنون باید لیست culture‌های موجود را از داخل پوشه languages خوانده و نام آنها را در ComboBox نمایش دهیم.
برای خواندن لیست culture‌های موجود، لیستی از CultureAssmeblyModel‌ها ایجاد کرده و با استفاده از متد LoadCultureAssmeblies، آن را پر می‌کنیم.

//will keep information about loaded assemblies
public List<CultureAssemblyModel> CultureAssemblies { get; set; }
 
//loads assmeblies in languages folder and adds their info to list
 void LoadCultureAssemblies()
 {
      //we should be sure that list is empty before adding info (do u want to add some cultures more than one? of course u dont!)
      CultureAssemblies.Clear();
      //creating a directory represents applications directory\languages
      DirectoryInfo dir = new DirectoryInfo(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\languages");
      //getting all .dll files in the language folder and its sub dirs. (who knows? maybe someone keeps each culture file in a seperate folder!)
      var assemblies = dir.GetFiles("*.dll", SearchOption.AllDirectories);
      //for each found .dll we will create a model and set its properties and then add to list  for (int i = 0; i < assemblies.Count(); i++)
      {
string name = assemblies[i].Name;
  CultureAssemblyModel model = new CultureAssemblyModel() { DisplayText = name.Split('.', '_')[2], Culture = name.Split('.', '_')[1], Name = name  , XamlFileName =name.Substring(0, name.LastIndexOf(".")) + ".xaml" }; CultureAssemblies.Add(model); } }
پس از دریافت اطلاعات culture‌های موجود، زمان نمایش آن‌ها در ComboBox است. این کار بسیار ساده است، تنها کافی است ItemsSource آن را با لیستی از CultureAssmeblyModel‌ها که ساختیم، مقدار دهی کنیم.

comboboxLanguages.ItemsSource = CultureAssemblies;
البته لازم به ذکر است که برای نمایش فقط نام هر CultureAssemblyModel در ComboBox، باید ItemTemplate مناسبی برای ComboBox ایجاد کنیم. در مثال ما ItemTemplate به شکل زیر خواهد بود:

<ComboBox HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" MinWidth="100" Name="comboboxLanguages">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding DisplayText}"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
</ComboBox>
توجه داشته باشید که با وجود اینکه فقط نام را در ComboBox نشان می‌دهیم، اما باز هم هر آیتم از ComboBox یک instance از نوع CultureAssemblyModel می‌باشد.

در مرحله بعد، قرار است متدی بنویسیم که اطلاعات زبان انتخاب شده را گرفته و با جابجایی ResourceDictionary ها، زبان برنامه را تغییر دهیم.
متدی با نام LoadCulture در کلاس App ایجاد می‌کنیم که یک CultureAssemblyModel به عنوان ورودی دریافت کرده و ResourceDictionary داخل آن را load می‌کند و آن را با ResourceDictionary فعلی موجود در App.xaml جابجا می‌نماید.
با این کار، Button هایی که قبلا مقدار Content خود را از Resource‌های موجود دریافت می‌کردند، اکنون از Resource‌های جابجا شده خواهند گرفت و به این ترتیب زبان انتخاب شده بر روی برنامه اعمال می‌شود.

//loads selected culture
 public void LoadCulture(CultureAssemblyModel culture)
 {
     //creating a FileInfo object represents .dll file of selected cultur
     FileInfo assemblyFile = new FileInfo("languages\\" + culture.Name);
     //loading .dll into memory as a .net assembly
     var assembly = Assembly.LoadFile(assemblyFile.FullName);
     //getting .dll file name
     var assemblyName = assemblyFile.Name.Substring(0, assemblyFile.Name.LastIndexOf("."));
     //creating string represents structure of a pack uri (something like this: /{myassemblyname;component/myresourcefile.xaml}
     string packUri = string.Format(@"/{0};component/{1}", assemblyName, culture.XamlFileName);
     //creating a pack uri
     Uri uri = new Uri(packUri, UriKind.Relative);
     //now we have created a pack uri that represents a resource object in loaded assembly
     //and its time to load that as a resource dictionary (do u remember that we had resource dictionary in culture assemblies? don't u?)
     var dic = Application.LoadComponent(uri) as ResourceDictionary;
     dic.Source = uri;
     //here we will remove current merged dictionaries in our resource dictionary and add recently-loaded resource dictionary as e merged dictionary
     var mergedDics = this.Resources.MergedDictionaries;
     if (mergedDics.Count > 0)
          mergedDics.Clear();
     mergedDics.Add(dic);
 }
برای ارسال زبان انتخاب شده به این متد، باید رویداد SelectionChanged را برای ComboBox مدیریت کنیم:

void comboboxLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e)
 {
     var selectedCulture = (CultureAssemblyModel)comboboxLanguages.SelectedItem;
     App app = Application.Current as App;
     app.LoadCulture(selectedCulture);
 }

کار انجام شد!
از مزیت‌های این روش می‌توان به WPF-Native بودن، سادگی در پیاده سازی، قابلیت load کردن هر زبان جدیدی در زمان اجرا بدون نیاز به کوچک‌ترین تغییر در برنامه اصلی و همچنین پشتیبانی کامل از نمایش زبان‌های مختلف از جمله فارسی اشاره کرد. 





مطالب
معرفی Actor Based Programming و توسعه نرم افزار های مقیاس پذیر و دارای عملیات همزمان بسیار زیاد - قسمت اول
مقدمه : 
زمانیکه هدفمان تولید سامانه‌ی نرم افزاری باشد که تعداد بسیار زیادی از کاربران با آن سرو کار دارند و اتفاقاً این سامانه قرار است عملیات بسیار حساسی (نظیر عملیات بانکی و مالی، مخابراتی و ...) را انجام دهد و عدم سرویس دهی مناسب آن قابل تحمل نبوده و باعث خسارات مالی، نارضایتی و ... گردد می‌بایست از روش‌های خاصی برای توسعه‌ی این گونه سیستم‌ها استفاده نمود. این نرم افزار‌ها برای اینکه بتوانند به تعداد درخواست‌های بسیار زیاد همزمان پاسخگو باشند و سرویس خود را با کیفیت مناسب ارائه دهند، می‌بایست دارای ویژگی‌های خاصی نظیر مقیاس پذیری (scalable) و تحمل پذیری در مقابل خطا (fault tolerance)  باشند.
خوب حالا که صورت مسئله مشخص شد، یکی از راه حل‌های موجود را که مدل Actor Based است، بررسی می‌کنیم.
مدل Actor Based یکـی از مـدل هـای اسـتاندارد بـرای توسـعه‌ی نـرم افزارهـایی بـا قابلیـت اطمینـان بسـیار بالا، تحمل پذیر درمقابـل خطـا و پاسـخ دهـی بسـیار سـریع مـی باشـد. در ایـن مـدل، وظـایف نـرم افـزار بـه مجموعــه‌ای از Actor هــا تقســیم (توزیع) گردیـده و هــر یــک از Actor هــا بــه صــورتی کــاملاً ایزولـه، در نــخ(thread) خـاص خودشـان اجـرا شـده و بخشـی از وظـایف سیسـتم را انجـام مـی دهنـد. سـپس بـا اتصـال Actor هـا بـه یکـدیگر، یـک خـط لولـه (Pipeline) تشـکیل شـده و بـا اسـتفاده از مکـانیزم هـای ارسـال و دریافت پیـام، امکـان همکـاری و برقـراری ارتبـاط بـین Actor هـا فـراهم شـده و در نتیجـه وظیفـه‌ی اصـلی و کلی نرم افـزار بـا حرکـت در یـک خـط لولـه و عبـور از Actor هـای مختلـف بـه صـورت مـوازی و همزمـان انجام خواهد شد. با توجه بـه اینکـه هـر یـک از پیـام‌هـای وارده بـه یـک Actor در یـک thread جداگانـه اجـرا مـی‌شـود، امکــان اینکــه در یــک لحظــه چنــدین Thread در یــک Actor در حــال اجــرا باشـنـد و جــود دارد و درنتیجه باید مکانیزم‌هایی وجود داشته باشد کـه تضـمین کنـد پیـام هـای وارد شـده بـه خـط لولـه، بـه ترتیـب معین شده، اجـرا و از خط لوله خارج می‌شوند. 
در این مدل هر یک از Actor هـا مـی تواننـد بـه صـورت توزیـع شـده و بـر روی سـروری مجزا اجـرا شـوند. خوشـبختانه فریمـورک هـای متفـاوت و بسـیار قـوی جهـت توسـعه بـه روش Actor Base وجـود دارند؛ بـه عنـوان مثال TPL DataFlow در Net. یکـی از نمونه‌های ساده آن بـوده کـه در سـال 2012 توسط Microsoft معرفـی شـد و Akka هم یک نمونه‌ی بسیار پخته‌تر و در بستر جاوا مطرح می‌باشد که پیاده سازی دات نتی آن هم با نام Akka.net موجود است. Erlang نیز محصول Ericsson بوده و دنیای خاص خود را دارد.
در این روش وظیفه توسعه دهنده این است که اولاً یک خط لوله از اکتور‌ها را تشکیل داده (کانفیگ)  و یک عمل بزرگ را به چندین عمل کوچک‌تر تقسیم نموده و هر کدام را به یک اکتور جهت اجرا ارسال نماید. تصویر نمونه زیر یک خط لوله متشکل از 4 اکتور را نشان می‌دهد که از طریق ارسال پیام با یکدیگر در ارتباط هستند تا با همکاری یکدیگر عملی را انجام دهند. این ساختار، Pipeline یا خط لوله نامیده می‌شود. 

در قسمت بعدی با جزئیات بیشتر و با نمونه‌های عملی این روش را بررسی می‌کنیم.
نظرات مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت سوم
در نسخه نهایی پروژه شما دو پلاگین قرار دادید. پلاگینی برای اخبار و مقالات و یک پروژه هم برای مدل‌های مشترک که کاربر در این قسمت قرار داره. حالا سوال من اینه که فرضا مدل Tags برای هر دو جدول اخبار و مقالات یکی هست. و ممکنه اصلا ما یک افزونه دیگری هم داشته باشیم که بخواد برچسب بگیره. برای این جور روابط باید به چه شکلی عمل کرد ؟ رابطه‌ی بین جدول اخبار و برچسب‌ها و رابطه بین جدول مقالات و برچست‌ها همگی چند به چند هستند.
نظرات مطالب
چک لیست تهیه یک برنامه ASP.NET MVC
با سلام جناب نصیری
عموما برای سادگی کار و تفکیک پذیری یک برنامه MVC پروژه به لایه هایی تقسیم بندی می‌شود لطفا در مورد لایه‌های زیر یک توضیح کلی بدهید و اینکه معمولا در این لایه‌ها چه قسمت هایی از پروژه قرار می‌گیرند :
Application.Web
Application.Manager
Application.Repository
Application.DAL
Application.DTO
Application.Core
Application.Common
و اینکه لایه‌های DomainClasses و DataLayer در دل این لایه‌ها قرار می‌گیرند یا بصورت لایه‌های مجزا هستند.
با تشکر
مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت اول - معرفی، نصب و تعریف قواعد اعتبارسنجی
روش مرسوم اعتبارسنجی اطلاعات مدل‌های ASP.NET Core، با استفاده از data annotations توکار آن است که در بسیاری از موارد هم به خوبی کار می‌کند. اما اگر به دنبال ویژگی‌های دیگری مانند نوشتن آزمون‌های واحد برای اعتبارسنجی اطلاعات، جداسازی شرط‌های اعتبارسنجی از تعاریف مدل‌ها، نوشتن اعتبارسنجی‌های پیچیده به همراه تزریق وابستگی‌ها هستید، کتابخانه‌ی FluentValidation می‌تواند جایگزین بهتر و بسیار کاملتری باشد.


نصب کتابخانه‌ی FluentValidation در پروژه

فرض کنید پروژه‌ی ما از سه پوشه‌ی FluentValidationSample.Web، FluentValidationSample.Models و FluentValidationSample.Services تشکیل شده‌است که اولی یک پروژه‌ی MVC است و دو مورد دیگر classlib هستند.
در پروژه‌ی FluentValidationSample.Models، بسته‌ی نیوگت کتابخانه‌ی FluentValidation را به صورت زیر نصب می‌کنیم:
dotnet add package FluentValidation.AspNetCore


جایگزین کردن سیستم اعتبارسنجی مبتنی بر DataAnnotations با FluentValidation

اکنون فرض کنید در پروژه‌ی Models، مدل ثبت‌نام زیر را اضافه کرده‌ایم که از همان data annotations توکار و استاندارد ASP.NET Core برای اعتبارسنجی اطلاعات استفاده می‌کند:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class RegisterModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email")]
        [EmailAddress]
        public string Email { get; set; }

        [Range(18, 60)]
        [Display(Name = "Age")]
        public int Age { get; set; }
    }
}
برای جایگزین کردن data annotations اعتبارسنجی اطلاعات با روش FluentValidation، می‌توان به صورت زیر عمل کرد:
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.UserName).NotNull();
            RuleFor(x => x.Password).NotNull().Length(6, 100);
            RuleFor(x => x.ConfirmPassword).Equal(x => x.Password);
            RuleFor(x => x.Email).EmailAddress();
            RuleFor(x => x.Age).InclusiveBetween(18, 60);
        }
    }
}
برای این منظور ابتدا یک کلاس Validator را با ارث بری از AbstractValidator از نوع مدلی که می‌خواهیم قواعد اعتبارسنجی آن‌را مشخص کنیم، ایجاد می‌کنیم. سپس در سازنده‌ی آن، می‌توان به متدهای تعریف شده‌ی در این کلاس پایه دسترسی یافت.
در اینجا در ابتدا به ازای هر خاصیت کلاس مدل مدنظر، یک RuleFor تعریف می‌شود که با استفاده از static reflection، امکان تعریف strongly typed آن‌ها وجود دارد. سپس ویژگی Required به متد NotNull تبدیل می‌شود و ویژگی StringLength توسط متد Length قابل تعریف خواهد بود و یا ویژگی Compare توسط متد Equal به صورت strongly typed به خاصیت دیگری متصل می‌شود.

پس از این تعاریف، می‌توان ویژگی‌های اعتبارسنجی اطلاعات را از مدل ثبت نام حذف کرد و تنها ویژگی‌های خاص Viewهای MVC را در صورت نیاز باقی گذاشت:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class RegisterModel
    {
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        public string ConfirmPassword { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Display(Name = "Age")]
        public int Age { get; set; }
    }
}


تعریف پیام‌های سفارشی اعتبارسنجی

روش تعریف پیام‌های سفارشی شکست اعتبارسنجی اطلاعات را توسط متد WithMessage در ادامه مشاهده می‌کنید:
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.UserName)
                .NotNull()
                    .WithMessage("Your first name is required.")
                .MaximumLength(20)
                    .WithMessage("Your first name is too long!")
                .MinimumLength(3)
                    .WithMessage(registerModel => $"Your first name `{registerModel.UserName}` is too short!");

            RuleFor(x => x.Password)
                .NotNull()
                    .WithMessage("Your password is required.")
                .Length(6, 100);

            RuleFor(x => x.ConfirmPassword)
                .NotNull()
                    .WithMessage("Your confirmation password is required.")
                .Equal(x => x.Password)
                    .WithMessage("The password and confirmation password do not match.");

            RuleFor(x => x.Email).EmailAddress();
            RuleFor(x => x.Age).InclusiveBetween(18, 60);
        }
    }
}
به ازای هر متد تعریف یک قاعده‌ی اعتبارسنجی جدید، بلافاصله می‌توان از متد WithMessage نیز استفاده کرد. همچنین این متد می‌تواند به اطلاعات اصل model دریافتی نیز همانند پیام سفارشی مرتبط با MinimumLength نام کاربری، دسترسی پیدا کند.


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

فرض کنید می‌خواهیم یک کلمه‌ی عبور وارد شده‌ی معتبر، حتما از جمع حروف کوچک، بزرگ، اعداد و symbols تشکیل شده باشد. برای این منظور می‌توان از متد Must استفاده کرد:
using System.Text.RegularExpressions;
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.Password)
                .NotNull()
                    .WithMessage("Your password is required.")
                .Length(6, 100)
                .Must(password => hasValidPassword(password));
            //...

        }

        private static bool hasValidPassword(string password)
        {
            var lowercase = new Regex("[a-z]+");
            var uppercase = new Regex("[A-Z]+");
            var digit = new Regex("(\\d)+");
            var symbol = new Regex("(\\W)+");
            return lowercase.IsMatch(password) &&
                    uppercase.IsMatch(password) &&
                    digit.IsMatch(password) &&
                    symbol.IsMatch(password);
        }
    }
}
متد Must، می‌تواند مقدار خاصیت متناظر را نیز در اختیار ما قرار دهد و بر اساس آن مقدار می‌توان خروجی true/false ای را بازگشت داد تا نشان شکست و یا موفقیت آمیز بودن اعتبارسنجی اطلاعات باشد.

البته lambda expression نوشته شده را می‌توان توسط method groups، به صورت زیر نیز خلاصه نوشت:
RuleFor(x => x.Password)
    .NotNull()
        .WithMessage("Your password is required.")
    .Length(6, 100)
    .Must(hasValidPassword);


انتقال تعاریف اعتبارسنج‌های سفارشی خواص به کلاس‌های مجزا

اگر نیاز به استفاده‌ی از متد hasValidPassword در کلاس‌های دیگری نیز وجود دارد، می‌توان اینگونه اعتبارسنجی‌های سفارشی را به کلاس‌های مجزایی نیز تبدیل کرد. برای مثال فرض کنید که می‌خواهیم ایمیل دریافت شده، فقط از یک دومین خاص قابل قبول باشد.
using System;
using FluentValidation;
using FluentValidation.Validators;

namespace FluentValidationSample.Models
{
    public class EmailFromDomainValidator : PropertyValidator
    {
        private readonly string _domain;

        public EmailFromDomainValidator(string domain)
            : base("Email address {PropertyValue} is not from domain {domain}")
        {
            _domain = domain;
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            if (context.PropertyValue == null) return false;
            var split = context.PropertyValue.ToString().Split('@');
            return split.Length == 2 && split[1].Equals(_domain, StringComparison.OrdinalIgnoreCase);
        }
    }
}
برای این منظور یک کلاس جدید را با ارث‌بری از PropertyValidator تعریف شده‌ی در فضای نام FluentValidation.Validators، ایجاد می‌کنیم. سپس متد IsValid آن‌را بازنویسی می‌کنیم تا برای مثال ایمیل‌ها را صرفا از دومین خاصی بپذیرد.
PropertyValidatorContext امکان دسترسی به نام و مقدار خاصیت در حال اعتبارسنجی را میسر می‌کند. همچنین مقدار کل model جاری را نیز به صورت یک object در اختیار ما قرار می‌دهد.

اکنون برای استفاده‌ی از آن می‌توان از متد SetValidator استفاده کرد:
RuleFor(x => x.Email)
    .SetValidator(new EmailFromDomainValidator("gmail.com"));
و یا حتی می‌توان یک متد الحاقی fluent را نیز برای آن طراحی کرد تا SetValidator را به صورت خودکار فراخوانی کند:
    public static class CustomValidatorExtensions
    {
        public static IRuleBuilderOptions<T, string> EmailAddressFromDomain<T>(
            this IRuleBuilder<T, string> ruleBuilder, string domain)
        {
            return ruleBuilder.SetValidator(new EmailFromDomainValidator(domain));
        }
    }
سپس تعریف قاعده‌ی اعتبارسنجی ایمیل‌ها به صورت زیر تغییر می‌کند:
RuleFor(x => x.Email).EmailAddressFromDomain("gmail.com");


تعریف قواعد اعتبارسنجی خواص تو در تو و لیستی

فرض کنید به RegisterModel این قسمت، دو خاصیت آدرس و شماره تلفن‌ها نیز اضافه شده‌است که یکی به شیء آدرس و دیگری به مجموعه‌ای از آدرس‌ها اشاره می‌کند:
    public class RegisterModel
    {
        // ...

        public Address Address { get; set; }

        public ICollection<Phone> Phones { get; set; }
    }

    public class Phone
    {
        public string Number { get; set; }
        public string Description { get; set; }
    }

    public class Address
    {
        public string Location { get; set; }
        public string PostalCode { get; set; }
    }
در یک چنین حالتی، ابتدا به صورت متداول، قواعد اعتبارسنجی Phone و Address را جداگانه تعریف می‌کنیم:
    public class PhoneValidator : AbstractValidator<Phone>
    {
        public PhoneValidator()
        {
            RuleFor(x => x.Number).NotNull();
        }
    }

    public class AddressValidator : AbstractValidator<Address>
    {
        public AddressValidator()
        {
            RuleFor(x => x.PostalCode).NotNull();
            RuleFor(x => x.Location).NotNull();
        }
    }
سپس برای تعریف اعتبارسنجی دو خاصیت پیچیده‌ی اضافه شده، می‌توان از همان متد SetValidator استفاده کرد که اینبار پارامتر ورودی آن، نمونه‌ای از AbstractValidator‌های هرکدام است. البته برای خاصیت مجموعه‌ای اینبار باید با متد RuleForEach شروع کرد:
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            // ...

            RuleFor(x => x.Address).SetValidator(new AddressValidator());

            RuleForEach(x => x.Phones).SetValidator(new PhoneValidator());
        }


در قسمت بعد، روش‌های مختلف استفاده‌ی از قواعد اعتبارسنجی تعریف شده را در یک برنامه‌ی ASP.NET Core بررسی می‌کنیم.



برای مطالعه‌ی بیشتر
- «FluentValidation #1»
مطالب
اتصال SQL Server به MySQL

اگر SQL Server و MySQL بر روی سیستم شما نصب است، روشی ساده برای انتقال اطلاعات بین این دو وجود دارد که نیازی به دخالت هیچ نوع برنامه‌ی جانبی نداشته و با امکانات موجود قابل مدیریت است.

ایجاد یک Linked server

برای اینکه SQL Server را به MySQL متصل کنیم می‌توان بین این دو یک Linked server تعریف کرد و سپس دسترسی به بانک‌های اطلاعاتی MySQL همانند یک بانک اطلاعاتی محلی SQL Server خواهد شد که شرح آن در ادامه ذکر می‌شود.
ابتدا نیاز است تا درایور ODBC مربوط به MySQL دریافت و نصب شود. آن‌را می‌توانید از اینجا دریافت کنید : (+)
سپس management studio را گشوده و در قسمت Server objects ، بر روی گزینه‌ی Linked servers کلیک راست نمائید. از منوی ظاهر شده، گزینه‌ی New linked server را انتخاب کنید:


در ادامه، باید تنظیمات زیر را در صفحه‌ی باز شده وارد کرد:


در قسمت Linked server و Product name ، نام دلخواهی را وارد کنید.
Provider انتخابی باید از نوع Microsoft OLE DB Provider for ODBC Drivers باشد.
مهم‌ترین تنظیم آن، قسمت Provider string است که باید به صورت زیر وارد شود (در غیر اینصورت کار نمی‌کند):
DRIVER={MySQL ODBC 5.1 Driver}; SERVER=localhost; DATABASE=testdb; USER=root; PASSWORD=mypass; OPTION=3;PORT=3306; CharSet=UTF8;
در اینجا نام دیتابیس پیش فرض، نام کاربری اتصال به MySQL و Password و غیره را می‌توان تنظیم کرد.
پس از انجام این تنظیمات بر روی دکمه‌ی Ok کلیک کنید تا Linked server ساخته شود:


اگر لیست بانک‌های اطلاعاتی را مشاهده نمودید، یعنی اتصال به درستی برقرار شده است.

تنظیمات ثانویه:

تا اینجا اس کیوال سرور به MySQL متصل شده است، اما برای استفاده بهینه از امکانات موجود نیاز است تا یک سری تغییرات دیگر را هم اعمال کرد.

تنظیم MSDASQL Provider :
در همان قسمت Linked provider ، ذیل قسمت Providers ، گزینه‌ی MSDASQL را انتخاب کرده و بر روی آن کلیک راست نمائید. سپس صفحه‌ی خواص آن‌را انتخاب کنید تا بتوان تنظیمات زیر را به آن اعمال کرد. این پروایدر جهت اتصال به MySQL مورد استفاده قرار می‌گیرد.



فعال سازی RPC :

برای اینکه بتوان از طریق SQL Server رکوردی را در یکی از جداول بانک‌های اطلاعاتی MySQL متصل شده ثبت نمود، می‌توان از دستور زیر استفاده کرد:
EXECUTE('insert into testdb.testtable(f1,f1) values(1,''data'')') at mysql

اینجا testdb نام بانک اطلاعاتی اتصالی MySQL است و testTable هم نام جدول مورد نظر. MySQL ایی که در آخر عبارت ذکر شده همان نام linked server ایی است که پیشتر تعریف کردیم.
به محض سعی در اجرای این کوئری خطای زیر ظاهر می‌شود:
Server 'mysql' is not configured for RPC.

برای رفع این مشکل، مجددا به صفحه‌ی خواص همان liked server ایجاد شده مراجعه کنید. در قسمت Server options دو گزینه مرتبط به RPC باید فعال شوند:



و اکنون برای کوئری گرفتن از اطلاعات ثبت شده هم از عبارت زیر می‌توان استفاده کرد:
SELECT * FROM OPENQUERY(mysql, 'SELECT * FROM testdb.testtable')

در این کوئری، MySQL نام Linked server ثبت شده است و testdb هم یکی از بانک‌های اطلاعاتی MySQL مورد نظر.


انتقال تمام اطلاعات یک جدول از بانک اطلاعاتی MySQL به SQL Server

پس از برقراری اتصال، اکنون import کامل یک جدول MySQL به SQL Server به سادگی اجرای کوئری زیر می‌باشد:
SELECT * INTO MyDb.dbo.testtable FROM openquery(MYSQL, 'SELECT * FROM testdb.testtable')

در این کوئری، MySQL همان Linked server تعریف شده است. MyDB نام بانک اطلاعاتی موجود در SQL Server جاری است و testtable هم جدولی است که قرار است اطلاعات testdb.testtable بانک اطلاعاتی MySQL به آن وارد شود.

با اطلاعات فارسی هم (در سمت SQL Server) مشکلی ندارد. همانطور که مشخص است، در اطلاعات provider string ذکر شده‌، مقدار charset به utf8 تنظیم شده و همچنین اگر نوع collation فیلدهای تعریف شده در MySQL نیز به utf8_persian_ci تنظیم شده باشد، با مشکل ثبت اطلاعات فارسی به صورت ???? مواجه نخواهید شد.


نکته:
اگر بانک اطلاعاتی MySQL شما بر روی local host نصب نیست، جهت فعال سازی دسترسی ریموت به آن، می‌توان به یکی از نکات زیر مراجعه کرد و سپس این اطلاعات جدید باید در همان قسمت provider string مرتبط با تعریف linked server وارد شوند:


مطالب مشابه:

هیچکدام از این روش‌ها قابل استفاده نبودند چون provider string صحیحی را نهایتا تولید نمی‌کنند. همچنین تمام این روش‌ها مبتنی است بر ایجاد DSN در کنترل پنل که اصلا نیازی به‌ آن نیست و اضافی است.

نظرات مطالب
امکان استفاده از کتابخانه‌های native در Blazor WASM 6x
مقاله فوق‌العاده ای بود، ممنون از شما
نقش Emscripten این وسط خیلی پر رنگ هست، چون نه تنها کد C و... رو کامپایل به Web Assembly می‌کنه، بلکه در ادامه هم مثلا اگه قراره فایل db مربوط به sqlite جایی ذخیره بشه، برای مثال تعیین می‌کنه که تو قسمت blob storage موجود در indexed db قرار بگیره، چون فرض مثال مرورگر که نمی‌تونه فایل db رو ذخیره کنه تو درایو C برای مثال! در واقع کد مربوطه رو سازگار با دنیای Browser کامپایل می‌کنه