مطالب
Angular Material 6x - قسمت هفتم - کار با انواع قالب‌ها
در این قسمت می‌خواهیم روش تغییر رنگ‌های قالب‌های پیش‌فرض Angular Material را به همراه تغییر پویای آن‌ها در زمان اجرا، بررسی کنیم. همچنین Angular Material از راست به چپ نیز به خوبی پشتیبانی می‌کند که مثالی از آن‌را در ادامه بررسی خواهیم کرد.


بررسی ساختار یک قالب Angular Material

قالب، مجموعه‌ای از رنگ‌ها است که به کامپوننت‌های Angular Material اعمال می‌شود. هر قالب از چندین جعبه‌رنگ یا palette تشکیل می‌شود:
- primary palette: به صورت گسترده‌ای در تمام کامپوننت‌ها مورد استفاده‌است.
- accent palette: به المان‌های تعاملی انتساب داده می‌شود.
- warn palette: برای نمایش خطاها و اخطارها بکار می‌رود.
- foreground palette: برای متون و آیکن‌ها استفاده می‌شود.
- background palette: برای پس‌زمینه‌ی المان‌ها بکار می‌رود.

روش انتخاب این جعبه رنگ‌ها نیز به صورت زیر است:
<mat-card>
  Main Theme:
  <button mat-raised-button color="primary">
    Primary
  </button>
  <button mat-raised-button color="accent">
    Accent
  </button>
  <button mat-raised-button color="warn">
    Warning
  </button>
</mat-card>
در Angular Material تمام قالب‌ها استاتیک بوده و در زمان کامپایل برنامه به صورت خودکار به آن اضافه می‌شوند. به همین جهت برنامه نیازی به تشکیل این اجزا و کامپایل یک قالب را در زمان آغاز آن ندارد.
همانطور که در قسمت اول این سری نیز بررسی کردیم، بسته‌ی Angular Material به همراه چندین قالب از پیش طراحی شده‌است (قالب‌های از پیش آماده‌ی متریال را در پوشه‌ی node_modules\@angular\material\prebuilt-themes می‌توانید مشاهده کنید) و در حین اجرای برنامه تنها یکی از آن‌ها که در فایل styles.css ذکر شده‌است، مورد استفاده قرار می‌گیرد.
اگر نیاز به سفارشی سازی بیشتری وجود داشته باشد، می‌توان قالب‌های ویژه‌ی خود را نیز طراحی کرد. این قالب جدید باید mat-core() sass mixin را import کند که حاوی تمام شیوه‌نامه‌های مشترک بین کامپوننت‌ها است. این مورد باید تنها یکبار به کل برنامه الحاق شود تا حجم آن‌را بیش از اندازه زیاد نکند. سپس این قالب سفارشی، جعبه رنگ‌های خاص خودش را معرفی می‌کند. در ادامه این جعبه رنگ‌ها توسط توابع mat-light-theme و یا mat-dark-theme ترکیب شده و مورد استفاده قرار می‌گیرند. سپس این قالب را include خواهیم کرد. به این ترتیب یک قالب سفارشی Angular Material، چنین طرحی را دارد:
@import '~@angular/material/theming';
@include mat-core();

$candy-app-primary: mat-palette($mat-indigo);
$candy-app-accent: mat-palette($mat-pink, A200, A100, A400);
$candy-app-warn: mat-palette($mat-red);

$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);

@include angular-material-theme($candy-app-theme);
اعداد و ارقامی را که در اینجا ملاحظه می‌کنید، در سیستم رنگ‌های طراحی متریال به darker hue و lighter hue تفسیر می‌شوند. همچنین امکان سفارشی سازی تایپوگرافی آن نیز وجود دارد.
ذکر جعبه رنگ اخطار در اینجا اختیاری است و اگر ذکر نشود به قرمز تنظیم خواهد شد.

ایجاد یک قالب سفارشی جدید Angular Material

برای ایجاد یک قالب سفارشی نیاز است از فایل‌های sass استفاده کرد. بنابراین بهترین روش ایجاد برنامه‌های Angular Material در ابتدای کار، ذکر صریح نوع style مورد استفاده به sass است:
 ng new MyProjectName --style=sass
اگر اینکار را انجام نداده‌ایم و حالت پیش‌فرض پروژه همان css است، مهم نیست. می‌توان فایل قالب سفارشی را در یک فایل با پسوند custom.theme.scss نیز در پوشه‌ی src قرار داد و سپس آن‌را در فایل angular.json مشخص کرد تا به صورت css کامپایل شده و مورد استفاده قرار گیرد:
"styles": [
   "node_modules/material-design-icons/iconfont/material-icons.css",
   "src/styles.css",
   "src/custom.theme.scss"
],
بدیهی است اگر از ابتدا style=sass را تنظیم کرده بودیم، نیازی به ایجاد این فایل اضافی نبود و همان styles.scss اصلی را می‌شد ویرایش کرد و در این حالت فایل angular.json بدون تغییر باقی می‌ماند.
پس از افزودن و تنظیم فایل custom.theme.scss، به فایل styles.css مراجعه کرده و قالب فعلی را به صورت comment در می‌آوریم:
/* @import "~@angular/material/prebuilt-themes/indigo-pink.css"; */

body {
  margin: 0;
}
سپس فایل src\custom.theme.scss را به صورت زیر تکمیل می‌کنیم:
@import '~@angular/material/theming';
@include mat-core();

$my-app-primary: mat-palette($mat-blue-grey);
$my-app-accent:  mat-palette($mat-pink, 500, 900, A100);
$my-app-warn:    mat-palette($mat-deep-orange);

$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

@include angular-material-theme($my-app-theme);

.alternate-theme {
  $alternate-primary: mat-palette($mat-light-blue);
  $alternate-accent:  mat-palette($mat-yellow, 400);

  $alternate-theme: mat-light-theme($alternate-primary, $alternate-accent);

  @include angular-material-theme($alternate-theme);
}
که در اینجا شامل مراحل import فایل‌های پایه‌ی Angular Material، تعریف جعبه رنگ جدید، ترکیب آن‌ها و در نهایت include آن‌ها می‌باشد.

در اینجا روش تعریف یک قالب دوم (alternate-theme) را نیز مشاهده می‌کنید. علت تعریف قالب دوم در همین فایل جاری، کاهش حجم نهایی برنامه است. از این جهت که اگر alternate-themeها را در فایل‌های scss دیگری قرار دهیم، مجبور به import تعاریف اولیه‌ی قالب‌های Angular Material در هرکدام به صورت جداگانه‌ای خواهیم بود که حجم قابل ملاحظه‌ای را به خود اختصاص می‌دهند. به همین جهت قالب‌های دیگر را نیز در همینجا به صورت کلاس‌های ثانویه تعریف خواهیم کرد.
 در این حالت روش استفاده‌ی از این قالب ثانویه به صورت زیر می‌باشد:
<mat-card class="alternate-theme">
  Alternate Theme:
  <button mat-raised-button color="primary">
    Primary
  </button>
  <button mat-raised-button color="accent">
    Accent
  </button>
  <button mat-raised-button color="warn">
    Warning
  </button>
</mat-card>

پس از افزودن فایل src\custom.theme.scss به برنامه، اگر آن‌را اجرا کنیم به خروجی زیر خواهیم رسید:




افزودن امکان انتخاب پویای قالب‌ها به برنامه

قصد داریم به منوی برنامه که اکنون گزینه‌ی new contact را به همراه دارد، گزینه‌ی toggle theme را هم جهت تغییر پویای قالب اصلی برنامه اضافه کنیم. به همین جهت فایل toolbar.component.html را گشوده و به صورت زیر تغییر می‌دهیم:
  <mat-menu #menu="matMenu">
    <button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
    <button mat-menu-item (click)="toggleTheme.emit()">Toggle theme</button>
  </mat-menu>
در اینجا دکمه‌ای به منو اضافه شده‌است که سبب صدور رخدادی به والد آن یا همان sidenav خواهد شد. علت اینجا است که تغییر قالب را در sidenav، که در برگیرنده‌ی router-outlet است، می‌توان به کل برنامه اعمال کرد.
بنابراین جهت تبادل اطلاعات بین toolbar و sidenav از یک رخ‌داد استفاده خواهیم کرد. برای این منظور فایل toolbar.component.ts را گشوده و این رخ‌داد را به آن اضافه می‌کنیم:
export class ToolbarComponent implements OnInit {

  @Output() toggleTheme = new EventEmitter<void>();
پس از این تعریف، به sidenav.component.html مراجعه کرده و به این رخ‌داد گوش فرا می‌دهیم:
<app-toolbar (toggleTheme)="toggleTheme()" (toggleSidenav)="sidenav.toggle()"></app-toolbar>
متد toggleTheme را نیز به صورت زیر به sidenav.component.ts اضافه می‌کنیم:
export class SidenavComponent {

  isAlternateTheme = false;

  toggleTheme() {
    this.isAlternateTheme = !this.isAlternateTheme;
  }
}
در اینجا یک خاصیت عمومی boolean را با کلیک بر روی گزینه‌ی منوی Toggle theme، به true و یا false تنظیم می‌کنیم. اکنون از این مقدار جهت تغییر css قالب sidenav استفاده خواهیم کرد:
<mat-sidenav-container fxLayout="row" class="app-sidenav-container" fxFill 
   [class.alternate-theme]="isAlternateTheme">
به این ترتیب اگر مقدار isAlternateTheme مساوی true باشد، کلاس alternate-theme به قالب sidenav به صورت پویا اعمال خواهد شد و برعکس. در تصویر زیر نمونه‌ای از تغییر پویای قالب برنامه را مشاهده می‌کنید:




افزودن پشتیبانی از راست به چپ به قالب برنامه

اگر به mat-sidenav-container ویژگی dir=rtl را اضافه کنیم، قالب برنامه راست به چپ خواهد شد. در ادامه می‌خواهیم شبیه به حالت تغییر پویای قالب سایت، گزینه‌ای را به منوی برنامه جهت تغییر جهت برنامه نیز اضافه کنیم. برای این منظور به قالب toolbar.component.html مراجعه کرده و گزینه‌ی Toggle dir را به آن اضافه می‌کنیم:
  <mat-menu #menu="matMenu">
    <button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
    <button mat-menu-item (click)="toggleTheme.emit()">Toggle theme</button>
    <button mat-menu-item (click)="toggleDir.emit()">Toggle dir</button>
  </mat-menu>
سپس این رخ‌داد را که قرار است در نهایت به sidenav منتقل شود، به صورت زیر به toolbar.component.ts اضافه می‌کنیم:
export class ToolbarComponent implements OnInit {

  @Output() toggleDir = new EventEmitter<void>();
اکنون در sidenav.component.html به این ر‌خ‌داد گوش فرا خواهیم داد:
<app-toolbar (toggleDir)="toggleDir()" 
(toggleTheme)="toggleTheme()" 
(toggleSidenav)="sidenav.toggle()"></app-toolbar>
متد toggleDir در sidenav.component.ts به صورت زیر پیاده سازی می‌شود:
export class SidenavComponent implements OnInit, OnDestroy {

  dir = "ltr";

  toggleDir() {
    this.dir = this.dir === "ltr" ? "rtl" : "ltr";
  }
}
و در نهایت این جهت را به mat-sidenav-container در فایل sidenav.component.html اعمال می‌کنیم:
<mat-sidenav-container fxLayout="row" class="app-sidenav-container" fxFill [dir]="dir"
  [class.alternate-theme]="isAlternateTheme">
در تصویر زیر نمونه‌ای از تغییر پویای جهت برنامه را مشاهده می‌کنید:




کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-06.zip
برای اجرای آن:
الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
مطالب
افزودن یک DataType جدید برای نگه‌داری تاریخ خورشیدی - 2
پیش از هرچیز به شما پیش‌نهاد می‌کنم؛ بار دیگر کد سی‌شارپ درس نخست را در پروژه‌ی خود کپی کنید و سپس Publish را بزنید. پس از ارسال آن مطلب، تغییراتی در جهت بهینه‌سازی کد دادم که به نظرم بهتر است شما نیز در پروژه‌ی خود به کار برید.

چرا از این نوع داده استفاده کنیم؟
نخستین پرسشی که ممکن است برای شما پیش بیاید این است که چرا بهتر است از این نوع داده استفاده کنیم. برای پاسخ به این پرسش باید راه‌کارهای گذشته را بررسی کنیم. معمولاً طراحان پایگاه داده‌ها برای استفاده از تاریخ خورشیدی، زمان را به صورت میلادی ثبت می‌کنند؛ سپس با یک scalar-valued function زمان درج شده را به خورشیدی تبدیل می‌کنند. در این صورت می‌توان با یک تابع کوچک دیگر بخش مربوط به ساعت را نیز از همان ستون به دست آورد. در این صورت می‌توانیم از کلیه‌ی متدهای مربوط به DateTime در SQL از جمله افزایش و کاهش و تفاضل دو تاریخ بهره برد. برخی دیگر از طراحان، ستونی از نوع (char(10 در نظر می‌گیرند و تاریخ خورشیدی را به صورت ده‌کاراکتری در آن ذخیره می‌کنند. این روش هرچند نیاز به تبدیل به خورشیدی را ندارد ولی کلیه‌ی مزایایی که در استفاده از DateTime به آن‌ها دسترسی داریم از دست می‌دهیم. افزون بر این جهت نگه‌داری زمان باید یک فیلد دیگر از نوع کاراکتری و یا در نگارش‌های نوین‌تر از نوع time تعریف کنیم. برخی دیگر از هر دو را در کنار هم استفاده می‌کنند و در واقع جهت سرعت بالاتر نمایش و بررسی داده‌ها از طریق محیط SQL Server از فیلد کاراکتری تاریخ خورشیدی و برای مقایسه و بدست آوردن ساعت از فیلد نوع DateTime استفاده می‌کنند.

از نظر فضای اشغال‌شده نوع DataTime، هشت بایت، smalldatetime (در صورت استفاده) 4 بایت و فیلد 10 کاراکتری تاریخ 10 بایت فضا اشغال می‌کند در صورتی که نوع JalaliDate با درنظر گرفتن همه‌ی مزایای انواع داده‌ی استفاده‌شده برای تاریخ، فقط 8 بایت فضا اشغال می‌کند. با استفاده از این نوع به راحتی داده‌ی تاریخ را بر اساس تقویم ایرانی اعتبارسنجی می‌کنید و بخش‌های مختلف زمان از سال تا ثانیه را با یک متد به دست می‌آورید. می‌توانید به راحتی به تاریخ خود زمانی را بیفزایید یا بکاهید و در گزارش‌ها بدون نگرانی از تبدیل درست استفاده کنید. چون کدباز است می‌توانید با کمی حوصله امکانات دیگر مد نظر خود را به آن بیفزایید و از آن در SQL بهره ببرید.

چگونه این نوع داده را حذف کنم!؟
شما می‌توانید به سادگی نوع داده‌ی ایجادشده توسط CLR را در مسیر زیر بیابید و اقدام به حذف آن نمایید:

همان‌طور که مشاهده می‌شود؛ حتی نوع داده‌ی سیستمی hierarchyid که جهت ساختار سلسله‌مراتبی مانند چارت سازمانی یا درخت تجهیزات استفاده می‌شود؛ نیز یک نوع داده‌ی CLR است.

آیا راه دیگری نیز برای افزودن این نوع داده به SQL به جز Publish کردن وجود دارد؟
مانند بسیاری دیگر از گونه‌های پروژه، در اینجا نیز شما یک فایل DLL خواهید داشت. این فایل برپایه‌ی تنظیماتی که شما در قسمت Properties پروژه‌ی خود انجام می‌دهید ساخته می‌شود. پس از تغییر مسیر فایل DLL در دستور زیر توسط یک New Query از Database خود، آن را اجرا کنید:

CREATE ASSEMBLY JalaliDate
FROM 'F:\prgJalaliDate.dll' 
WITH PERMISSION_SET = SAFE;
هم‌چنین در صورت ویرایش‌های دوباره پروژه از دستور زیر استفاده کنید:
ALTER ASSEMBLY JalaliDate
FROM 'F:\prgJalaliDate.dll'
با استفاده از دستورهای زیر می‌توانید از چگونگی درج فایل‌های افزوده شده آگاه شوید:
select * from sys.assemblies
select * from sys.assembly_files
تا اینجا SQL Server، دی‌ال‌ال مربوط به پروژه را شناخته است. برای تعریف نوع داده از دستور زیر بهره ببرید:
CREATE TYPE dbo.JalaliDate 
EXTERNAL NAME JalaliDate.[JalaliDate];
این کار همانند استفاده از گزینه‌ی Publish در Visual Studio است.
هم‌چنین چنان‌چه در SQL Server 2012 از منوی راست‌کلیک پایگاه داده‌ها روی گزینه Tasks و سپس Generate Scripts را انتخاب کنیم، از مشاهده‌ی سند ساخته شده، درخواهیم یافت که حتی دستورهای مربوط به ساخت اسمبلی CLR با تبدیل فایل به کد در Scripts وجود دارد و با اجرای آن در سروری دیگر، انتقال می‌یابد.

GO
/****** Object:  SqlAssembly [prgJalaliDate]    Script Date: 2013/04/30 08:27:00 ب.ظ ******/
CREATE ASSEMBLY [prgJalaliDate]
FROM 0x4D5A90000300000004000000FFFF0000B8000000000000 ..... بقیه‌ی کدها حذف شده
WITH PERMISSION_SET = SAFE

GO
ALTER ASSEMBLY [prgJalaliDate]
ADD FILE FROM 0x4D6963726F736F667420432F432B2B204D534620372E30300D0A1A44530..... بقیه‌ی کدها حذف شده
AS N'prgJalaliDate.pdb'

GO
/****** Object:  UserDefinedType [dbo].[JalaliDate]    Script Date: 2013/04/30 08:27:00 ب.ظ ******/
CREATE TYPE [dbo].[JalaliDate]
EXTERNAL NAME [prgJalaliDate].[JalaliDate]

GO

دنباله دارد ...
پروژه‌ها
Mvc File Manager
به نظر من بهترین روش برای یادگیری برنامه نویسی انجام  یک پروژه واقعی و کاربردی است . (هرچند ساده)  به همین دلیل در حین یادگیری asp.net mvc  تصمیم گرفتم یک فایل منجیر درست کنم که با پیشرفت در یادگیری asp.net mvc اون رو تکمیل کنیم.

فاز اول : نسخه 0.1.1       نسخه 0.1.2
پیاده سازی کنترلر های Browse,Download,Upload, CreateFolder,Delete
هدف از این مرحله یادگیری کنترلر ، ویو ، ویو مدل ، مدل بایندینگ ، روتینگ ، اکشن ریزالت و....
فاز دوم : (انجام شد) نسخه 0.2.3
پیاده سازی کنترلر های Rename,Properties 
استفاده از  WebGrid  در مرورگر فایل
محاسبه حجم پوشه
نسخه اصلاحی آقای وکیلی:دریافت
تغییر در نوع چینش models
افزودن PlUploader برای آپلود فایل
تغییر در Partial به نام _breadCrumb
امکان چند انتخاب هم زمان - و همچنین حذف  چند مورد هم زمان
باز شدن پوشه‌ها و دانلود شدن فایل‌ها با دابل کلیک 
تغییرات مختصر در style
تغییر در ساختار لینک برگشت -> انتقال از model به view و پیاده سازی با jquery به دلیل سهولت بیشتر

فاز سوم:
پیاده سازی اعتبار سنجی کاربران و نقش‌های آنان 
تعریف نقش‌های زیر (ایده خام)
Admin (Full access)
FileManager_Read(readonly access)
FileManager_Write(Creat Folder & upload file)
FileManager_Change(Move & Rename)
FileManager_Delete(Delete file and Folder
فاز چهارم :
پیاده سازی مراحل قبل تحت Ajax
فاز پنجم:
بهینه سازی و تکمیل پروژه
ایده‌های پراکنده :
قابلیت کپی 
قابلیت انتخاب چندتایی برای کپی و حذف و ...
 قابلیت آپلود همزمان چند فایل
چند زبانه بودن 
توسعه اینترفیس (درختواره پوشه‌ها ، ویوهای مختلف جهت نمایش فایل (لیست، آیکون‌های کوچک ، آیکون‌های بزرگ))
نمایش و تغییر دسترسی‌های ویندوز
آپلود فایل فشرده و اکسترکت کردن آن 
جستجوی فایل
.
.
.
مطالب
String.format در جاوا اسکریپت
مقدمه 
با اینکه زبان برنامه نویسی جاوا اسکریپت زبانی بسیار قدرتمند و با امکانات زیاد است، اما فقدان برخی متدهای کمکی پرمصرف در آن در برخی موارد باعث دردسرهایی می‌شود. امکانی برای فرمت‌بندی رشته‌ها یکی از این نیازهای نسبتا پرکاربرد است.
متدی که در این مطلب قصد توضیح پیاده‌سازی آنرا داریم، String.format نام دارد که فرایندی مشابه متد متناظر در دات نت را انجام می‌دهد. هم‌چنین سعی شده است تا نحوه پیاده‌سازی این متد کمکی از ابتدایی‌ترین نمونه‌ها تا نسخه‌های پیشرفته‌تر برای درک بهتر مطلب نشان داده شود.
.
پیاده‌سازی متد String.format
1. در این پیاده‌سازی از اولین فرایندی که ممکن است به ذهن یک برنامه‌نویس خطور کند استفاده شده است. این پیاده‌سازی بسیار ساده به صورت زیر است:
String.format = function () {
  var s = arguments[0];
  for (var i = 0; i < arguments.length - 1; i++) {
    s = s.replace("{" + i + "}", arguments[i + 1]);
  }
  return s;
};

2. پیاده‌سازی مشابهی هم با استفاده از نوع دیگری از حلقه for که تقریبا! مشابه با حلقه foreach در #C است به صورت زیر می‌توان درنظر گرفت:
String.format = function () {
  var s = arguments[0];
  for (var arg in arguments) {
    var i = parseInt(arg);
    s = s.replace("{" + i + "}", arguments[i + 1]);
  }
  return s;
};
در این متدها ابتدا فرمت واردشده توسط کاربر از لیست آرگومان‌های متد خوانده شده و در متغیر s ذخیره می‌شود. سپس درون یک حلقه به ازای هر توکن موجود در رشته فرمت، یک عملیات replace با مقدار متناظر در لیست آرگومان‌های متد انجام می‌شود. نحوه استفاده از این متد نیز به صورت زیر است:
console.log(String.format("{0} is nice!", "donettips.info"));
هر دو متد خروجی یکسانی دارند، به صورت زیر:
donettips.info is nice!
تا اینجا به نظر می‌رسد که عملیات به‌درستی پیش می‌رود. اما اولین و بزرگ‌ترین مشکل در این دو متد نحوه کارکردن متد replace در جاوا اسکریپت است. این متد با این نحوه فراخوانی تنها اولین توکن موجود را یافته و عملیات جایگزینی را برای آن انجام می‌دهد. برای روشن‌تر شدن موضوع به مثال زیر توجه کنید:
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
با اجرای این مثال نتیجه زیر حاصل می‌شود:
donettips.info is very nice! {0} is {1} nice!
همان‌طور که می‌بنید عملیات replace برای سایر توکن‌ها انجام نمی‌شود.

3. برای حل مشکل فوق می‌توان از روش ساده زیر استفاده کرد:
String.format = function () {
  var original = arguments[0],
      replaced;
  for (var i = 0; i < arguments.length - 1; i++) {
    replaced = '';
    while (replaced != original) {
      original = replaced || original;
      replaced = original.replace("{" + i + "}", arguments[i + 1]);
    }
  }
  return replaced;
};
در این روش عملیات replace تا زمانی‌که تغییری در رشته جاری ایجاد نشود ادامه می‌یابد. با استفاده از این متد، خروجی مثال قبل درست و به صورت زیر خواهد بود:
donettips.info is very nice! donettips.info is very nice!

4. راه حل دیگر استفاده از امکانات شی RegExp در دستور replace است. نکته مهم استفاده از modifier کلی یا global (که با حرف g مشخص می‌شود) در شی تولیدی از RegExp است (^ و ^ و ^، برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار می‌شود). برای استفاده از این شی متد ما به صورت زیر تغییر می‌کند:
String.format = function () {
  var s = arguments[0];
  for (var i = 0; i < arguments.length - 1; i++) {
    s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]);
  }
  return s;
};
استفاده از این متد هم نتیجه درستی برای مثال آخر ارائه می‌دهد.

5. روش دیگری که کمی از دو متد قبلی سریع‌تر اجرا می‌شود (به دلیل استفاده از حلقه while) به صورت زیر است:
String.format = function () {
  var s = arguments[0],
      i = arguments.length - 1;
  while (i--) {
    s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]);
  }
  return s;
};
این متد نیز نتیجه مشابهی ارائه می‌کند. حال به مثال زیر توجه کنید:
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
خروجی صحیح مثال فوق باید به صورت زیر باشد:
zero:0 {2}:1 two:2
درصورتی‌که رشته‌ای که دو متد از سه متد آخر (3 و 4) به عنوان خروجی ارائه می‌دهند به‌صورت زیر است:
zero:0 two:1 two:2
برای آخرین متد که ازحلقه while (درواقع با اندیس معکوس) استفاده می‌کند (5) مثالی که خطای مورد بحث را نشان می‌دهد به صورت زیر است:
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
که خروجی اشتباه زیر را برمی‌گرداند:
zero:0 one:1 one:2
درصورتی‌که باید مقدار زیر را برگشت دهد:
zero:0 one:1 {1}:2
دلیل رخدادن این خطا اجرای عملیات replace به صورت جداگانه و کامل برای هر توکن، از اول تا آخر برای رشته‌های replace شده جاری است که کار را خراب می‌کند.

6. برای حل مشکل بالا نیز می‌توان از یکی دیگر از امکانات دستور replace استفاده کرد که به صورت زیر است:
String.format = function () {
  var args = arguments;
  return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; });
};
در اینجا از قابلیت سفارشی‌سازی عملیات جایگزینی در دستور replace استفاده شده است. با استفاده از این ویژگی عملیات replace برای هر توکن جداگانه انجام می‌شود و بنابراین تغییرات اعمالی در حین عملیات تاثیر مستقیمی برای ادامه روند نخواهد گذاشت.
دقت کنید که برای بکاربردن RegExp درون دستور replace به جای تولید یک نمونه از شی RegExp می‌توان عبارت مربوطه را نیز مستقیما بکار برد. در اینجا از عبارتی کلی برای دریافت تمامی توکن‌های با فرمتی به صورت {عدد} استفاده شده است.
متد سفارشی مربوطه نیز شماره ردیف توکن یافته‌شده به همراه خود عبارت یافته‌شده را به عنوان آرگومان ورودی دریافت کرده و مقدار متناظر را از لیست آرگومان‌های متد اصلی پس از تبدیل شماره ردیف توکن به یک عدد، برگشت می‌دهد (در اینجا نیز برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار می‌شود).
برای جلوگیری از تداخل بین آرگومان‌های متد اصلی و متد تهیه‌شده برای سفارشی‌سازی عملیات جایگزینی، در ایتدای متد اصلی، لیست آرگومان‌های آن درون متغیر جداگانه‌ای (args) ذخیره شده است.
با استفاده از این متد خروجی درست نشان داده می‌شود. حال مثال زیر را درنظر بگیرید:
console.log(String.format("{0} is {1} nice!", "donettips.info"));
خروجی این مثال به‌صورت زیر است:
donettips.info is undefined nice!
پیاده‌سازی زیر برای حل این مشکل استفاده می‌شود.

7. برای کنترل بیشتر و رفع خطاهای احتمالی در متد بالا، می‌توان ابتدا از وجود آرگومان مربوطه در متغیر args اطمینان حاصل کرد تا از جایگزینی مقدار undefined در رشته نهایی جلوگیری کرد. مانند نمونه زیر:
String.format = function () {
  var s = arguments[0],
      args = arguments;
  return s.replace(/{(\d+)}/g, function (match, number) {
    var i = parseInt(number);
    return typeof args[i + 1] != 'undefined' ? args[i + 1] : match;
  });
};
با استفاده از این متد جدید خروجی مثال‌های قبل درست خواهد بود.
در فرمت بندی رشته‌ها برای نمایش خود کاراکتر { یا } از تکرار آن‌ها (یعنی {{ یا }}) استفاده می‌شود. اما متد ما تا این لحظه این امکان را ندارد. برای مثال:
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}}  {{{{2}}}}   {2}", "zero", "{2}", "two"));
که خروجی زیر را ارائه می‌دهد:
zero:0 {2}:1 two:2, {zero} {{{2}}}  {{{two}}}   two
.
8. برای پیاده‌سازی امکان اشاره‌شده در بالا می‌توان از کد زیر استفاده کرد:
String.format = function () {
  var s = arguments[0],
      args = arguments;
  return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) {
    if (match == "{{") { return "{"; }
    if (match == "}}") { return "}"; }
    var i = parseInt(number);
    return typeof args[i + 1] != 'undefined'
                              ? args[i + 1]
                              : match;
  });
};
در اینجا با استفاده از یک عبارت RegExp پیچیده‌تر و کنترل تکرار کاراکترهای { و } در متد سفارشی جایگزینی در دستور replace، پیاده‌سازی اولیه این ویژگی ارائه شده است.
این متد خروجی صحیح زیر را برای مثال آخر ارائه می‌دهد:
zero:0 {2}:1 two:2, {0} {{2}}  {{2}}   two

پیاده‌سازی به‌صورت یک خاصیت prototype
تمامی متدهای نشان داده‌شده تا اینجا به‌صورت مستقیم از طریق String.format در دسترس خواهند بود (تعریفی شبیه به متدهای استاتیک در دات نت). درصورتی‌که بخواهیم از این متدها به صورت یک خاصیت prototype شی string استفاده کنیم (چیزی شبیه به متدهای instance در اشیای دات نت) می‌توانیم از تعریف زیر استفاده کنیم:
String.prototype.format = function () {
   ...
}
تنها فرق مهم این پیاده‌سازی این است که رشته مربوط به فرمت وارده در این متد از طریق شی this در دسترس است و بنابراین شماره اندیس آرگومان‌های متد یکی کمتر از متدهای قبلی است که باید مدنظر قرار گیرد. مثلا برای متد آخر خواهیم داشت:
String.prototype.format = function () {
  var s = this.toString(),
      args = arguments;
  return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) {
    if (match == "{{") { return "{"; }
    if (match == "}}") { return "}"; }
    return typeof args[number] != 'undefined'
                              ? args[number]
                              : match;
  });
};

نکته: در تمامی خواص prototype هر شی در جاوا اسکریپت، متغیر this از نوع object است. بنابراین برای جلوگیری از وقوع هر خطا بهتر است ابتدا آن‌را به نوع مناسب تبدیل کرد. مثل استفاده از متد toString در متد فوق که موجب تبدیل آن به رشته می‌شود.

ازآنجاکه نیاز به تغییر اندیس در متد سفارشی عملیات replace وجود ندارد، بنابراین خط مربوط به تبدیل آرگومان number به یک مقدار عددی (با دستور parseInt) حذف شده است و از این متغیر به صورت مستقیم استفاده شده است. در این حالت عملیات تبدیل توسط خود جاوا اسکریپت مدیریت می‌شود که کار را راحت‌تر می‌سازد.
بنابراین متد ما به صورت زیر قابل استفاده است:
console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}}  {{{{2}}}}   {2}".format("zero", "{2}", "two"));

پیاده‌سازی با استفاده از توکن‌های غیرعددی
برای استفاده از توکن‌های غیرعددی می‌توانیم به صورت زیر عمل کنیم:
String.format = function () {
  var s = arguments[0],
      args = arguments[1];
  for (var arg in args) {
    s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]);
  }
  return s;
};
برای حالت prototype نیز داریم:
String.prototype.format = function () {
  var s = this.toString(),
      args = arguments[0];
  for (var arg in args) {
    s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]);
  }
  return s;
};
با استفاده از این دو متد داریم:
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" }));
console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
.
تا اینجا متدهایی نسبتا کامل برای نیازهای عادی برنامه‌نویسی تهیه شده است. البته کار توسعه این متد برای پشتیبانی از امکانات پیشرفته‌تر فرمت‌بندی رشته‌ها می‌تواند ادامه پیدا کند.

کتابخانه‌های موجود
یکی از کامل‌ترین کتابخانه‌های کار با رشته‌ها همان کتابخانه معروف Microsoft Ajax Client Libray است که بیشتر امکانات موجود کار با رشته‌ها در دات نت را در خود دارد. صرفا جهت آشنایی، پیاده‌سازی متد String.format در این کتابخانه در زیر آورده شده است:
String.format = function String$format(format, args) {
  /// <summary locid="M:J#String.format" />
  /// <param name="format" type="String"></param>
  /// <param name="args" parameterArray="true" mayBeNull="true"></param>
  /// <returns type="String"></returns>
//  var e = Function._validateParams(arguments, [
//    { name: "format", type: String },
//    { name: "args", mayBeNull: true, parameterArray: true }
//  ]);
//  if (e) throw e;
  return String._toFormattedString(false, arguments);
};
String._toFormattedString = function String$_toFormattedString(useLocale, args) {
  var result = '';
  var format = args[0];
  for (var i = 0; ; ) {
    var open = format.indexOf('{', i);
    var close = format.indexOf('}', i);
    if ((open < 0) && (close < 0)) {
      result += format.slice(i);
      break;
    }
    if ((close > 0) && ((close < open) || (open < 0))) {
      if (format.charAt(close + 1) !== '}') {
        throw Error.argument('format', Sys.Res.stringFormatBraceMismatch);
      }
      result += format.slice(i, close + 1);
      i = close + 2;
      continue;
    }
    result += format.slice(i, open);
    i = open + 1;
    if (format.charAt(i) === '{') {
      result += '{';
      i++;
      continue;
    }
    if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch);
    var brace = format.substring(i, close);
    var colonIndex = brace.indexOf(':');
    var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1;
    if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid);
    var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1);
    var arg = args[argNumber];
    if (typeof (arg) === "undefined" || arg === null) {
      arg = '';
    }
    if (arg.toFormattedString) {
      result += arg.toFormattedString(argFormat);
    }
    else if (useLocale && arg.localeFormat) {
      result += arg.localeFormat(argFormat);
    }
    else if (arg.format) {
      result += arg.format(argFormat);
    }
    else
      result += arg.toString();
    i = close + 1;
  }
  return result;
}
دقت کنید قسمت ابتدایی این متد که برای بررسی اعتبار آرگومان‌های ورودی است، برای سادگی عملیات کامنت شده است. همان‌طور که می‌بینید این متد پیاده‌سازی نسبتا مفصلی دارد و امکانات بیشتری نیز در اختیار برنامه نویسان قرار می‌دهد. البته سایر متدهای مربوطه بدلیل طولانی بودن در اینجا آورده نشده است. برای مثال امکانات پیشرفته‌تری مثل زیر با استفاده از این کتابخانه در دسترس هستند:
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001));
// result:   100.00, ¤100.00, 10,000.01 %, 100.0001

console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45)));
// result:   02/01/2015, 10:45
آخرین نسخه این کتابخانه از اینجا قابل دریافت است (این متدها درون فایل MicrosoftAjax.debug.js قرار دارند). این کتابخانه دیگر به این صورت و با این نام توسعه داده نمیشود و چند سالی است که تصمیم به توسعه ویژگی‌های جدید آن به صورت پلاگین‌های jQuery گرفته شده است.

کتابخانه دیگری که می‌توان برای عملیات فرمت‌بندی رشته‌ها در جاوا اسکریپت از آن استفاده کرد، کتابخانه معروف jQuery Validation است. این کتابخانه یک متد نسبتا خوب با نام format برای فرمت کردن رشته‌ها دارد. نحوه استفاده از این متد به صورت زیر است:
var template = jQuery.validator.format("{0} is not a valid value");
console.log(template("abc"));
// result: 'abc is not a valid value'

کتابخانه نسبتا کامل دیگری که وجود دارد، با عنوان Stringformat از اینجا قابل دریافت است. برای استفاده از این کتابخانه باید به صورت زیر عمل کرد:
String.format([full format string], [arguments...]);
// or:
[date|number].format([partial format string]);
همان‌طور که می‌بینید این کتابخانه امکانات کامل‌تری نیز دارد. مثال‌های مربوط به این کتابخانه به صورت زیر هستند که توانایی‌های نسبتا کامل آن‌را نشان می‌دهد:
// Object path
String.format("Welcome back, {username}!", 
{ id: 3, username: "JohnDoe" });
// Result: "Welcome back, JohnDoe!"

// Date/time formatting
String.format("The time is now {0:t}.", 
new Date(2009, 5, 1, 13, 22));
// Result: "The time is now 01:22 PM."

// Date/time formatting (without using a full format string)
var d = new Date();
d.format("hh:mm:ss tt");
// Result: "02:28:06 PM"

// Custom number format string
String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111);
// Result: "Please call me at +46 (0) 111-11 11."

// Another custom number format string
String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346);
// Result: "The last year result was -$5,543.35."

// Alignment
String.format("|{0,10:PI=0.00}|", Math.PI);
// Result: "|   PI=3.14|"

// Rounding
String.format("1/3 ~ {0:0.00}", 1/3);
// Result: "1/3 ~ 0.33"

// Boolean values
String.format("{0:true;;false}", 0);
// Result: "false"

// Explicitly specified localization
// (note that you have to include the .js file for used cultures)
msf.setCulture("en-US");
String.format("{0:#,0.0}", 3641.667);
// Result: "3,641.7"

msf.setCulture("sv-SE");
String.format("{0:#,0.0}", 3641.667);
// Result: "3 641,7"

یک کتابخانه دیگر نیز از این آدرس قابل دریافت است. این کتابخانه با عنوان String.format نام‌گذاری شده است. نحوه استفاده از این کتابخانه نیز به صورت زیر است:
//inline arguments
String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value');
//returns: 'some string with first value and second value injected argument {number}'

//single array
String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]);
//returns: 'some string with first value and second value injected using array {number}'

//single object
String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'});
//returns: 'some string with first value and second value injected using {propertyName}'
کتابخانه نسبتا معروف و کامل sprintf نیز در اینجا وجود دارد. این کتابخانه امکانات بسیاری همچون متدهای متناظر در زبان C دارد.

منابع

مطالب
آشنایی و بررسی ابزار Version Manager
در مطلبی با نسخه بندی و چرخه انتشار نرم افزار آشنا شدید. اما کمبود ابزاری کارآمد و حرفه ای در ویژوال استادیو من را بر آن داشت تا افزونه‌ای را تهیه و در دسترس تمامی توسعه دهندگان قرار دهم. پس از مطالعه و بررسی روش‌های نگارش بندی نرم افزار و ابزار‌های موجود، دو روش عمده نسخه بندی نرم افزار وجود دارد که در زیر آورده شده است.
  • نسخه بندی معنایی
  • نسخه بندی استاندارد مایکروسافت
در روش معنایی چهار قسمت نسخه بندی Major.Minor.Build.Revision به صورت زیر افزایش و تغییر می‌یابد:
  1. Revision تا 1000 افزایش می‌یابد
  2. در اجرای بعدی به 0 تنظیم شده و قسمت Build بعلاوه 1 می‌شود.
  3. Build تا 100 افزایش می‌یابد
  4. در اجرای بعدی Build به 0 تنظیم شده و قسمت Minor بعلاوه 1 می‌شود و Revision هم 0 می‌شود.
  5. Minor تا 10 افزایش می‌یابد.
  6. در اجرای بعدی Minor به 0 تنطیم شده، قسمت Major بعلاوه 1 می‌شود و قسمت‌های قبل همه 0 می‌شوند. 
در روش استاندارد ماکروسافت مقادیر قسمت‌های Build و Revision از الگوریتم زیر محاسبه می‌شود.
  1. مقدار Build از مجموع روزهای که از تاریخ پروژه گذشته است بدست می‌آید.
  2. مقدار Revision از مجموع ثانیه‌های که از نیمه شب محلی روز جاری تا زمان اجرای پروژه تقسم بر دو بدست می‌آید.
  3. مقادیر Major و Minor هم با توجه تولید نسخه جدید و تغییرات عمده در نرم افزار بصورت دستی تغییر می‌یابد.
راهنمای نصب و اجرا:
ابتدا افزونه Version Manager را از سایت ویژوال استادیو گالری دریافت و نصب کنید.
سپس از منوی View > Other Windows > Version Manager را انتخاب کنید.

پس از باز شدن پنجره Version Manager پروژه‌های Solution جاری بارگذاری و  Assembly Version و File Version هر پروژه را می‌توانید به راحتی مشاهده و یا تغییر دهید.

اگر بخواهید بصورت خودکار بر اساس شمای انتخاب شده، نسخه نرم افزار را افزایش دهید، شمای نسخه بندی را انتخاب کنید. در دو زمان Build و یا Rebuild پروژه می توان نسخه را افزایش داد.

Semantic Versioning:

  1. گزینه Semantic Versioning را از قسمت Version Style انتخاب کنید.
  2. گزینه Increment Action قسمتی از نگارش که می‌خواهید شمای  نسخه بندی بر روی آن اعمال شود را مشخص می‌کند. که دو گزینه Build یا Revision وجود دارد
  3. گزینه Increment Event زمان رویداد اعمال شمای انتخاب شده را مشخص می‌سازد.
  4. تنظیمات را ذخیره و پروژه خود را Rebuild نمایید.
  5. در پنجره Output نسخه جدید نشان داده می‌شود.

Microsoft DateTime:

  1. گزینه Microsoft DateTime را از قسمت Version Style انتخاب کنید.

  2. تاریخ شروع پروژه بصورت خودکار خوانده و نمایش داده می‌شود یا تاریخ شروع پروژه را انتخاب کنید
  3. با توجه به انتخاب Increment Action نسخه جدید محاسبه می‌شود.
  4. پروژه را Rebuild و نسخه جدید را مشاهده نمایید.

    همچنین از پروژه‌های #C و VB.NET و C++ Managed  پشتیبانی می‌کند

    این ابزار هنوز در نگارش بتا است و ممکن است باگ‌هایی داشته باشد.

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

نظرات مطالب
چگونگی گزارشگیری از Business Objects مانند List توسط StimulSoft
من در حین اجرا خط نهایی Action Result  به خطای زیر برخورد می‌کنم
 Could not load file or assembly 'UsingBusinessObjects.dll' or one of its dependencies. The system cannot find the file specified.

غیر از این فایل ها باید چیز دیگری هم بار اضافه کنم؟
نظرات مطالب
افزونه فارسی به پارسی برای word 2007
چند مورد رو می‌تونید بررسی کنید
- البته نیازی نیست آن 200 و خرده‌ای مگ را دریافت کنید. علت بالا بودن حجم آن این است که 32 بیتی و 64 بیتی و غیره، همه را با هم دارد. به آدرس زیر مراجعه کنید
http://msdn.microsoft.com/en-us/netframework/aa569263.aspx
روی دکمه نصب کلیک کنید تا یک برنامه 2 مگی در اختیار شما قرار دهد. این برنامه قسمت‌های مورد نیاز سیستم شما رو از سایت مایکروسافت دریافت و نصب خواهد کرد. بنابراین حجم کمتری دارد.
- آیا این افزونه به لیست add-in ها در word‌ اضافه شده؟ (شکل سوم در این صفحه)
- event viewer ویندوز را باز کنید. در قسمت run ویندوز تایپ کنید eventvwr.msc و سپس enter (و یا به قسمت administrative tools ویندوز مراجعه کنید این برنامه را مشاهده خواهید کرد). در صفحه باز شده به قسمت applications مراجعه کنید. آیا موردی با source مساوی FarsiToParsi از نوع خطا با آیکون قرمز مشاهده می‌شود؟ اگر بله، لطفا دوبار روی آن کلیک کنید و توضیحات صفحه باز شده را اینجا ارسال کنید. از روی این خطا می‌شود مشکل را بهتر بررسی کرد.

با تشکر
مطالب
کار با چندین نوع بانک اطلاعاتی متفاوت در Entity Framework Core
یکی از مزایای کار با ORMها، امکان تعویض نوع بانک اطلاعاتی برنامه، بدون نیازی به تغییری در کدهای برنامه است. برای مثال فرض کنید می‌خواهید با تغییر رشته‌ی اتصالی برنامه، یکبار از بانک اطلاعاتی SQL Server و بار دیگر از بانک اطلاعاتی کاملا متفاوتی مانند SQLite استفاده کنید. در این مطلب نکات استفاده‌ی از چندین نوع بانک اطلاعاتی متفاوت را در برنامه‌های مبتنی بر EF Core بررسی خواهیم کرد.


هر بانک اطلاعاتی باید Migration و Context خاص خودش را داشته باشد

تامین کننده‌ی بانک‌های اطلاعاتی مختلف، عموما تنظیمات خاص خودشان را داشته و همچنین دستورات SQL متفاوتی را نیز تولید می‌کنند. به همین جهت نمی‌توان از یک تک Context، هم برای SQLite و هم SQL Server استفاده کرد. به علاوه قصد داریم اطلاعات Migrations هر کدام را نیز در یک اسمبلی جداگانه قرار دهیم. در یک چنین حالتی EF نمی‌پذیرد که Context تولید کننده‌ی Migration، در اسمبلی دیگری قرار داشته باشد و باید حتما در همان اسمبلی Migration قرار گیرد. بنابراین ساختار پوشه بندی مثال جاری به صورت زیر خواهد بود:


- در پوشه‌ی EFCoreMultipleDb.DataLayer فقط اینترفیس IUnitOfWork را قرار می‌دهیم. از این جهت که وقتی قرار شد در برنامه چندین Context تعریف شوند، لایه‌ی سرویس برنامه قرار نیست بداند در حال حاضر با کدام Context کار می‌کند. به همین جهت است که تغییر بانک اطلاعاتی برنامه، تغییری را در کدهای اصلی آن ایجاد نخواهد کرد.
- در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLite کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQLite را قرار می‌دهیم.
- در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLServer کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQL Server را قرار می‌دهیم.

برای نمونه ابتدای Context مخصوص SQLite چنین شکلی را دارد:
    public class SQLiteDbContext : DbContext, IUnitOfWork
    {
        public SQLiteDbContext(DbContextOptions options) : base(options)
        { }

        public virtual DbSet<User> Users { set; get; }
و IDesignTimeDbContextFactory مخصوص آن که برای Migrations از آن استفاده می‌شود، به صورت زیر تهیه خواهد شد:
namespace EFCoreMultipleDb.DataLayer.SQLite.Context
{
    public class SQLiteDbContextFactory : IDesignTimeDbContextFactory<SQLiteDbContext>
    {
        public SQLiteDbContext CreateDbContext(string[] args)
        {
            var basePath = Directory.GetCurrentDirectory();
            Console.WriteLine($"Using `{basePath}` as the BasePath");
            var configuration = new ConfigurationBuilder()
                                    .SetBasePath(basePath)
                                    .AddJsonFile("appsettings.json")
                                    .Build();
            var builder = new DbContextOptionsBuilder<SQLiteDbContext>();
            var connectionString = configuration.GetConnectionString("SqliteConnection")
                                                .Replace("|DataDirectory|", Path.Combine(basePath, "wwwroot", "app_data"));
            builder.UseSqlite(connectionString);
            return new SQLiteDbContext(builder.Options);
        }
    }
}
هدف از این فایل، ساده سازی کار تولید اطلاعات Migrations برای EF Core است. به این صورت ساخت new SQLiteDbContext توسط ما صورت خواهد گرفت و دیگر EF Core درگیر جزئیات وهله سازی آن نمی‌شود.


تنظیمات رشته‌های اتصالی بانک‌های اطلاعاتی مختلف

در اینجا محتویات فایل appsettings.json را که در آن تنظیمات رشته‌های اتصالی دو بانک SQL Server LocalDB و همچنین SQLite در آن ذکر شده‌اند، مشاهده می‌کنید:
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SqlServerConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=ASPNETCoreSqlDB;AttachDbFilename=|DataDirectory|\\ASPNETCoreSqlDB.mdf;Integrated Security=True;MultipleActiveResultSets=True;",
    "SqliteConnection": "Data Source=|DataDirectory|\\ASPNETCoreSqliteDB.sqlite",
    "InUseKey": "SqliteConnection"
  }
}
همین رشته‌ی اتصالی است که در SQLiteDbContextFactory مورد استفاده قرار می‌گیرد.
یک کلید InUseKey را هم در اینجا تعریف کرده‌ایم تا مشخص باشد در ابتدای کار برنامه، کلید کدام رشته‌ی اتصالی مورد استفاده قرار گیرد. برای مثال در اینجا کلید رشته‌ی اتصالی SQLite تنظیم شده‌است.
در این تنظیمات یک DataDirectory را نیز مشاهده می‌کنید. مقدار آن در فایل Startup.cs برنامه به صورت زیر بر اساس پوشه‌ی جاری تعیین می‌شود و در نهایت به wwwroot\app_data اشاره خواهد کرد:
var connectionStringKey = Configuration.GetConnectionString("InUseKey");
var connectionString = Configuration.GetConnectionString(connectionStringKey)
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));


دستورات تولید Migrations و به روز رسانی بانک اطلاعاتی

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

برای تولید Migrations، از طریق خط فرمان، به پوشه‌ی اسمبلی مدنظر وارد شده و دستور زیر را اجرا کنید:
For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set mydate=%%c_%%a_%%b)
For /f "tokens=1-2 delims=/:" %%a in ("%TIME: =0%") do (set mytime=%%a%%b)
dotnet build
dotnet ef migrations --startup-project ../EFCoreMultipleDb.Web/ add V%mydate%_%mytime% --context SQLiteDbContext
در اینجا ذکر startup-project و همچنین context برای پروژه‌هایی که context آن‌ها خارج از startup-project است و همچنین بیش از یک context دارند، ضروری‌است. بدیهی است این دستورات را باید یکبار در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLite و یکبار در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLServer اجرا کنید.
دو سطر اول آن، زمان اجرای دستورات را به عنوان نام فایل‌ها تولید می‌کنند.

پس از تولید Migrations، اکنون نوبت به تولید بانک اطلاعاتی و یا به روز رسانی بانک اطلاعاتی موجود است:
dotnet build
dotnet ef --startup-project ../EFCoreMultipleDb.Web/ database update --context SQLServerDbContext
در این مورد نیز ذکر startup-project و همچنین context مدنظر ضروری است.


بدیهی است این رویه را پس از هربار تغییراتی در موجودیت‌های برنامه و یا تنظیمات آن‌ها در Contextهای متناظر، نیاز است مجددا اجرا کنید. البته اجرای اولین دستور اجباری است؛ اما می‌توان دومین دستور را به صورت زیر نیز اجرا کرد:
namespace EFCoreMultipleDb.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            applyPendingMigrations(app);
// ...
        }

        private static void applyPendingMigrations(IApplicationBuilder app)
        {
            var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                var uow = scope.ServiceProvider.GetService<IUnitOfWork>();
                uow.Migrate();
            }
        }
    }
}
متد applyPendingMigrations، کار وهله سازی IUnitOfWork را انجام می‌دهد. سپس متد Migrate آن‌را اجرا می‌کند، تا تمام Migrations تولید شده، اما اعمال نشده‌ی به بانک اطلاعاتی، به صورت خودکار به آن اعمال شوند. متد Migrate نیز به صورت زیر تعریف می‌شود:
namespace EFCoreMultipleDb.DataLayer.SQLite.Context
{
    public class SQLiteDbContext : DbContext, IUnitOfWork
    {
    // ... 

        public void Migrate()
        {
            this.Database.Migrate();
        }
    }
}

مرحله‌ی آخر: انتخاب بانک اطلاعاتی در برنامه‌ی آغازین

پس از این تنظیمات، قسمتی که کار تعریف IUnitOfWork و همچنین DbContext جاری برنامه را انجام می‌دهد، به صورت زیر پیاده سازی می‌شود:
namespace EFCoreMultipleDb.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUsersService, UsersService>();

            var connectionStringKey = Configuration.GetConnectionString("InUseKey");
            var connectionString = Configuration.GetConnectionString(connectionStringKey)
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));
            switch (connectionStringKey)
            {
                case "SqlServerConnection":
                    services.AddScoped<IUnitOfWork, SQLServerDbContext>();
                    services.AddDbContext<SQLServerDbContext>(options =>
                    {
                        options.UseSqlServer(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                    dbOptions.EnableRetryOnFailure();
                                });
                    });
                    break;
                case "SqliteConnection":
                    services.AddScoped<IUnitOfWork, SQLiteDbContext>();
                    services.AddDbContext<SQLiteDbContext>(options =>
                    {
                        options.UseSqlite(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                });
                    });
                    break;
                default:
                    throw new NotImplementedException($"`{connectionStringKey}` is not defined in `appsettings.json` file.");
            }

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }
در اینجا ابتدا مقدار InUseKey از فایل تنظیمات برنامه دریافت می‌شود. بر اساس مقدار آن، رشته‌ی اتصالی مدنظر دریافت شده و سپس یکی از دو حالت SQLite و یا SQLServer انتخاب می‌شوند. برای مثال اگر Sqlite انتخاب شده باشد، IUnitOfWork به SQLiteDbContext تنظیم می‌شود. به این ترتیب لایه‌ی سرویس برنامه که با IUnitOfWork کار می‌کند، به صورت خودکار وهله‌ای از SQLiteDbContext را دریافت خواهد کرد.


آزمایش برنامه

ابتدا کدهای کامل این مطلب را از اینجا دریافت کنید: EFCoreMultipleDb.zip
سپس آن‌را اجرا نمائید. چنین تصویری را مشاهده خواهید کرد:


اکنون برنامه را بسته و سپس فایل appsettings.json را جهت تغییر مقدار InUseKey به کلید SqlServerConnection ویرایش کنید:
{
  "ConnectionStrings": {
    // …
    "InUseKey": "SqlServerConnection"
  }
}
اینبار اگر مجددا برنامه را اجرا کنید، چنین خروجی قابل مشاهده‌است:


مقدار username، در contextهای هر کدام از این بانک‌های اطلاعاتی، با مقدار متفاوتی به عنوان اطلاعات اولیه‌ی آن ثبت شده‌است. سرویسی هم که اطلاعات آن‌را تامین می‌کند، به صورت زیر تعریف شده‌است:
namespace EFCoreMultipleDb.Services
{
    public interface IUsersService
    {
        Task<User> FindUserAsync(int userId);
    }

    public class UsersService : IUsersService
    {
        private readonly IUnitOfWork _uow;
        private readonly DbSet<User> _users;

        public UsersService(IUnitOfWork uow)
        {
            _uow = uow;
            _users = _uow.Set<User>();
        }

        public Task<User> FindUserAsync(int userId)
        {
            return _users.FindAsync(userId);
        }
    }
}
همانطور که مشاهده می‌کنید، با تغییر context برنامه، هیچ نیازی به تغییر کدهای UsersService نیست؛ چون اساسا این سرویس نمی‌داند که IUnitOfWork چگونه تامین می‌شود.
مطالب
آشنایی با Feature Toggle - بخش دوم
در بخش اول آشنایی با Feature Toggle، با مفهوم Feature Toggle آشنا شدیم و در بخش پایانی مقاله، به معرفی یکی از کتابخانه‌های نوشته شده توسط مایکروسافت پرداختیم.
در این مقاله به صورت کاربردی‌تر به استفاده از کتابخانه‌ی مورد استفاده می‌پردازیم. برای ادامه نیاز هست بسته‌ی زیر را که مخصوص برنامه‌های مبتنی بر ASP.NET CORE است نصب نمایید :
Install-package Microsoft.FeatureManagement.AspNetCore

فرض کنید یک قابلیت را تحت عنوان Chat پیاده سازی کرده و با توجه به تکنولوژی‌هایی که استفاده کرده‌اید، فقط با مرورگر کروم سازگار هست و شما باید این قابلیت را فقط برای کاربرانی که مروگر کروم دارند، فعال نمایید؛ در غیر اینصورت غیرفعال و در دسترس کاربران نباشد. برای این منظور فرض میکنیم کنترلر زیر مسئول تمام کارهای مربوط به قابلیت چت می‌باشد :
[FeatureGate("chat")]
public class ChatController : Controller
{
      public IActionResult Index()
      {
          // do sth
      }
}
همانطور که در کد بالا قابل مشاهده می‌باشد ، کنترلر با یک Attribute مزین شده‌است که از Attribute‌های توکار کتابخانه می‌باشد. با استفاده از این ویژگی می‌توانیم یک کنترلر و یا اکشن متد را کلا از دسترس خارج کنیم (اگر مقدار این قابلیت در appsetting.json غیرفعال باشد).
اگر درخواستی به کنترلر Chat ارسال شود و قابلیت چت در فایل appsetting.json غیرفعال باشد (طبق روش‌هایی که در مقاله قبل توضیح داده شد) کاربر با خطای 404 مواجه خواهد شد.
میتوان به FeatuteGate اسم چندین قابلیت را داد و اگر همه‌ی آنها فعال باشند، کنترلر/اکشن در دسترس خواهد بود؛ در غیر اینصورت خطای 404 دریافت می‌شود.
[FeatureGate("feature1", "feature2")]
public class ChatController : Controller
 {
        public IActionResult Index()
        {
            // do sth
        }
 }
  "FeatureManagement": {
    "feature1": true,
    "feature2": false
  },

 برای حالتیکه نیاز هست اسم چندین قابلیت را به FeatureGate بدهیم، میتوانیم تعیین کنیم که آیا همه‌ی قابلیت‌ها باید فعال باشند تا کنترلر/ اکشن در دسترس باشد یا خیر؟ برای این منظور یک Enum توکار، به اسم RequirementType به همراه این کتابخانه وجود دارد که کار آن And/OR است:
public enum RequirementType
    {
        //
        // Summary:
        //     The enabled state will be attained if any feature in the set is enabled.
        Any = 0,
        //
        // Summary:
        //     The enabled state will be attained if all features in the set are enabled.
        All = 1
    }
همانطور که از توضیحات آن قابل تشخیص است، در زمان استفاده از FeatureGate میتوانیم با استفاده از این enum مشخص کنیم که اگر فقط یکی از قابلیت‌ها فعال بود، کنترلر/اکشن موردنظر فعال و در دسترس باشد، در غیر اینصورت از دسترس خارج شود و تمامی درخواست‌ها را با خطای 404 پاسخ دهد.
نمونه‌ای از استفاده از این enum به صورت زیر است:
 [FeatureGate(RequirementType.Any,"feature1", "feature2","feature3")]
 public class ChatController : Controller
 {
        public IActionResult Index()
        {
            // do sth
        }
 }

تگ <feature>
تا اینجا موفق شدیم یک کنترلر و یا اکشن متد را غیرفعال و از دسترس خارج نماییم. فرض کنید قابلیت چت بنا بر تنظیمات انجام شده، غیرفعال می‌باشد، منتها در منوی سایت همچنان لینک آن در حال نمایش است و کاربران میتوانند لینک را کیک کنند (و در نتیجه با خطای 404 مواجه می‌شوند). برای غیر فعال کردن المان‌هایی (تگ) مربوط به یک قابلیت، می‌توانیم از tag helper مربوطه به صورت زیر استفاده نماییم :
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore // put this line in _ViewImports

<feature name="feature1,feature2,feature3">
  <li>
        <a asp-area="" asp-controller="Chat" asp-action="index">Stay in contact</a>
    </li>
</feature>
 لازم به ذکر هست اینجا هم می‌توان با مقداردهی خصویت requirement با یکی از مقدارهای Any و یا All، مشخص نماییم به صورت And اجرا شود یا خیر.

نوشتن Handler سفارشی
همانطور که در بالا هم بیان شد، اگر یک قابلیت به هر دلیلی غیرفعال باشد، کاربران با خطای 404 مواجه خواهند شد. اگر نیاز داشتید کاربر را به صفحه‌ی دیگری هدایت کنید و یا Status Code بهتری را برگردانید، میتوانید این‌کار را با پیاده سازی یک هندلر سفارشی که اینترفیس IDisabledFeaturesHandler را پیاده سازی میکند، انجام دهید. در زیر یک نمونه پیاده سازی شده را مشاهده می‌کنید:
public class RedirectDisabledFeatureHandler : IDisabledFeaturesHandler
    {
        public Task HandleDisabledFeatures(IEnumerable<string> features, ActionExecutingContext context)
        {
            context.Result = new RedirectResult("url");
            return Task.CompletedTask;
        }
    }
و سپس نیاز هست تا این هندلر را به صورت زیر ثبت نماییم :
  public void ConfigureServices(IServiceCollection services)
   {
            services.AddFeatureManagement().UseDisabledFeaturesHandler(new RedirectDisabledFeatureHandler()); ;
    }
نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: روش سازگار کردن اعتبارسنجی فرم‌های استاندارد Blazor با کلاس‌های CSS بوت استرپ 4 و 5
زمانیکه از EditForm و کامپوننت‌های توکار Blazor استفاده می‌کنیم، اگر کامپوننتی در وضعیت اعتبارسنجی شده قرار داشته باشد، با کلاس valid:
class="modified valid form-control"
و اگر در وضعیت شکست اعتبارسنجی قرارگیرد، با کلاس invalid مزین می‌شود:
class="modified invalid form-control"
اما برای یکپارچه سازی آن با کلاس‌های اعتبارسنجی بوت استرپ 4 و 5، نیاز است از کلاس‌های is-valid و is-invalid بجای valid و invalid استفاده شود. این تغییر نیاز به استفاده از «یک نکته‌ی تکمیلی: امکان اعتبارسنجی دستی فرم‌ها در Blazor» را دارد؛ چون با دسترسی به EditContext است که می‌توان CSS provider آن‌را سفارشی سازی کرد؛ برای مثال:
EditContext = new EditContext(Model);
EditContext.SetFieldCssClassProvider(new BootstrapFieldCssClassProvider());
که سفارشی ساز مخصوص بوت استرپ، به صورت زیر قابل تعریف است:
using System;
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorComponents
{
    /// <summary>
    /// Supplies CSS class names for form fields to represent their validation state or other state information from an EditContext.
    /// </summary>
    public class BootstrapFieldCssClassProvider : FieldCssClassProvider
    {
        /// <summary>
        /// Gets a string that indicates the status of the specified field as a CSS class.
        /// </summary>
        public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
        {
            if (editContext == null)
            {
                throw new ArgumentNullException(nameof(editContext));
            }

            var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "is-valid" : "is-invalid";
            }
            return isValid ? "" : "is-invalid";
        }
    }
}
در اینجا در ابتدا بررسی می‌شود که آیا فیلد جاری معتبر است یا خیر و همچنین آیا ویرایش شده‌است یا خیر؟ سپس بر این اساس، کلاس‌های ویژه‌ی بوت استرپ، بجای کلاس‌های پیش‌فرض ارائه خواهند شد.