مطالب
جستجوی غیر حساس به بزرگی و کوچکی حروف در SQLite توسط EF-Core
اگر پیشتر با SQL Server کار کرده باشید، حالت پیش‌فرض حساس بودن جستجوی SQLite به بزرگی و کوچکی حروف را انتظار نخواهید داشت؛ تا زمانیکه هنگام لاگین، اکانت Admin بتواند وارد سیستم شود و اکانت admin خیر. در این مطلب نحوه‌ی انجام تنظیمات مرتبط با جستجوی غیرحساس به بزرگی و کوچکی حروف را در SQLite و EF-Core، بررسی خواهیم کرد.


Collations و حساسیت به بزرگی و کوچکی حروف

پردازش متون در بانک‌های اطلاعاتی پیچیده‌است و عموما فراتر است از انتظارات ساده‌ی اولیه، خصوصا اینکه بانک‌های اطلاعاتی متفاوت، روش‌های متفاوتی را هم در این زمینه بکار می‌گیرند. برای مثال بانک‌های اطلاعاتی مانند SQLite و PostgreSQL به صورت پیش‌فرض به بزرگی و کوچکی حروف حساس هستند، اما بانک‌هایی مانند SQL Server و MySQL خیر. همچنین این حساسیت، بر روی کارآیی جستجو نیز بسیار تاثیر گذار است. برای مثال می‌توان از متدهایی مانند string.ToLower برای انجام جستجوهای غیرحساس به بزرگی و کوچکی حروف استفاده کرد، اما بکارگیری آن‌ها بلافاصله استفاده‌ی از ایندکس‌ها را غیرفعال می‌کنند و سبب انجام جستجوهایی بسیار کند خواهند شد.

برای مواجه شدن با یک چنین حالت‌هایی بدون افت کارآیی برنامه، مفهوم پایه‌ای به نام collation در بانک‌های اطلاعاتی ارائه شده‌است که مشخص می‌کند مقادیر رشته‌ای چگونه باید مرتب شده یا مقایسه شوند. برای مثال یک collation غیرحساس به بزرگی و کوچکی حروف، در حین مقایسه‌ی رشته‌ها، به بزرگی و کوچکی حروف بکار گرفته شده‌ی در عبارت اهمیتی نمی‌دهد. همچنین باید دقت داشت که یک چنین مقایسه‌ای بسته به فرهنگ بکار گرفته شده، می‌توان متفاوت باشد؛ برای مثال در زبان ترکی، i و I حروف متفاوتی هستند و نباید در حین مقایسه‌ی غیرحساس به بزرگی و کوچکی حروف، یکی در نظر گرفته شوند. به همین جهت تعداد قابل ملاحظه‌ای case-insensitive collations از پیش تعریف شده، بسته به فرهنگ‌های مختلف وجود دارند؛ نمونه‌ی دیگر آن فرهنگ آلمانی است که در آن عموما ä و ae را یکسان درنظر می‌گیرند. به علاوه collation بر روی نحوه‌ی مرتب سازی حروف نیز تاثیر دارد؛ برای مثال در فرهنگ آلمانی، ä پس از a قرار می‌گیرد، اما در فرهنگ سوئدی در انتهای حروف الفباء واقع شده‌است.

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


روش تعیین collation در سطح بانک اطلاعاتی

در اغلب بانک‌های اطلاعاتی، یک collation پیش‌فرض، در سطح کل آن‌ها تعریف شده‌است و بر روی تمام پردازش‌های متنی و تمام ستون‌های جداول تاثیرگذار است. برای مثال حالت پیش‌فرض collation در SQL Server (اگر هیچ تنظیم پیش‌فرض دیگری در حین تعریف بانک اطلاعاتی وجود نداشته باشد) مقدار SQL_Latin1_General_CP1_CI_AS است. این مقدار یک collation غیرحساس به بزرگی و کوچکی حروف است. مقدار CI آن به معنای case-insensitive و AS آن مخفف accent-sensitive (حساس به لهجه) است.
از زمان EF-Core 5x، امکان کار با collations و تعیین آن‌ها نیز میسر شده‌است. برای مثال برای تعیین یک چنین collation ای در سطح بانک اطلاعاتی می‌توان به صورت زیر در متد OnModelCreating عمل کرد:
modelBuilder.UseCollation("SQL_Latin1_General_CP1_CS_AS");
البته بهتر است یک چنین تنظیماتی را از ابتدای کار و پیش از تعریف و ایجاد بانک اطلاعاتی درنظر داشت؛ چون تغییر collation پس از ایجاد بانک اطلاعاتی، تداخلات زیادی را ایجاد می‌کند. برای مثال SQL Server حتی اجازه‌ی join دو جدول با collation متفاوت را نمی‌دهد؛ هرچند راه‌حل‌هایی برای آن وجود دارد اما بهتر است این مقدار یکبار و آن هم در ابتدای کار تعیین شود.


روش تعیین collation در سطح جداول بانک اطلاعاتی

Collations را همچنین می‌توان در سطح جداول نیز مشخص کرد تا بتوان در صورت نیاز، collation پیش‌فرض بانک اطلاعاتی را بازنویسی نمود. برای مثال شاید نیاز داشته باشید جداولی case-insensitive و تعدادی دیگر case-sensitive باشند.
در EF-Core 5x به بعد، روش انجام اینکار به صورت زیر است:
modelBuilder.Entity<Customer>().Property(c => c.Name)
   .UseCollation("SQL_Latin1_General_CP1_CI_AS");
در اینجا collation ستون Name جدول Customer، به صورت صریحی مشخص شده‌است.


روش تعیین پویای collation در سطح کوئری‌های بانک اطلاعاتی

یک جدول می‌تواند collation پیش‌فرضی داشته باشد، اما در حین کوئری گرفتن، collation آن‌را به صورت موقت و پویا تغییر داد. برای مثال بجای استفاده از متد ToLower که سبب می‌شود از ایندکس‌ها استفاده نشود، می‌توان از collation خاصی در حین کوئری گرفتن استفاده کرد:
var customers = context.Customers
   .Where(c => EF.Functions.Collate(c.Name, "SQL_Latin1_General_CP1_CS_AS") == "John").ToList();
البته باید دقت داشت که تعیین collation در این حالت نیز سبب می‌شود تا از ایندکس‌ها استفاده نشود. از این جهت که ایندکس‌ها به صورت پیش‌فرض بر اساس collation یک ستون یا جدول تهیه می‌شوند. هرچند بانک اطلاعاتی‌هایی مانند PostgreSQL, Sqlite امکان تعیین collation را در حین تهیه‌ی ایندکس‌ها نیز میسر می‌کنند. برای مثال می‌توان ایندکس‌های حساس و غیر حساس به بزرگی و کوچکی حروف را در این بانک‌های اطلاعاتی، به صورت جداگانه‌ای تعریف کرد تا در صورت نیاز، از آن‌ها استفاده شود.

یک نکته: هر چند کوئری‌های سمت دات نت به صورت پیش‌فرض حساس به بزرگی و کوچکی حروف هستند (مانند s1 == s2)، اما EF-Core هیچ تلاشی را برای انجام یک کوئری case-sensitive در سمت بانک اطلاعاتی انجام نخواهد داد و == سی شارپ به صورت مستقیمی به تساوی SQL ترجمه می‌شود که بسته به collation جاری، می‌تواند یا حتی نمی‌تواند حساس به بزرگی و کوچکی حروف باشد. بنابراین حالت پیش‌فرض کوئری‌های EF-Core استفاده از collation پیش‌فرض ستون‌ها است. هرچند متدهایی مانند string.Equals امکان مقایسه‌ی غیرحساس به بزرگی و کوچکی حروف را در دات نت میسر می‌کنند (چون به همراه پارامتر StringComparison هستند)، اما EF-Core سعی در ترجمه‌ی آن‌ها به SQL نخواهد کرد و تعیین صریح collation توسط متد EF.Functions.Collate به شما واگذار شد‌ه‌است.
 

تعیین collation غیرحساس به بزرگی و کوچکی حروف در SQLite، توسط EF-Core

با توجه به توضیحات فوق، متد زیر، collation ویژه‌ی nocase را که در SQLite به معنای collation غیرحساس به بزرگی و کوچکی حروف است، به کل بانک اطلاعاتی و همچنین تمام ستون‌های رشته‌ای آن به صورت خودکار اعمال می‌کند:
public static void SetCaseInsensitiveSearchesForSQLite(this ModelBuilder modelBuilder)
{
    if (modelBuilder == null)
    {
        throw new ArgumentNullException(nameof(modelBuilder));
    }

    modelBuilder.UseCollation("NOCASE");
    foreach (var property in modelBuilder.Model.GetEntityTypes()
                                            .SelectMany(t => t.GetProperties())
                                            .Where(p => p.ClrType == typeof(string)))
    {
        property.SetCollation("NOCASE");
    }
}
سپس روش استفاده‌ی از آن به صورت زیر خواهد بود:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    if (modelBuilder == null)
    {
        throw new ArgumentNullException(nameof(modelBuilder));
    }

    modelBuilder.SetCaseInsensitiveSearchesForSQLite();
}
مطالب
MEF و الگوی Singleton

در مورد معرفی مقدماتی MEF می‌توانید به این مطلب مراجعه کنید و در مورد الگوی Singleton به اینجا.


کاربردهای الگوی Singleton عموما به شرح زیر هستند:
1) فراهم آوردن دسترسی ساده و عمومی به DAL (لایه دسترسی به داده‌ها)
2) دسترسی عمومی به امکانات ثبت وقایع سیستم در برنامه logging -
3) دسترسی عمومی به تنظیمات برنامه
و موارد مشابهی از این دست به صورتیکه تنها یک روش دسترسی به این اطلاعات وجود داشته باشد و تنها یک وهله از این شیء در حافظه قرار گیرد.

با استفاده از امکانات MEF دیگر نیازی به نوشتن کدهای ویژه تولید کلاس‌های Singleton نمی‌باشد زیرا این چارچوب کاری دو نوع روش وهله سازی از اشیاء (PartCreationPolicy) را پشتیبانی می‌کند: Shared و NonShared . حالت Shared دقیقا همان نام دیگر الگوی Singleton است. البته لازم به ذکر است که حالت Shared ، حالت پیش فرض تولید وهله‌ها بوده و نیازی به ذکر صریح آن همانند ویژگی زیر نیست:
[PartCreationPolicy(CreationPolicy.Shared)]

مثال:
فرض کنید قرار است از کلاس زیر تنها یک وهله بین صفحات یک برنامه‌ی Silverlight توزیع شود. با استفاده از ویژگی‌ Export به MEF اعلام کرده‌ایم که قرار است سرویسی را ارائه دهیم :

using System;
using System.ComponentModel.Composition;

namespace SlMefTest
{
[Export]
public class WebServiceData
{
public int Result { set; get; }

public WebServiceData()
{
var rnd = new Random();
Result = rnd.Next();
}
}

}
اکنون برای اثبات اینکه تنها یک وهله از این کلاس در اختیار صفحات مختلف قرار خواهد گرفت، یک User control جدید را به همراه یک دکمه که مقدار Result را نمایش می‌دهد به برنامه اضافه خواهیم کرد. دکمه‌ی دیگری را نیز به همین منظور به صفحه‌ی اصلی برنامه اضافه می‌کنیم.
کدهای صفحه اصلی برنامه (که از یک دکمه و یک Stack panel جهت نمایش محتوای یوزر کنترل تشکیل شده) به شرح بعد هستند:
<UserControl x:Class="SlMefTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<StackPanel>
<Button Content="MainPageButton" Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0" Name="button1"
VerticalAlignment="Top" Width="98" Click="button1_Click" />
<StackPanel Name="panel1" Margin="5"/>
</StackPanel>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class MainPage
{
[Import]
public WebServiceData Data { set; get; }

public MainPage()
{
InitializeComponent();
this.Loaded += mainPageLoaded;
}

void mainPageLoaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
panel1.Children.Add(new SilverlightControl1());
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
با استفاده از ویژگی Import به MEF اعلام می‌کنیم که به اطلاعاتی از نوع شیء WebServiceData نیاز داریم و توسط متد CompositionInitializer.SatisfyImports کار وهله سازی و پیوند زدن export و import های همانند صورت می‌گیرد. سپس استفاده‌ی مستقیم از Data.Result مجاز بوده و مقدار آن null نخواهد بود.

کدهای User control ساده اضافه شده به شرح زیر هستند:

<UserControl x:Class="SlMefTest.SilverlightControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<Button Content="UserControlButton"
Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Name="button1"
VerticalAlignment="Top"
Width="125"
Click="button1_Click" />
</Grid>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class SilverlightControl1
{
[Import]
public WebServiceData Data { set; get; }

public SilverlightControl1()
{
InitializeComponent();
this.Loaded += silverlightControl1Loaded;
}

void silverlightControl1Loaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
اکنون قبل از شروع برنامه یک break point را در سازنده‌ی کلاس WebServiceData قرار دهید. سپس برنامه را آغاز نمائید. تنها یکبار این سازنده فراخوانی خواهد شد (هر چند در دو کلاس کار Import اطلاعات WebServiceData صورت گرفته است). همچنین با کلیک بر روی دو دکمه‌ای که اکنون در صفحه‌ی اصلی برنامه ظاهر می‌شوند، فقط یک عدد مشابه نمایش داده می‌شود (با توجه به اینکه اطلاعات هر دکمه در یک وهله‌ی جداگانه قرار دارد؛ یکی متعلق است به صفحه‌ی اصلی و دیگری متعلق است به user control اضافه شده).

مطالب
احراز هویت مبتنی بر فرم در شیرپوینت
از دغدغه‌های همیشگی در راه اندازی پرتال‌های مبتنی بر شیرپوینت سیستم احراز هویت آن است. این سیستم بصورت پیش فرض بر مبنای Windows Authentication است و ناگفته پیداست این نوع احراز هویت تنها در شبکه‌های محلی کاربرد دارد آنهم در صورتی که همه کاربران و سطوح دسترسی، بدرستی در AD تعریف شده باشد و نیز
یک سری مشکلات دیگر که بیشتر به توسعه شیرپوینت در شرکت و انتقال آن و انطباق آن با محیط پروژه برمیگردد.
به عبارت دیگر شما به عنوان یک توسعه دهنده ویا نَصاب(!) شیرپوینت خیلی نباید درگیر سیستم احراز هویت پیش فرض مشتری بشوید. برای اینکار بهترین گزینه استفاده از احراز هویت بر مبنای فرم (Form Based Authentication - FBA) است که برای ما برنامه نویسان Asp.net بسیار آشناست؛ سیستم احراز هویتی خوش دست و فراگیر با قاعده‌های مشخص.

متاسفانه اکثر راهکارهایی که در وب پیرامون راه اندازی FBA در شیرپوینت معرفی شده‌اند دارای اشکالات ریز و درشتی هستند و یا اینکه یک یا چند گام از فرایند را توضیح نداده‌اند و معمولا در پایان یکجای کار لنگ میزند و FBA بخوبی عملیاتی نمیشود.
بر همین اساس بر آن شدم تا با بررسی چندتا از این مقالات موجود و نیز تجربه عملی خودم این راهکارها را ترکیب کنم که نتیجه اش فرایند شش مرحله ای زیر شده است. 
 برای راه اندازی FBA بر روی SharePoint 2010 باید مراحل زیر را به ترتیب انجام داد.
1. ساخت بانک اطلاعاتی FBA بر روی MSSQL
بانک اطلاعاتی FBA ساختار مشخصی از جداول و SP‌ها دارد که تا حد امکان بهتر است در ساختار پیش فرض آن تغییری ایجاد نکنیم. برای ایجاد این بانک کافی است به محل نصب دات نت فریم 2.0 که بصورت پیش فرض در مسیر"C:\Windows\Microsoft.NET\Framework64\v2.0.50727 " قرار دارد  بروید و روی فایل "aspnet_regsql.exe "  کلیک کنید و مراحل نصب را تا آخر پیش بروید.

در ادامه MSSQL را باز کنید و مطئمن شوید که بانک FBA به درستی ایجاد شده باشد.

2.وارد IIS شده و در بخش روت سرور ،توجه شود که حتما این تغییرات در روت سرور اعمال شود تا همه سایت‌ها از آن ارث بری کنند و تنظیمات شما یکسان در همه Web Application‌ها اعمال شود.
این مرحله  Connection String و Role provider و Membership provider مورد نظرتان را اضافه کنید. 

موقع اضافه کردن Provider‌‌ها باید توجه داشت که Application Name برابر "/" قرار داده شود.

 3.به بخش مدیریت شیرپوینت رفته و یک WebApplication جدید ایجاد کنید.

توجه داشت که در بخش Authentication بایدگزینه    Claims Authentication را انتخاب کنید. 

و نیز برای تنظیمات مربوط به FBA باید نام Provider هایی را که در مرحله قبل ایجاده کرده بودید را در این بخش قرار دهید.
اگر پرتال شما بازدید کننده‌های ناشناس(بازدید کنندگانی که عضو نیستند و در سیستم FBA نام کاربری ندارند) را نیز پشتیبانی میکند باید گزینه اول(Allow anonymous) را Yes کنید.
اگر تمایلی ندارید که دیگر احراز هویت مبتنی بر ویندوز فعال باشد تیک Enable Windows Authentication را بردارید.

 

4.حال دوباره سراغ IIS میرویم و روی وب جدیدی که ایجاد کرده ایم کلیک میکنیم و سپس روی آیکون Net Users  کلیک میکنیم

 اگر با پیغامی مبنی بر تعیین Provider پیش فرض مواجه شدیم مقادیر پیش فرض Provider‌ها را برابر با نام Provider هایی که خودمان تعریف کرده ایم قرار میدهیم. 

در ادامه چند کاربر و رول هم به عنوان کاربران اولیه و مدیران پرتال ایجاد میکنیم .

 

5.حال دوباره به بخش مدیریت برمیگردیم و یک Site Collection جدید روی Web Application مورد نظر ایجاد میکنیم.

در اینجا بهتر است مدیر اول را از کاربران ویندوزی انتخاب کرد و مدیر دوم را از بخش کاربران FBA

نکته بی ربط:اگر تمپلتی از قبل برای سایتتان دارید در بخش انتخاب نوع سایت بعد از انتخاب زبان باید به تب سوم(Custom) بروید و تنها گزینه موجود را انتخاب کنید.با این کار در مراحل بعد تمپلت مورد نظرتان را در سایت آپلود میکنید و آن را بر سایت جاری اعمال میکنید.
 در این مرحله بعد از ایجاد موفقیت آمیز Site collection دوباره به IIS سری بزنید و پس از انتخاب Web Application مورد نظر بروی Authentication کلیک کنید و مطمئن شوید که Form Authentication مقدارش Enable میباشد. در این بخش اگر هر دو حالت Form Base Authentication و Windows Authentication فعال باشد IIS در گوشه سمت راست به شما خطایی نمایش میدهد مبنی بر اینکه شما نباید هر دو حالت را همزمان فعال کنید. البته خیلی این تذکر را جدی نگیرید و بکارتان ادامه دهید و در نهایت بعد از اینکه مراحل را کامل انجام دادید و از اجرای کامل FBA اطمینان حاصل نمودید،میتوانید برگردید و حالت Windows Authentication را غیر فعال کنید.
 6.تنظیمات STS:
علاوه بر تنظیمات مربوط به بخش Web Application باید تنظیمات FBA را روی Application سرویس‌ها نیز انجام داد تا سرویس‌های WCF نیز پشتیبانی از FBA را بپذیرند. برای تنظیمات این بخش روی Security Token Service Application که زیر مجموعه SharePoint Web Services قرار دارد کلیک کنید.

 
در ادامه تنها کافی است Connection string را جهت اتصال به بانکی که در ابتدا ساختیم ایجاد کرده و Provider‌ها را نیز مطابق قبل اضافه کنیم. فقط توجه شود Connection string و Provider‌ها همنام قبلی‌ها نباشند ولی Application Name همچنان برابر با "/" مقدار دهی شود.
هیچ تغییر دیگری در این Application ایجاد نشود.مثلا Authentication به هیچ وجه تغییر نکند و در حالت ویندوزی باقی بماند.
کار تقریبا به پایان رسیده است،میتوانید در پرتال لاگین کنید!
آنچنانکه که در تصویر می‌بینید هر دو حالت ویندوزی و FBA برای احراز هویت فعال می‌باشد.

 پی نوشت:
1.همانطور که احتمالا متوجه شده اید این آموزش با راهکارهای حاضر یک سری تفاوت‌ها داشت که عمده‌ترین آن عدم تغییر در بخش احراز هویت مدیریت شیرپوینت بود. علت این امر نیز به این خاطر است که اساسا هر کاربری به این بخش دسترسی ندارد و تنها مدیر سیستم است که باید به این بخش دسترسی داشته باشد، بر همین اساس ترجیح میدهم احراز هویت آن به همان شکل اولیه(Windows Authentication ) باقی بماند.
2.در این نوشته من از شرح تنظیمات و نکات ریز و بدیهی خوداری کردم با این پیش فرض که خواننده مطلب، بر اصول پایه شیرپوینت و Asp.net آگاهی دارد. در غیر این صورت بهتر است از لینک هایی مرجع زیر کمک بگیرید.
مطالب
نوشتن اعتبارسنج‌های سفارشی برای فرم‌های مبتنی بر قالب‌ها در Angular
در مطلب «فرم‌های مبتنی بر قالب‌ها در Angular - قسمت چهارم - اعتبارسنجی ورودی‌ها» مشاهده کردیم که Angular در روش فرم‌های مبتنی بر قالب‌ها، تنها از 4 روش بومی اعتبارسنجی مرورگرها مانند ذکر ویژگی required برای فیلدهای اجباری، ویژگی‌های minlength و maxlength برای تعیین حداقل و حداکثر تعداد حروف مجاز قابل ورود در یک فیلد و از pattern برای کار با عبارات با قاعده پشتیبانی می‌کند. برای بهبود این وضعیت در این مطلب قصد داریم روش تهیه اعتبارسنج‌های سفارشی مخصوص حالت فرم‌های مبتنی بر قالب‌ها را بررسی کنیم.


تدارک مقدمات مثال این قسمت

این مثال، در ادامه‌ی همین سری کار با فرم‌های مبتنی بر قالب‌ها است. به همین جهت ابتدا ماژول جدید CustomValidators را به آن اضافه می‌کنیم:
 >ng g m CustomValidators -m app.module --routing
همچنین به فایل app.module.ts مراجعه کرده و CustomValidatorsModule را بجای CustomValidatorsRoutingModule در قسمت imports معرفی می‌کنیم. سپس به این ماژول جدید، کامپوننت فرم ثبت نام یک کاربر را اضافه خواهیم کرد:
 >ng g c CustomValidators/user-register
که اینکار سبب به روز رسانی فایل custom-validators.module.ts و افزوده شدن UserRegisterComponent به قسمت declarations آن می‌شود.
در ادامه کلاس مدل معادل فرم ثبت نام کاربران را تعریف می‌کنیم:
 >ng g cl CustomValidators/user
با این محتوا:
export class User {
  constructor(
    public username: string = "",
    public email: string = "", 
    public password: string = "", 
    public confirmPassword: string = "" 
  ) {}
}
در طراحی فرم HTML ایی آن نیاز است این موارد رعایت شوند:
- ورود نام کاربری اجباری بوده و باید بین 5 تا 8 حرف باشد.
- ورود ایمیل اجباری بوده و باید فرمت مناسبی نیز داشته باشد.
- ورود کلمه‌ی عبور اجباری بوده و باید با confirmPassword تطابق داشته باشد.
- ورود «کلمه‌ی عبور خود را مجددا وارد کنید» اجباری بوده و باید با password تطابق داشته باشد.



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

هرچند می‌توان اعتبارسنجی ایمیل‌ها را توسط ویژگی استاندارد pattern نیز مدیریت کرد، اما جهت بررسی نحوه‌ی انتقال آن به یک اعتبارسنج سفارشی، کار را با ایجاد یک دایرکتیو مخصوص آن ادامه می‌دهیم:
 >ng g d CustomValidators/EmailValidator -m custom-validators.module
این دستور علاوه بر ایجاد فایل جدید email-validator.directive.ts و تکمیل ساختار ابتدایی آن، کار به روز رسانی custom-validators.module.ts را نیز انجام می‌دهد. در این حالت به صورت خودکار قسمت declarations این ماژول با EmailValidatorDirective مقدار دهی می‌شود.
در ادامه کدهای کامل این اعتبارسنج سفارشی را مشاهده می‌کنید:
import { Directive } from "@angular/core";
import { AbstractControl, NG_VALIDATORS, Validator } from "@angular/forms";

@Directive({
  selector:
    "[appEmailValidator][formControlName],[appEmailValidator][formControl],[appEmailValidator][ngModel]",
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: EmailValidatorDirective,
      multi: true
    }
  ]
})
export class EmailValidatorDirective implements Validator {
  validate(element: AbstractControl): { [key: string]: any } {
    const emailRegex = /\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/;
    const valid = emailRegex.test(element.value);
    return valid ? null : { appEmailValidator: true };
  }
}
توضیحات تکمیلی:
- علت تعریف این اعتبارسنج به صورت یک دایرکتیو جدید این است که بتوان selector آن‌را همانند ویژگی‌های HTML، به فیلد ورودی اضافه کرد:
<input #email="ngModel" required appEmailValidator type="text" class="form-control" 
name="email" [(ngModel)]="model.email">

- روش تعریف selector آن اندکی متفاوت است:
selector:
"[appEmailValidator][formControlName],[appEmailValidator][formControl],[appEmailValidator][ngModel]",
در اینجا مطابق https://angular.io/guide/styleguide#style-02-08 توصیه شده‌است که:
الف) نام دایرکتیو باید با یک پیشوند شروع شود و این پیشوند در فایل angular-cli.json. به app تنظیم شده‌است:
"apps": [
{
   // ...
   "prefix": "app",
این مساله در جهت مشخص کردن سفارشی بودن این دایرکتیو و همچنین کاهش احتمال تکرار نام‌ها توصیه شده‌است.
ب) در اینجا formControlName، formControl و ngModel قید شده‌ی در کنار نام selector این دایرکتیو را نیز مشاهده می‌کنید. وجود آن‌ها به این معنا است که کلاس این دایرکتیو، به المان‌هایی که به آن‌ها ویژگی appEmailValidator اضافه شده‌است و همچنین آن المان‌ها از یکی از سه نوع ذکر شده هستند، اعمال می‌شود و در سایر موارد بی‌اثر خواهد بود. البته ذکر این سه نوع، اختیاری است و صرفا می‌توان نوشت:
 selector: "[appEmailValidator]"

- پس از آن قسمت providers را مشاهده می‌کنید:
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: EmailValidatorDirective,
      multi: true
    }
کار قسمت multi آن این است که EmailValidatorDirective (یا همان کلاس جاری) را به لیست NG_VALIDATORS توکار (اعتبارسنج‌های توکار مبتنی بر قالب‌ها) اضافه می‌کند و سبب بازنویسی هیچ موردی نخواهد شد. بنابراین وجود این قسمت در جهت تکمیل تامین کننده‌های توکار Angular ضروری است.

- سپس پیاده سازی اینترفیس توکار Validator را مشاهده می‌کنید:
 export class EmailValidatorDirective implements Validator {
این اینترفیس جزو مجموعه‌ی فرم‌های مبتنی بر قالب‌ها است و از آن جهت نوشتن اعتبارسنج‌های سفارشی می‌توان استفاده کرد.
برای پیاده سازی این اینترفیس، نیاز است متد اجباری ذیل را نیز افزود و تکمیل کرد:
 validate(element: AbstractControl): { [key: string]: any }
کار این متد این است که المانی را که appEmailValidator به آن اعمال شده‌است، به عنوان پارامتر متد validate در اختیار کلاس جاری قرار می‌دهد. به این ترتیب می‌توان برای مثال به مقدار آن دسترسی یافت و سپس منطق سفارشی را پیاده سازی و یک خروجی key/value را بازگشت داد.
validate(element: AbstractControl): { [key: string]: any } {
  const emailRegex = /\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/;
  const valid = emailRegex.test(element.value);
  return valid ? null : { appEmailValidator: true };
}
برای مثال در اینجا مقدار فیلد ایمیل element.value توسط عبارت باقاعده‌ی نوشته شده بررسی می‌شود. اگر با این الگو انطباق داشته باشد، نال بازگشت داده می‌شود (اعلام عدم وجود مشکلی در اعتبارسنجی) و اگر خیر، یک شیء key/value دلخواه را می‌توان بازگشت داد.

- اکنون که این دایرکتیو جدید طراحی و ثبت شده‌است (در قسمت declarations فایل custom-validators.module.ts)، تنها کافی است selector آن‌را به المان ورودی مدنظر اعمال کنیم تا کار اعتبارسنجی آن‌را به صورت خودکار مدیریت کند:
<input #email="ngModel" required appEmailValidator type="text" class="form-control"
name="email" [(ngModel)]="model.email">


نحوه‌ی طراحی خروجی متد validate

هنگام پیاده سازی متد validate اینترفیس Validator، هیچ قالب خاصی برای خروجی آن درنظر گرفته نشده‌است و همینقدر که این خروجی یک شیء key/value باشد، کفایت می‌کند. برای مثال اگر اعتبارسنج استاندارد required با شکست مواجه شود، یک چنین شی‌ءایی را بازگشت می‌دهد:
 { required:true }
و یا اگر اعتبارسنج استاندارد minlength باشکست مواجه شود، اطلاعات بیشتری را در قسمت مقدار این کلید بازگشتی، ارائه می‌دهد:
{ minlength : {
     requiredLength : 3,
     actualLength : 1
   }
}
در کل اینکه چه چیزی را بازگشت دهید، بستگی به طراحی مدنظر شما دارد؛ برای نمونه در اینجا appEmailValidator (یک کلید و نام دلخواه است و هیچ الزامی ندارد که با نام selector این دایرکتیو یکی باشد)، به true تنظیم شده‌است:
 { appEmailValidator: true }
بنابراین شرط تامین نوع خروجی، برقرار است. علت true بودن آن نیز مورد ذیل است:
<div class="alert alert-danger"  *ngIf="email.errors.appEmailValidator">
The entered email is not valid.
</div>
در اینجا اگر false را بازگشت دهیم، هرچند email.errors دارای کلید جدید appEmailValidator شده‌است، اما ngIf سبب رندر خطای اعتبارسنجی «ایمیل وارد شده معتبر نیست.» به علت false بودن نتیجه‌ی نهایی، نمی‌شود. یا حتی می‌توان بجای true یک رشته و یا یک شیء با توضیحات بیشتری را نیز تنظیم کرد؛ چون value این key/value به any تنظیم شده‌است و هر چیزی را می‌پذیرد.
از دیدگاه اعتبارسنج فرم‌های مبتنی بر قالب‌ها، همینقدر که آرایه‌ی email.errors دارای عضو و کلید جدیدی شد، کار به پایان رسیده‌است و اعتبارسنجی المان را شکست خورده ارزیابی می‌کند. مابقی آن، اطلاعاتی است که برنامه نویس ارائه می‌دهد (بر اساس نیازهای نمایشی برنامه).


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

در طراحی کلاس User که معادل فیلدهای فرم ثبت نام کاربران است، دو خاصیت کلمه‌ی عبور و تائید کلمه‌ی عبور را مشاهده می‌کنید:
public password: string = "",
public confirmPassword: string = ""
Angular به همراه اعتبارسنج توکاری برای بررسی یکی بودن این دو نیست. به همین جهت نمونه‌ی سفارشی آن‌را همانند EmailValidatorDirective فوق تهیه می‌کنیم. ابتدا یک دایرکتیو جدید را به نام EqualValidator به ماژول custom-validators اضافه می‌کنیم:
 >ng g d CustomValidators/EqualValidator -m custom-validators.module
که سبب ایجاد فایل جدید equal-validator.directive.ts و به روز رسانی قسمت declarations فایل custom-validators.module.ts با EqualValidatorDirective نیز می‌شود.

در ادامه کدهای کامل آن‌را در ذیل مشاهده می‌کنید:
import { Directive, Attribute } from "@angular/core";
import { Validator, AbstractControl, NG_VALIDATORS } from "@angular/forms";

@Directive({
  selector:
    "[appValidateEqual][formControlName],[appValidateEqual][formControl],[appValidateEqual][ngModel]",
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: EqualValidatorDirective,
      multi: true
    }
  ]
})
export class EqualValidatorDirective implements Validator {
  constructor(@Attribute("compare-to") public compareToControl: string) {}

  validate(element: AbstractControl): { [key: string]: any } {
    const selfValue = element.value;
    const otherControl = element.root.get(this.compareToControl);

    console.log("EqualValidatorDirective", {
       thisControlValue: selfValue,
       otherControlValue: otherControl ? otherControl.value : null
    });

    if (otherControl && selfValue !== otherControl.value) {
      return {
        appValidateEqual: true // Or a string such as 'Password mismatch.' or an abject.
      };
    }

    if (
      otherControl &&
      otherControl.errors &&
      selfValue === otherControl.value
    ) {
      delete otherControl.errors["appValidateEqual"];
      if (!Object.keys(otherControl.errors).length) {
        otherControl.setErrors(null);
      }
    }

    return null;
  }
}
توضیحات تکمیلی:
- قسمت آغازین این اعتبارسنج سفارشی، مانند توضیحات EmailValidatorDirective است که در ابتدای بحث عنوان شد. این کلاس به یک Directive مزین شده‌است تا بتوان selector آن‌را به المان‌های HTML ایی فرم افزود (برای مثال در اینجا به دو فیلد ورود کلمات عبور). قسمت providers آن نیز تنظیم شده‌است تا EqualValidatorDirective جاری به لیست توکار NG_VALIDATORS اضافه شود.
- در ابتدای کار، پیاده سازی اینترفیس Validator، همانند قبل انجام شده‌است؛ اما چون در اینجا می‌خواهیم نام فیلدی را که قرار است کار مقایسه را با آن انجام دهیم نیز دریافت کنیم، ابتدا یک Attribute و سپس یک پارامتر و خاصیت عمومی دریافت کننده‌ی مقدار آن‌را نیز افزوده‌ایم:
export class EqualValidatorDirective implements Validator {
  constructor(@Attribute("compare-to") public compareToControl: string) {}
به این ترتیب زمانیکه قرار است فیلد کلمه‌ی عبور را تعریف کنیم، ابتدا ویژگی appValidateEqual یا همان selector این اعتبارسنج به آن اضافه شده‌است تا کار فعال سازی ابتدایی صورت گیرد:
<input #password="ngModel" required type="password" class="form-control"
appValidateEqual compare-to="confirmPassword" name="password" [(ngModel)]="model.password">
سپس Attribute یا ویژگی به نام compare-to نیز تعریف شده‌است. این compare-to همان نامی است که به Attribute@ نسبت داده شده‌است. سپس مقداری که به این ویژگی نسبت داده می‌شود، توسط خاصیت compareToControl دریافت خواهد شد.
در اینجا محدودیتی هم از لحاظ تعداد ویژگی‌ها نیست و اگر قرار است این اعتبارسنج اطلاعات بیشتری را نیز دریافت کند می‌توان ویژگی‌های بیشتری را به سازنده‌ی آن نسبت داد.

یک نکته: می‌توان نام این ویژگی را با نام selector نیز یکی انتخاب کرد. به این ترتیب ذکر نام ویژگی آن، هم سبب فعال شدن اعتبارسنج و هم نسبت دادن مقداری به آن، سبب مقدار دهی خاصیت متناظر با آن، در سمت کلاس اعتبارسنج می‌گردد.

- در ابتدای این اعتبارسنج، نحوه‌ی دسترسی به مقدار یک کنترل دیگر را نیز مشاهده می‌کنید:
export class EqualValidatorDirective implements Validator {
  constructor(@Attribute("compare-to") public compareToControl: string) {}

  validate(element: AbstractControl): { [key: string]: any } {
    const selfValue = element.value;
    const otherControl = element.root.get(this.compareToControl);

    console.log("EqualValidatorDirective", {
       thisControlValue: selfValue,
       otherControlValue: otherControl ? otherControl.value : null
    });
در اینجا element.value مقدار المان یا کنترل HTML جاری است که appValidateEqual به آن اعمال شده‌است.
بر اساس مقدار خاصیت compareToControl که از ویژگی compare-to دریافت می‌شود، می‌توان به کنترل دوم، توسط element.root.get دسترسی یافت.

- در ادامه‌ی کار، مقایسه‌ی ساده‌ای را مشاهده می‌کنید:
    if (otherControl && selfValue !== otherControl.value) {
      return {
        appValidateEqual: true // Or a string such as 'Password mismatch.' or an abject.
      };
    }
اگر کنترل دوم یافت شد و همچنین مقدار آن با مقدار کنترل جاری یکی نبود، همان شیء key/value مورد انتظار متد validate، در جهت اعلام شکست اعتبارسنجی بازگشت داده می‌شود.

- در پایان کدهای متد validate، چنین تنظیمی نیز قرار گرفته‌است:
    if (otherControl && otherControl.errors && selfValue === otherControl.value) {
      delete otherControl.errors["appValidateEqual"];
      if (!Object.keys(otherControl.errors).length) {
        otherControl.setErrors(null);
      }
    }

    return null;
اعتبارسنج تعریف شده، فقط به کنترلی که هم اکنون در حال کار با آن هستیم اعمال می‌شود. اگر پیشتر کلمه‌ی عبوری را وارد کرده باشیم و سپس به فیلد تائید آن مراجعه کنیم، وضعیت اعتبارسنجی فیلد کلمه‌ی عبور قبلی به حالت غیرمعتبر تنظیم شده‌است. اما پس از تکمیل فیلد تائید کلمه‌ی عبور، هرچند وضعیت فیلد جاری معتبر است، اما هنوز وضعیت فیلد قبلی غیرمعتبر می‌باشد. برای رفع این مشکل، ابتدا کلید دلخواه appValidateEqual را از آن حذف می‌کنیم (همان کلیدی است که پیشتر در صورت مساوی نبودن مقدار فیلدها بازگشت داده شده‌است). حذف این کلید سبب نال شدن آرایه‌ی errors یک شیء نمی‌شود و همانطور که پیشتر عنوان شد، Angular تنها به همین مورد توجه می‌کند. بنابراین در ادامه کار، setErrors یا تنظیم آرایه‌ی errors به نال هم انجام شده‌است. در اینجا است که Angular فیلد دوم را نیز معتبر ارزیابی خواهد کرد.


تکمیل کامپوننت فرم ثبت نام کاربران

اکنون user-register.component.ts را که در ابتدای بحث اضافه کردیم، چنین تعاریفی را پیدا می‌کند:
import { NgForm } from "@angular/forms";
import { User } from "./../user";
import { Component, OnInit } from "@angular/core";

@Component({
  selector: "app-user-register",
  templateUrl: "./user-register.component.html",
  styleUrls: ["./user-register.component.css"]
})
export class UserRegisterComponent implements OnInit {
  model = new User();

  constructor() {}

  ngOnInit() {}

  submitForm(form: NgForm) {
    console.log(this.model);
    console.log(form.value);
  }
}
در اینجا تنها کار مهمی که انجام شده‌است، ارائه‌ی خاصیت عمومی مدل، جهت استفاده‌ی از آن در قالب HTML ایی این کامپوننت است. بنابراین به فایل user-register.component.html مراجعه کرده و آن‌را نیز به صورت ذیل تکمیل می‌کنیم:

ابتدای فرم
<div class="container">
  <h3>Registration Form</h3>
  <form #form="ngForm" (submit)="submitForm(form)" novalidate>
در اینجا novalidate اضافه شده‌است تا اعتبارسنجی توکار مرورگرها با اعتبارسنجی سفارشی فرم جاری تداخل پیدا نکند. همچنین توسط یک template reference variable به وهله‌ای از فرم دسترسی یافته و آن‌را به متد submitForm کامپوننت ارسال کرده‌ایم.

تکمیل قسمت ورود نام کاربری

    <div class="form-group" [class.has-error]="username.invalid && username.touched">
      <label class="control-label">User Name</label>
      <input #username="ngModel" required maxlength="8" minlength="4" type="text"
        class="form-control" name="username" [(ngModel)]="model.username">
      <div *ngIf="username.invalid && username.touched">
        <div class="alert alert-info">
          errors: {{ username.errors | json }}
        </div>
        <div class="alert alert-danger"  *ngIf="username.errors.required">
          username is required.
        </div>
        <div class="alert alert-danger"  *ngIf="username.errors.minlength">
          username should be minimum {{username.errors.minlength.requiredLength}} characters.
        </div>
        <div class="alert alert-danger"  *ngIf="username.errors.maxlength">
          username should be max {{username.errors.maxlength.requiredLength}} characters.
        </div>
      </div>
    </div>
اعتبارسنجی فیلد نام کاربری شامل سه قسمت بررسی errors.required، errors.minlength و errors.maxlength است.


تکمیل قسمت ورود ایمیل

    <div class="form-group" [class.has-error]="email.invalid && email.touched">
      <label class="control-label">Email</label>
      <input #email="ngModel" required appEmailValidator type="text" class="form-control"
        name="email" [(ngModel)]="model.email">
      <div *ngIf="email.invalid && email.touched">
        <div class="alert alert-info">
          errors: {{ email.errors | json }}
        </div>
        <div class="alert alert-danger"  *ngIf="email.errors.required">
          email is required.
        </div>
        <div class="alert alert-danger"  *ngIf="email.errors.appEmailValidator">
          The entered email is not valid.
        </div>
      </div>
    </div>
در اینجا نحوه‌ی استفاده‌ی از دایرکتیو جدید appEmailValidator را ملاحظه می‌کنید. این دایرکتیو ابتدا به المان فوق متصل و سپس نتیجه‌ی آن در قسمت ngIf، برای نمایش خطای متناظری بررسی شده‌است.


تکمیل قسمت‌های ورود کلمه‌ی عبور و تائید آن

    <div class="form-group" [class.has-error]="password.invalid && password.touched">
      <label class="control-label">Password</label>
      <input #password="ngModel" required type="password" class="form-control"
        appValidateEqual compare-to="confirmPassword" name="password" [(ngModel)]="model.password">
      <div *ngIf="password.invalid && password.touched">
        <div class="alert alert-info">
          errors: {{ password.errors | json }}
        </div>
        <div class="alert alert-danger"  *ngIf="password.errors.required">
          password is required.
        </div>
        <div class="alert alert-danger"  *ngIf="password.errors.appValidateEqual">
          Password mismatch. Please complete the confirmPassword .
        </div>
      </div>
    </div>

    <div class="form-group" [class.has-error]="confirmPassword.invalid && confirmPassword.touched">
      <label class="control-label">Retype password</label>
      <input #confirmPassword="ngModel" required type="password" class="form-control"
        appValidateEqual compare-to="password" name="confirmPassword" [(ngModel)]="model.confirmPassword">
      <div *ngIf="confirmPassword.invalid && confirmPassword.touched">
        <div class="alert alert-info">
          errors: {{ confirmPassword.errors | json }}
        </div>
        <div class="alert alert-danger"  *ngIf="confirmPassword.errors.required">
          confirmPassword is required.
        </div>
        <div class="alert alert-danger"  *ngIf="confirmPassword.errors.appValidateEqual">
          Password mismatch.
        </div>
      </div>
    </div>
در اینجا نحوه‌ی اعمال دایرکتیو جدید appValidateEqual و همچنین ویژگی compare-to آن‌را به فیلدهای کلمه‌ی عبور و تائید آن مشاهده می‌کنید.
همچنین خروجی آن نیز در قسمت ngIf آخر بررسی شده‌است و سبب نمایش خطای اعتبارسنجی متناسبی می‌شود.


تکمیل انتهای فرم

    <button class="btn btn-primary" [disabled]="form.invalid" type="submit">Ok</button>
  </form>
</div>
در اینجا بررسی می‌شود که آیا فرم معتبر است یا خیر. اگر خیر، دکمه‌ی submit آن غیرفعال می‌شود و برعکس.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-08.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی دستورات
>npm install
>ng build --watch
و در دومی دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
اکنون می‌توانید برنامه را در آدرس http://localhost:5000 مشاهده و اجرا کنید.
مطالب
انجام اعمال ریاضی بر روی Generics
کامپایلر سی‌شارپ اگر نتواند نوع‌های عملوندها را در حین بکارگیری عملگرها تشخیص دهد، اجازه‌ی استفاده از عملگر را نخواهد داد و کار کامپایل، با یک خطا خاتمه می‌یابد. برای نمونه مثال زیر را در نظر بگیرید:
    public interface ICalculator<T>
    {
        T Add(T operand1, T operand2);
    }

    public class Calculator<T> : ICalculator<T>
    {
        public T Add(T operand1, T operand2)
        {
            return operand1 + operand2;
        }
    }
در اینجا چون کامپایلر نمی‌داند که عملگر + بر روی چه نوع‌هایی قرار است اعمال شود (به علت جنریک تعریف شدن این نوع‌ها و مشخص نبودن اینکه آیا این نوع، اصلا عملگر + دارد یا خیر)، با صدور خطای زیر، عملیات کامپایل را متوقف می‌کند:
 Operator '+' cannot be applied to operands of type 'T' and 'T'
برای حل این مساله، چندین روش مطرح شده‌است که در ادامه تعدادی از آن‌ها را مرور خواهیم کرد.


روش اول: واگذار کردن استراتژی عملیات ریاضی به یک کلاس خارجی

این راه حلی است که توسط اعضای تیم سی‌شارپ در روزهای ابتدایی معرفی جنریک‌ها مطرح شده‌است. فرض کنید می‌خواهیم لیستی از جنریک‌ها را با هم جمع بزنیم:
    public class Calculator2<T>
    {
        public T Sum(List<T> list)
        {
            T sum = 0;
            for (int i = 0; i < list.Count; i++)
                sum += list[i];
            return sum;
        }
    }
این کد نیز قابل کامپایل نبوده و امکان اعمال عملگر + بر روی نوع ناشناخته‌ی T میسر نیست.
    public interface ICalculator<T>
    {
        T Add(T operand1, T operand2);
    }

    public class Int32Calculator : ICalculator<int>
    {
        public int Add(int operand1, int operand2)
        {
            return operand1 + operand2;
        }
    }

    public class AlgorithmLibrary<T> where T : new() 
    {
        private readonly ICalculator<T> _calculator;
        public AlgorithmLibrary(ICalculator<T> calculator)
        {
            _calculator = calculator;
        }

        public T Sum(List<T> items)
        {
            var sum = new T();
            for (var i = 0; i < items.Count; i++)
            {
                sum = _calculator.Add(sum, items[i]);
            }
            return sum;
        }
    }
در راه حل ارائه شده، یک اینترفیس عمومی که متد جمع را تعریف کرده‌است، مشاهده می‌کنیم. سپس این اینترفیس در سازنده‌ی کتابخانه‌ی الگوریتم‌‌های برنامه تزریق شده‌است. اکنون کدهای AlgorithmLibrary بدون مشکل کامپایل می‌شوند. هر زمان که نیاز به استفاده از آن بود، بر اساس نوع T، پیاده سازی خاصی را باید ارائه داد. برای مثال در اینجا Int32Calculator پیاده سازی نوع int را انجام داده‌است. برای استفاده از آن نیز خواهیم داشت:
 var result = new AlgorithmLibrary<int>(new Int32Calculator()).Sum(new List<int> { 1, 2, 3 });

البته این نوع پیاده سازی را که کار اصلی آن واگذاری عملیات جمع، به یک کلاس خارجی است، توسط Func نیز می‌توان خلاصه‌تر کرد:
    public class Algorithms<T> where T : new() 
    {
        public T Calculate(Func<T, T, T> add, IEnumerable<T> numbers)
        {
            var sum = new T();
            foreach (var number in numbers)
            {
                sum = add(sum, number);
            }
            return sum;
        }
    }
استفاده از Action و Func نیز یکی دیگر از روش‌های تزریق وابستگی‌ها است که در اینجا بکار گرفته شده‌است. برای استفاده از آن خواهیم داشت:
 var result = new Algorithms<int>().Calculate((a, b) => a + b, new[] { 1, 2, 3 });
آرگومان اول روش جمع زدن را مشخص می‌کند و آرگومان دوم، لیستی است که باید اعضای آن جمع زده شوند.


روش دوم: استفاده از واژه‌ی کلیدی dynamic

با استفاده از واژه‌ی کلیدی dynamic می‌توان بررسی نوع داده‌ها را به زمان اجرا موکول کرد. به این ترتیب دیگر کامپایلر مشکلی با کامپایل قطعه کد ذیل نخواهد داشت:
    public class Calculator<T> : ICalculator<T>
    {
        public T Add(T operand1, T operand2)
        {
            return (dynamic)operand1 + operand2;
        }
    }
و مثال زیر نیز به خوبی کار می‌کند:
 var test = new Calculator<int>().Add(1, 2);
البته بدیهی است که نوع تعریف شده در اینجا باید دارای عملگر + باشد. در غیر اینصورت در زمان اجرا برنامه با یک خطا خاتمه خواهد یافت.
روش فوق نسبت به حالتی که بر اساس نوع T تصمیم‌گیری شود و از عملگر + متناظری استفاده گردد، خوانایی بهتری دارد:
public T Add(T t1, T t2)
{
    if (typeof(T) == typeof(double))
    {
        var d1 = (double)t1;
        var d2 = (double)t2;
        return (T)(d1 + d2);
    }
    else if (typeof(T) == typeof(int)){
        var i1 = (int)t1;
        var i2 = (int)t2;
        return (T)(i1 + i2);
    }
    else ...
}


روش سوم: استفاده از Expression Trees

روش زیر بسیار شبیه است به حالتیکه از Func در روش اول استفاده شد. در اینجا این Func به صورت پویا تولید و سپس صدا زده می‌شود:
using System;
using System.Linq.Expressions;

namespace GenericsArithmetic
{
    public class Solution3
    {
        public T Add<T>(T a, T b)
        {
            var paramA = Expression.Parameter(typeof(T), "a");
            var paramB = Expression.Parameter(typeof(T), "b");

            var body = Expression.Add(paramA, paramB);
            var add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
            return add(a, b);
        }
    }
}
البته این مثال، یک مثال ابتدایی در این مورد است. بر همین مبنا و ایده، یک کتابخانه‌ی با کارآیی بالا، تحت عنوان Generic Operators که جزو Misc utils می‌باشد، تهیه شده‌است.
به کمک کتابخانه‌ی Generic Operators، کدهای جمع زدن اعضای یک لیست جنریک به صورت ذیل خلاصه می‌شوند:
public static T Sum<T>(this IEnumerable<T> source)
{
    T sum = Operator<T>.Zero;
    foreach (T value in source)
    {
            sum = Operator.Add(sum, value);
    }
    return sum;
}
مطالب
آشنایی با ساختار IIS قسمت هفتم

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

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

ماژول‌های سودمند یا utility

نام ماژول:

UriCacheModule

توضیح:

این ماژول نوعی کش برای URLها به شمار می‌رود. موقعی که url درخواست می‌شود، اطلاعات در اولین درخواست خوانده شده و کش می‌شود و اگر دوباره همان url درخواست شود، بدون خواندن تنظیمات و بر اساس تنظیمات قبلی، کار url مربوطه را انجام میدهد تا اطلاعات پیکربندی تغییر کند و بر اساس اطلاعات جدید، خود را به روز کند.

تگ قابل پیکربندی:

لازم ندارد

وابستگی:

ندارد

اثرات حذف آن:

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

نام ماژول :

FileCacheModule

توضیح :

فایل هندلِ فایل‌هایی که قبلا در سرور باز شده‌اند را کش می‌کند تا در صورت نیاز در دفعات بعدی سریعتر عمل کند.

تگ قابل پیکربندی :

لازم ندارد .

وابستگی :

ندارد.

اثرات حذف آن :

کارایی سیستم کاهش می‌یابد. سیستم در هر اجرای دستور مربوط به فایل‌ها باید فایل هندل را به دست آورد.

نام ماژول :

TokenCacheModule

توضیح :

توکن‌های امنیتی ویندوز که پسوردهایی بر اساس authentication schemes هستند را کش می‌کند (anonymous authentication, basic authentication, IIS client certificate authentication )

تگ قابل پیکربندی :

لازم ندارد

وابستگی :

ندارد

اثرات حذف آن :

کارایی سیستم به شدت پایین می‌آید. کاربر باید با هر درخواستی لاگین کند. یکی از اصلی‌ترین ضربه‌ها با حذف این ماژول این است که اگر مثلا یک پسورد از یک فایل html محافظت می‌کند و این صفحه به 50 تصویر ارجاع دارد، 51 بار باید درخواست لاگین اجرا گردد یا شاید هم بدتر

MANAGED ENGINE: ASP.NET INTEGRATION

نام ماژول :

ManagedEngine

توضیح :

مدیریت ماژول‌های native و مدیریت شده

تگ قابل پیکربندی :


وابستگی :

ندارد

اثرات حذف آن :

مشخصا غیرفعال شدن asp.net integrated و غیر فعال شدن تمامی ماژول‌ها و هندلر‌های تگ وب کانفیگ یا داخل فایل کانفیگ IIS که در مقالات قبلی به تفصیل بیان کرده‌ایم.

IIS 7 NATIVE MODULES

نام ماژول :

HttpCacheModule

توضیح :

مدیریت کش خروجی در htttp.sys بر اساس پیکربندی مثل تعریف سایز کش و ...

تگ قابل پیکربندی :

System.webServer/caching

وابستگی :

ندارد.

اثرات حذف آن :

محتوا دیگر به صورت کرنل مد، کش نمی‌شود و کش پروفایل هم ندید گرفته می‌شود و احتمالا بر کارآیی و استفاده از منابع هم اثر می‌گذارد.

نام ماژول :

DynamicCompressionModule

توضیح :

پیاده سازی in-memory compression در محتوای پویا

تگ قابل پیکربندی :

system.webServer/httpCompression and system.webServer/urlCompression.

وابستگی :

وابستگی ندارد چرا که به طور پیش فرض غیرفعال است.

نام ماژول :

StaticCompressionModule

توضیح :

پیادسازی فشرده سازی در محتوای ایستا و برای فایل‌های سیستمی از نوع in memory

تگ قابل پیکربندی :

system.webServer/httpCompression and system.webServer/urlCompression

وابستگی :

ندارد.

اثرات حذف آن :

در صورت عدم فشرده سازی بر مصرف ترافیک تاثیر گذار است.

نام ماژول :

DefaultDocumentModule

توضیح :

پیاده سازی یک لیست سند پیش فرض. درخواست‌ها مدام پشت سر هم می‌آیند و این درخواست‌های پشت سرهم، به سند پیش فرض هدایت می‌شوند. همان پنجره ای که شما به ترتیب فایل‌های index.htm,index.asp,default.aspx و... را تعیین می‌کنید.

تگ قابل پیکربندی :

system.webServer/defaultDocument

وابستگی :

ندارد.

اثرات حذف آن :

درخواست را به ریشه هدایت می‌کند. مثلا برای localhost صفحه 404 باز میگرداند و اگر directoryBrowsing فعال باشد لیستی از دایرکتوری ریشه را باز میگرداند.

نام ماژول :

DirectoryListingModule

توضیح :

پیادی سازی لیستی از محتویات یک دایرکتوری

تگ قابل پیکربندی :

system.webServer/directoryBrowse

وابستگی :

ندارد.

اثرات حذف آن :

اگر این ماژول و ماژول قبلی غیرفعال باشند response نهایی خالی است.

نام ماژول :

ProtocolSupportModule

توضیح :

پیاده سازی اختصاصی از response header

پیاده سازی تنظیمات trace و HTTP verbs.

پیاده سازی تنظیمات مربوطه به keep-alive بر اساس پیکربندی

تگ قابل پیکربندی :

system.webServer/httpProtocol

وابستگی :

ندارد.

اثرات حذف آن :

بازگرداندن پیام خطای "405 Method not allowed".

نام ماژول :

HttpRedirectionModule

توضیح :

پیاده سازی عملیات انتقال یا redirect

تگ قابل پیکربندی :

system.webServer/httpRedirect

وابستگی :

ندارد.

اثرات حذف آن :

خطر امنیتی: اگر منابعی با redirect کردن محافظت می‌شوند، از این پس در دسترسند.

نام ماژول :

ServerSideIncludeModule

توضیح :

حمایت از فایل shtm یا shtml و ...

تگ قابل پیکربندی :

system.webServer/serverSideInclude

وابستگی :

ندارد.

اثرات حذف آن :

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

نام ماژول :

StaticFileModule

توضیح :

فایل‌های ایستا را به همراه پسوند ارسال می‌کند. مثل jpg,html و نوع محتوا را بر اساس staticContent/mimeMap پیکربندی می‌کند.

تگ قابل پیکربندی :

system.webServer/staticContent

وابستگی :

ندارد.

اثرات حذف آن :

فایل‌های ایستا دیگر ارائه نشده و به جای آن خطای 404 بازگشت داده می‌شود.

نام ماژول :

AnonymousAuthenticationModule

توضیح :

پیاده سازی سیستم شناسایی افراد ناشناس. همانطور که میدانید در یک وب سایت حداقل محتوایی برای افرادی بدون داشتن اکانت هم وجود دارد. برای اینکار یک شیء httpuser ایجاد می‌کند.

تگ قابل پیکربندی :

system.webServer/security/authentication/anonymousAuthentication

وابستگی :

ندارد.

اثرات حذف آن :

حداقل باید یک سیستم امنیتی برای شناسایی یا authenticate وجود داشته باشد. httpuser یک ساختار داده ای در IIS می‌باشد و در صورت نبودن هیچ سیستم شناسایی وجود نداشته و در نبود شیء httpuser سیستم خطای 401.2 را تولید می‌کند.

نام ماژول :

CertificateMappingAuthenticationModule

توضیح :

مجوز SSL را به Active Directory نگاشت می‌کند.

تگ قابل پیکربندی :

system.webServer/security/authentication/clientCertificateMappingAuthentication

وابستگی :

برای اینکه این ماژول وظیفه خود را انجام دهد باید تنظیمات SSL انجام شود و همچنین سیستم IIS جزئی از دامنه Active directory باشد

اثرات حذف آن :

درخواست‌ها، نرمال رسیدگی میشوند انگار SSL وجود ندارد.

نام ماژول :

BasicAuthenticationModule

توضیح :

پیاده سازی پایه‌ای و روتین شناسایی کاربران بر اساس آن چیزی که در استانداد زیر آمده است

RFC 2617.

تگ قابل پیکربندی :

system.webServer/security/authentication/basicAuthentication

وابستگی :

None.

اثرات حذف آن :

حداقل باید یک سیستم امنیتی برای شناساسایی یا authenticate وجود داشته باشد. httpuser یک ساختار داده‌ای در IIS می‌باشد و در صورت نبود، هیچ سیستم شناسایی یافت نشده و نبود شیء  httpuser در سیستم، خطای 401.2 را تولید می‌کند.

نام ماژول :

WindowsAuthenticationModule

توضیح :

((windows Authentication (NTLM or Negotiate (Kerberos

تگ قابل پیکربندی :

system.webServer/security/authentication/windowsAuthentication

وابستگی :

ندارد.

اثرات حذف آن :

حداقل باید یک سیستم امنیتی برای شناسایی یا authenticate وجود داشته باشد. httpuser یک ساختار داده ای در IIS می‌باشد و در صورت نبود، هیچ سیستم شناسایی یافت نشده و نبود شیء httpuser در سیستم، خطای 401.2 را تولید می‌کند.

نام ماژول :

DigestAuthenticationModule

توضیح :

پیاده سازی سیستم شناسایی دیاجست بر اساس

RFC 2617 .

تگ قابل پیکربندی :

system.webServer/security/authentication/digestAuthentication

وابستگی :

IIS باید بخشی از دامنه Active Directory باشد.

اثرات حذف آن :

حداقل باید یک سیستم امنیتی برای شناسایی یا authenticate وجود داشته باشد. httpuser یک ساختار داده ای در IIS می‌باشد و در صورت نبود، هیچ سیستم شناسایی یافت نشده و نبود شیء httpuser در سیستم، خطای 401.2 را تولید می‌کند.

نام ماژول :

IISCertificateMappingAuthenticationModule

توضیح :

پیاده سازی نگاشت مجوزهای IIS، نگهداری و ذخیره اطلاعات همه نگاشت‌ها و مجوزهای کاربری چون SSL client certificates  

تگ قابل پیکربندی :

system.webServer/iisClientCertificateMappingAuthentication

وابستگی :

اطلاعات SSL به همراه دریافت client certificates جهت پیکربندی این ماژول

اثرات حذف آن :

حداقل باید یک سیستم امنیتی برای شناسایی یا authenticate وجود داشته باشد. httpuser یک ساختار داده ای در IIS می‌باشد و در صورت نبود، هیچ سیستم شناسایی یافت نشده و نبود شیء httpuser در سیستم، خطای 401.2 را تولید می‌کند.

نام ماژول :

UrlAuthorizationModule

توضیح :

پیاده سازی authorization بر اساس قوانین پیکربندی شده

تگ قابل پیکربندی :

system.webServer/security/authorization

وابستگی :

ندارد.

اثرات حذف آن :

محتواهای محافظت شده توسط authorization دیگر محافظت نمی‌شوند.

نام ماژول :

IsapiModule

توضیح :

پیاده سازی ISAPI Extension 

تگ قابل پیکربندی :

system.webServer/isapiCgiRestriction

وابستگی :

ندارد.

اثرات حذف آن :

هندلر‌های معرفی شده در بخش IsapiModule و تگ handlers دیگر اجرا نمی‌شوند

نام ماژول :

IsapiFilterModule

توضیح :

پیاده سازی ISAPI filter 

تگ قابل پیکربندی :

system.webServer/isapiFilters

وابستگی :

ندارد.

اثرات حذف آن :

اگر برنامه ای از ISAPI filter استفاده می‌کند، در اجرا دچار مشکل خواهد شد.

نام ماژول :

IpRestrictionModule

توضیح :

یک سیستم تشخیص دسترسی  بر اساس آی پی‌های ورژن4

تگ قابل پیکربندی :

system.webServer/security/ipSecurity

وابستگی :

IPv4 stack باید نصب شود.

اثرات حذف آن :

کلاینت هایی که IP هایشان در IPsecurity لیست شده‌اند ندید گرفته میشوند

نام ماژول :

RequestFilteringModule

توضیح :

پیاده سازی یک مجموعه قدرتمند از قوانین امنیتی که درخواست‌های مشکوک را پس می‌زند.

تگ قابل پیکربندی :

system.webServer/security/requestFiltering

وابستگی :

ندارد.

اثرات حذف آن :

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

نام ماژول :

CustomLoggingModule

توضیح :

پیاده سازی اینترفیس ILogPlugin در سمت IIS، به مشتریان اجازه میدهد تا لاگ‌های خود را توسعه دهند. هر چند این روش توصیه نمی‌شود و توصیه کارشناس مایکروسافت استفاده از یک ماژول دست نویس از نوع RQ_LOG_REQUEST می باشد.

Implements the ILogPlugin interface on top of IIS. ILogPlugin is a previous COM implementation that allowed customers to extend IIS logging. We do not not recommend extending IIS using this interface. Instead, customers should write a module and subscribe to the RQ_LOG_REQUEST notification.

تگ قابل پیکربندی :

system.webServer/httpLogging and system.applicationhost/sites/site/logFile/customLogPluginClsid

وابستگی :

ندارد.

اثرات حذف آن :

مسلما پلاگین‌های‌های این اینترفیس از کار می‌‌افتند که سیستم ODBC Logging هم جز آن است.

نام ماژول :

CustomErrorModule

توضیح :

پیاده سازی مدیریت خطاهای ویژه

تگ قابل پیکبرندی :

system.webServer/httpErrors

وابستگی :

None.

اثرات حذف آن :

در صورتی که خطایی از هسته باشد، نتیجه یک صفحه، با توضیح مختصری از خطا خواهد بود. در غیر این صورت اگر خطا از برنامه یا کامپوننتی باشد جزئیات خطا فاش خواهد شد

نام ماژول :

HttpLoggingModule

توضیح :

پیاده سازی سیستم logging استاندارد http.sys

تگ قابل پیکربندی :

system.applicationHost/log and system.webServer/httpLogging

وابستگی :

ندارد.

اثرات حذف آن :

از کار افتادن سیستم لاگ

نام ماژول :

FailedRequestsTracingModule

توضیح :

پیاده سازی سیستم ردیابی درخواست‌های ناموفق و اجرای قوانین، طبق پیکربندی

تگ قابل پیکربندی :

system.webServer/tracing and system.webServer/httpTracing

وابستگی :

ندارد.

اثرات حذف آن :

Tracing http requests will no longer work.

نام ماژول :

RequestMonitorModule

توضیح :

پیاده سازی IIS Run-time State and Control Interface یا به اختصار RSCA . به کاربران اجازه می‌دهد از اطلاعات، حین اجرا، کوئری بگیرند. مثل درخواست درحال اجرای جاری، آغاز به کار یا توقف وب سایت و دامنه‌های اپلیکیشن در حال اجرای جاری

تگ قابل پیکربندی :

ندارد.

وابستگی :

ندارد.

اثرات حذف آن :

ابزارهای مرتبط با این موضوع از کار می‌افتند

نام ماژول :

CgiModule

توضیح :

پیاده سازی CGI در سمت IIS

تگ قابل پیکبرندی :

system.webServer/cgi and system.webServer/isapiCgiRestriction

وابستگی :

ندارد.

اثرات حذف آن :

برنامه‌های CGI متوقف می‌شوند

نام ماژول :

TracingModule

توضیح :

پیاده سازی سیستم ردیابی ETW

تگ قابل پیکربندی :

system.webServer/httpTracing

وابستگی :

ندارد.

اثرات حذف آن :

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

نام ماژول :

ConfigurationValidationModule

توضیح :

اعتبارسنجی تنظیمات برنامه ASP.Net که به حالت integrate انتقال یافته است

تگ قابل پیکربندی :

system.webServer/Validation

وابستگی :

ندارد.

اثرات حذف آن :

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

MANAGED MODULES:

نام ماژول :

OutputCache

توضیح :

پیاده سازی output caching

تگ قابل پیکربندی :

system.web/caching/outputCache

وابستگی :

نیاز به ManagedEngine .

اثرات حذف آن :

عدم اجرای output cache

نام ماژول :

Session

توضیح :

مدیریت سشن ها

تگ قابل پیکربندی :

system.web/sessionState

وابستگی :

نیاز به ManagedEngine . 

اثرات حذف آن :

سشن‌ها از دسترس خارج می‌شوند.

نام ماژول :

WindowsAuthentication

توضیح :

اینجا 

تگ قابل پیکربندی :

system.web/authentication

وابستگی :

نیاز به ManagedEngine .

اثرات حذف آن :

این حالت قابل اجرا نخواهد بود

نام ماژول :

FormsAuthentication

توضیح :

اینجا 

تگ قابل پیکربندی :

system.web/authentication

وابستگی :

نیاز به ManagedEngine .

اثرات حذف آن :

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

نام ماژول :

DefaultAuthentication

توضیح :

اطمینان از وجود شی Authentication در context مربوطه 

تگ قابل پیکربندی :

system.web/authentication

وابستگی :

نیاز به ManagedEngine .  

اثرات حذف آن :

اگر مد Forms authentication انتخاب شده باشد بر روی بعضی از کاربران ناشناس کار نخواهد کرد و رویداد DefaultAuthentication.OnAuthenticate اجرا نخواهد شد.

نام ماژول :

RoleManager

توضیح :

اینجا 

تگ قابل پیکربندی :


وابستگی :

نیاز به ManagedEngine .

اثرات حذف آن :

این قابلیت در دسترس نمی‌باشد

نام ماژول :

UrlAuthorization

توضیح :

اینجا 

تگ قابل پیکربندی :

system.web/authorization.

وابستگی :

نیاز به ManagedEngine .  

اثرات حذف آن :

باعث از کار افتادن asp.net authorization و فاش شدن بعضی اطلاعات و همچنین دیگر تهدیدات امنیتی

نام ماژول :

AnonymousIdentification

توضیح :

اینجا 

تگ قابل پیکربندی :


وابستگی :

نیاز به ManagedEngine . 

اثرات حذف آن :

The anonymous identification feature used by the ASP.NET Profile will not work.

نام ماژول :

Profile

توضیح :

اینجا 

تگ قابل پیکربندی :


وابستگی :

ManagedEngine module must be installed.

اثرات حذف آن :

ASP.Net Profile از کار خواهد افتاد

نام ماژول :

UrlMappingsModule

توضیح :

 تبدیل یک Url واقعی به یک Url کاربرپسند 

تگ قابل پیکبرندی :


وابستگی :

نیاز به ManagedEngine .

اثرات حذف آن :

نگاشت Url‌ها صورت نمی‌گیرد

مطالب
استفاده از دیتابیس Sqlite در الکترون (قسمت اول)
یکی از مهمترین بخش‌های هر برنامه، بخش ذخیره و بازیابی دیتا است. برای ذخیره سازی از طریق وب و مرورگر، راه‌های مختلف زیادی چون webStorage ,Indexed DB ,Sqlite ,NeDB, و ... وجود دارند.

Sqlite دیتابیس مناسبی برای برنامه‌های چندسکویی است و عموما به عنوان اولین گزینه استفاده می‌شود. برای کار با این دیتابیس، ما از ماژول sql.js که یکی از ماژول‌های معروف در جاوااسکریپت است، استفاده می‌کنیم. برای نصب آن از طریق npm، به شکل زیر اقدام می‌کنیم:
npm install sql.js --save
سپس کد همیشگی زیر را برای آغاز، در فایل index.js وارد می‌کنیم:
const{app,BrowserWindow}=require("electron");

let win;
function onLoad()
{
  win=new BrowserWindow({
    width:800,
    height:600
  });
  win.loadURL(`file://${__dirname}/index.html`);
}

app.on("ready",onLoad());
  ابتدا باید بررسی کنیم که آیا دیتابیس از قبل موجود است یا خیر و اگر موجود نبود، برای اولین بار آن را بسازیم. برای بررسی وجود یک فایل نیز می‌توانیم از چند دستور مختلف استفاده کنیم. path.exists و fs.exists، دو عدد از آن‌ها می‌باشند که هر دوی آنان به صورت غیرهمزمان هستند و پارمتر اولشان نام فایل، به همراه مسیر (یا تنها مسیر) است و پارامتر دوم هم یک تابع callback است که به عنوان پارامتر، جواب را بر می‌گرداند. برای استفاده از حالت همزمان، عبارت Sync را به انتهای نام متدها اضافه کنید. نحوه استفاده از آن به شکل زیر است:
var path=require("path");
path.exists('filepath",(status)=>
{
....
});
var status=path.existsSync("file");
//===============================
var fs=require("fs");
fs.exists('filepath",(status)=>
{
....
});
var status=path.existsSync("file");
ولی بهتر است بدانید که تاریخ انقضای دو دستور بالا سر آمده است و الان از دستور fs.stat استفاده می‌شود. این متد هم به دو شکل همزمان و غیرهمزمان وجود دارد:
fs.stat('foo.txt', function(err, stat) {
    if(err == null) {
        console.log('فایل موجوده');
    } else if(err.code == 'ENOENT') {
        // فایل وجود نداره
        fs.writeFile('log.txt', 'Some log\n');
    } else {
        //خطای دیگری رخ داده است
    }
});
برای استفاده از حالت همزمان هم کد را به شکل زیر بنویسید:
try {
  stats = fs.statSync(path);
  console.log("File exists.");
}
catch (e) {
  console.log("File does not exist.");
}
سپس کد زیر را در فرآیند اصلی وارد می‌کنیم:
const fs = require('fs');
const sql = require('sql.js');

  dbPath = './mydb.sqlite';
  dbExists=false;

try {
  dbExists = fs.statSync(dbPath);
}
catch (e) {
}

if(!dbExists)
{
  //create Database
var sqlStr=fs.readFileSync("./sql.txt");
var db = new sql.Database();
db.run(String(sqlStr));

//write to disk
var data=db.export();
var buffer=new Buffer(data);
fs.writeFileSync(dbPath,buffer);
}
else{
  var buffer = fs.readFileSync(dbPath);
  var db = new sql.Database(buffer);
}
ابتدا بررسی می‌کنیم که آیا فایلی با نام test.sqlite در مسیر جاری است یا خیر. در صورتی که نباشد، کد داخل شرط اجرا می‌شود. در اولین خط شرط، فایلی را با نام sql.txt که شامل محتوای زیر است، می‌خوانیم:
CREATE TABLE numbers (
    id     INT          PRIMARY KEY
                        UNIQUE
                        NOT NULL,
    fname  VARCHAR (20) NOT NULL,
    lname  VARCHAR (30) NOT NULL,
    number VARCHAR (15) NOT NULL
);
insert into numbers values(1,'ali','yeganeh','03111223344');
insert into numbers values(2,'xxx','yyy','45454555');
سپس در خطوط بعدی دیتابیس جدیدی را ایجاد می‌کنیم و دستور sql را با متد run اجرا می‌کنیم. دستوراتی که متد run اجرا می‌کند، شامل خروجی نیستند. پس دستوراتی را که نیاز به خروجی ندارند، به این متد بسپارید. سپس از این دیتابیس، یک شیء خروجی را دریافت میکنم و با بافر کردن، آن را در یک فایل ذخیره می‌کنیم. در صورتی هم که از قبل دیتابیس وجود داشته باشد، بافر خوانده شده را مستقیما به سازنده Database می‌دهیم.
بعد از آن نیاز است تا دیتابیس در دسترس Render Process‌ها قرار بگیرد که در مقاله "شیوه کدنویسی در الکترون " در مورد global صحبت کرده‌ایم و نحوه استفاده از آن را فرا گرفتیم:
global.db=db;

در پایان اجرای برنامه لازم است که دیتابیس توسط دستور close بسته شود. سپس کد زیر را در رویداد windows-all-closed می‌نویسیم:
app.on('window-all-closed', () => {
  db.close();
  if (process.platform !== 'darwin') {
    app.quit();
  }
});
در این کد گفته‌ایم که موقعی که تمام پنجره‌های برنامه بسته شدند، دیتابیس را نیز ببند.
(چند مورد خارج از بحث): کد بعدی که مورد استفاده قرار گرفته است و در مقالات قبلی در مورد آن صحبت نکرده‌ایم این است که در سیستم‌های مک، وضعیت به این قرار است که اگر شما برنامه را ببیندید، آن برنامه بسته نشده و در پس زمینه فعال است و می‌توانید آن را از طریق dock اطراف صفحه، مجددا فعال کنید. ولی با نوشتن کد بالا، ما این وضعیت را اعلام کرده‌ایم که اگر تمامی پنجره‌ها بسته شدند، کل برنامه را ببند.

همچنین بسیار خوب است که کد زیر را هم همیشه اضافه کنید:
win.on('closed', () => {
    win = null;
  });
موقعی که پنجره مربوطه بسته شود، متغیری که به پنجره اشاره می‌کند، در حافظه می‌ماند. پس بهتر است که این مقدار حافظه را رها کنید تا Garbage Collector اقدام به حذف آن در حافظه کند.
پس اگر این کد را نوشتید، وضعیت سیستم عامل مک را به خاطر داشته باشید و مجبور هستید کد زیر را نیز اضافه کنید:
app.on('activate', () => {
  if (win === null) {
    createWindow();
  }
});
در این صورت اگر کاربر پنجره را از طریق Dock مجددا فعال کرد، پنجره برنامه شما نیز مجددا نمایش داده خواهدشد.

بعد از اینکه دیتابیس را به شیء global دادیم، در صفحه html کد زیر را وارد می‌کنیم:
<html>
  <head>
    <script src="./jquery.min.js"></script>
    <link href="./bootstrap-3.3.6-dist/css/bootstrap.min.css" rel="stylesheet"></link>
    <meta charset="utf-8">
    <title></title>
    <script>
    const {remote}=require("electron");
    let db=remote.getGlobal("db");
    </script>
  </head>
  <body>

<table id="people" class="table table-hover table-striped">
<th>
  <tr>
    <td>First Name</td>
    <td>last Name</td>
    <td>Phone Number</td>
  </tr>
</th>
<tbody>

</tbody>
</table>
  </body>
</html>
در این کد یک جدول داریم و قصد ما این است که آن دو سطر را که در ابتدا اضافه کردیم، در آن نمایش دهیم. چیزی که در این کدها قابل ملاحظه است، این است که ما از بسته‌های بوت استرپ و جی کوئری استفاده می‌کنیم. در ادامه کدهای زیر را در تگ اسکریپت  وارد می‌کنیم:
$(document).ready(()=>
{
  //show data
var tableBody=$("#people");

db.each("select * from numbers",(row)=>{
  let rowTemplate=`<tr><td>${row.fname}</td><td>${row.lname}</td><td>${row.number}</td></tr>`;
  tableBody.append(rowTemplate);
});
متد each برای مواقعی مناسب است که شما تعدادی سطر را برای بازگشت دارید و callback آن، به ازای هر سطر اجرا می‌شود و با استفاده از یک template string سطر سازی را انجام داده و آن را با استفاده از توابع جی کوئری به انتهای جدول اضافه می‌کنیم.
حال وقت آن رسیده است که خروجی کار را ببینیم. پس کد npm start را اجرا می‌کنیم. همانطور که می‌بینید خروجی به راحتی نمایش داده می‌شود. در مقاله بعدی بیشتر در این مورد صحبت می‌کنیم.
مطالب
راهبری در Silverlight به کمک الگوی MVVM

مقدمات راهبری (Navigation) در سیلورلایت را در اینجا می‌توانید مطالعه نمائید : +
مطلبی را که در فصل فوق نخواهید یافت در مورد نحوه‌ی بکارگیری الگوی MVVM جهت پیاده سازی Navigation در یک برنامه‌ی سیلورلایت است؛ علت آن هم به این بر می‌گردد که این فصل پیش از مباحث Binding مطرح شد.

صورت مساله:
یکی از اصول MVVM این است که در ViewModel‌ نباید ارجاعی از View وجود داشته باشد (ViewModel باید در بی‌خبری کامل از وجود اشیاء UI و ارجاع مستقیم به آن‌ها طراحی شود)، اما برای پیاده سازی مباحث Navigation نیاز است به نحوی به شیء Frame قرار داده شده در صفحه‌ی اصلی یا قالب اصلی برنامه دسترسی یافت تا بتوان درخواست رهنمون شدن به صفحات مختلف را صادر کرد. اکنون چکار باید کرد؟

راه حل:
یکی از راه حل‌های جالبی که برای این منظور وجود دارد استفاده از امکانات کلاس Messenger مجموعه‌ی MVVM Light toolkit است. از طریق ViewModel برنامه، آدرس صفحه‌ی مورد نظر را به صورت یک پیغام به View مورد نظر ارسال می‌کنیم و سپس View برنامه که به این پیغام‌ها گوش فرا می‌دهد، پس از دریافت آدرس مورد نظر، نسبت به فراخوانی تابع Navigate شیء Frame رابط کاربری برنامه اقدام خواهد کرد. به این صورت ViewModel برنامه به View خود جهت اعمال راهبری برنامه، گره نخواهد خورد.

روش پیاده سازی:
ابتدا ساختار پروژه را در نظر بگیرید (این شکل دگرگون شده‌ی Solution explorer مرتبط است با productivity tools نصب شده):



در پوشه‌ی Views ، دو صفحه اضافه شده‌اند که توسط user control ایی به نام menu لیست شده و راهبری خواهند شد. مونتاژ نهایی هم در MainPage.xaml صورت می‌گیرد.
کدهای XAML‌ مرتبط با منوی ساده برنامه به شرح زیر هستند (Menu.xaml) :

<UserControl x:Class="MvvmLight6.Views.Menu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MvvmLight6.ViewModels" mc:Ignorable="d"
FlowDirection="RightToLeft" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:MenuViewModel x:Key="vmMenuViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource vmMenuViewModel}}">
<HyperlinkButton Content="صفحه یک" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page1.xaml"
/>
<HyperlinkButton Content="صفحه دو" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page2.xaml"
/>
</StackPanel>
</UserControl>

کدهای ViewModel مرتبط با این View که کار Command گردانی را انجام خواهد داد به شرح زیر است:
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLight6.ViewModels
{
public class MenuViewModel
{
public RelayCommand<string> DoNavigate { set; get; }

public MenuViewModel()
{
DoNavigate = new RelayCommand<string>(doNavigate);
}

private static void doNavigate(string url)
{
Messenger.Default.Send(url, "MyNavigationService");
}
}
}

تمام آیتم‌های منوی فوق یک روال را صدا خواهند زد : DoNavigate . تنها تفاوت آن‌ها در CommandParameter ارسالی به RelayCommand ما است که حاوی آدرس قرارگیری فایل‌های صفحات تعریف شده است. این آدرس‌ها با کمک امکانات کلاس Messenger مجموعه‌ی MVVM light toolkit به View اصلی برنامه ارسال می‌گردند.
کدهای XAML مرتبط با MainPage.xaml به شرح زیر هستند:

<UserControl x:Class="MvvmLight6.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:usr="clr-namespace:MvvmLight6.Views"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="268" />
</Grid.ColumnDefinitions>
<usr:Menu Grid.Column="1" />
<sdk:Frame Margin="5"
Name="frame1"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Grid.Column="0" />
</Grid>
</UserControl>

و کار دریافت پیغام‌ها (یا همان آدرس صفحات جهت انجام راهبری) و عکس العمل نشان دادن به آن‌ها توسط کدهای ذیل صورت خواهد گرفت:
using System;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLight6
{
public partial class MainPage
{
public MainPage()
{
registerMessenger();
InitializeComponent();
}

private void registerMessenger()
{
Messenger.Default.Register<string>(this, "MyNavigationService", doNavigate);
}

private void doNavigate(string uri)
{
frame1.Navigate(new Uri(uri, UriKind.Relative));
}
}
}

ابتدا یک Messenger در اینجا رجیستر می‌شود و سپس به ازای هر بار دریافت پیغامی با token مساوی MyNavigationService ، متد doNavigate فراخوانی خواهد گردید.
کدهای این مثال را از اینجا می‌توانید دریافت کنید.

مطالب
EF Code First #2

در قسمت قبل با تنظیمات و قراردادهای ابتدایی EF Code first آشنا شدیم، هرچند این تنظیمات حجم کدنویسی ابتدایی راه اندازی سیستم را به شدت کاهش می‌دهند، اما کافی نیستند. در این قسمت نگاهی سطحی و مقدماتی خواهیم داشت بر امکانات مهیا جهت تنظیم ویژگی‌های مدل‌های برنامه در EF Code first.

تنظیمات EF Code first توسط اعمال متادیتای خواص

اغلب متادیتای مورد نیاز جهت اعمال تنظیمات EF Code first در اسمبلی System.ComponentModel.DataAnnotations.dll قرار دارند. بنابراین اگر مدل‌های خود را در اسمبلی و پروژه class library جداگانه‌ای تعریف و نگهداری می‌کنید (مثلا به نام DomainClasses)، نیاز است ابتدا ارجاعی را به این اسمبلی به پروژه جاری اضافه نمائیم. همچنین تعدادی دیگر از متادیتای قابل استفاده در خود اسمبلی EntityFramework.dll قرار دارند. بنابراین در صورت نیاز باید ارجاعی را به این اسمبلی نیز اضافه نمود.
همان مثال قبل را در اینجا ادامه می‌دهیم. دو کلاس Blog و Post در آن تعریف شده (به این نوع کلاس‌ها POCO – the Plain Old CLR Objects نیز گفته می‌شود)، به همراه کلاس Context که از کلاس DbContext مشتق شده است. ابتدا دیتابیس قبلی را دستی drop کنید. سپس در کلاس Blog، خاصیت public int Id را مثلا به public int MyTableKey تغییر دهید و پروژه را اجرا کنید. برنامه بلافاصله با خطای زیر متوقف می‌شود:

One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmEntityType: : EntityType 'Blog' has no key defined.

زیرا EF Code first در این کلاس خاصیتی به نام Id یا BlogId را نیافته‌است و امکان تشکیل Primary key جدول را ندارد. برای رفع این مشکل تنها کافی است ویژگی Key را به این خاصیت اعمال کنیم:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample01.Models
{
public class Blog
{
[Key]
public int MyTableKey { set; get; }

همچنین تعدادی ویژگی دیگر مانند MaxLength و Required را نیز می‌توان بر روی خواص کلاس اعمال کرد:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample01.Models
{
public class Blog
{
[Key]
public int MyTableKey { set; get; }

[MaxLength(100)]
public string Title { set; get; }

[Required]
public string AuthorName { set; get; }

public IList<Post> Posts { set; get; }
}
}

این ویژگی‌ها دو مقصود مهم را برآورده می‌سازند:
الف) بر روی ساختار بانک اطلاعاتی تشکیل شده تاثیر دارند:

CREATE TABLE [dbo].[Blogs](
[MyTableKey] [int] IDENTITY(1,1) NOT NULL,
[Title] [nvarchar](100) NULL,
[AuthorName] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY CLUSTERED
(
[MyTableKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

همانطور که ملاحظه می‌کنید در اینجا طول فیلد Title به 100 تنظیم شده است و همچنین فیلد AuthorName اینبار NOT NULL است. به علاوه primary key نیز بر اساس ویژگی Key اعمالی تعیین شده است.
البته برای اجرای کدهای تغییر کرده مدل، فعلا بانک اطلاعاتی قبلی را دستی می‌توان حذف کرد تا بتوان به ساختار جدید رسید. در مورد جزئیات مبحث DB Migration در قسمت‌های بعدی مفصلا بحث خواهد شد.

ب) اعتبار سنجی اطلاعات پیش از ارسال کوئری به بانک اطلاعاتی
برای مثال اگر در حین تعریف وهله‌ای از کلاس Blog، خاصیت AuthorName مقدار دهی نگردد، پیش از اینکه رفت و برگشتی به بانک اطلاعاتی صورت گیرد، یک validation error را دریافت خواهیم کرد. یا برای مثال اگر طول اطلاعات خاصیت Title بیش از 100 حرف باشد نیز مجددا در حین ثبت اطلاعات، یک استثنای اعتبار سنجی را مشاهده خواهیم کرد. البته امکان تعریف پیغام‌های خطای سفارشی نیز وجود دارد. برای این حالت تنها کافی است پارامتر ErrorMessage این ویژگی‌ها را مقدار دهی کرد. برای مثال:
[Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
public string AuthorName { set; get; }

نکته‌ی مهمی که در اینجا وجود دارد، وجود یک اکوسیستم هماهنگ و سازگار است. این نوع اعتبار سنجی هم با EF Code first هماهنگ است و هم برای مثال در ASP.NET MVC به صورت خودکار جهت اعتبار سنجی سمت سرور و کلاینت یک مدل می‌تواند مورد استفاده قرار گیرد و مفاهیم و روش‌های مورد استفاده در آن نیز یکی است.


تنظیمات EF Code first به کمک Fluent API

اگر علاقمند به استفاده از متادیتا، جهت تعریف قیود و ویژگی‌های خواص کلاس‌های مدل خود نیستید، روش دیگری نیز در EF Code first به نام Fluent API تدارک دیده شده است. در اینجا امکان تعریف همان ویژگی‌ها توسط کدنویسی نیز وجود دارد، به علاوه اعمال قیود دیگری که توسط متادیتای مهیا قابل تعریف نیستند.
محل تعریف این قیود، کلاس Context که از کلاس DbContext مشتق شده است، می‌باشد و در اینجا، کار با تحریف متد OnModelCreating شروع می‌شود:

using System.Data.Entity;
using EF_Sample01.Models;

namespace EF_Sample01
{
public class Context : DbContext
{
public DbSet<Blog> Blogs { set; get; }
public DbSet<Post> Posts { set; get; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasKey(x => x.MyTableKey);
modelBuilder.Entity<Blog>().Property(x => x.Title).HasMaxLength(100);
modelBuilder.Entity<Blog>().Property(x => x.AuthorName).IsRequired();

base.OnModelCreating(modelBuilder);
}
}
}

به کمک پارامتر modelBuilder، امکان دسترسی به متدهای تنظیم کننده ویژگی‌های خواص یک مدل یا موجودیت وجود دارد. در اینجا چون می‌توان متدها را به صورت یک زنجیره به هم متصل کرد و همچنین حاصل نهایی شبیه به جمله بندی انگلیسی است، به آن Fluent API یا API روان نیز گفته می‌شود.
البته در این حالت امکان تعریف ErrorMessage وجود ندارد و برای این منظور باید از همان data annotations استفاده کرد.


نحوه مدیریت صحیح تعاریف نگاشت‌ها به کمک Fluent API

OnModelCreating محل مناسبی جهت تعریف حجم انبوهی از تنظیمات کلاس‌های مختلف مدل‌های برنامه نیست. در حد سه چهار سطر مشکلی ندارد اما اگر بیشتر شد بهتر است از روش زیر استفاده شود:

using System.Data.Entity;
using EF_Sample01.Models;
using System.Data.Entity.ModelConfiguration;

namespace EF_Sample01
{
public class BlogConfig : EntityTypeConfiguration<Blog>
{
public BlogConfig()
{
this.Property(x => x.Id).HasColumnName("MyTableKey");
this.Property(x => x.RowVersion).HasColumnType("Timestamp");
}
}


با ارث بری از کلاس EntityTypeConfiguration،‌ می‌توان به ازای هر کلاس مدل، تنظیمات را جداگانه انجام داد. به این ترتیب اصل SRP یا Single responsibility principle نقض نخواهد شد. سپس برای استفاده از این کلاس‌های Config تک مسئولیتی به نحو زیر می‌توان اقدام کرد:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new BlogConfig());




نحوه تنظیمات ابتدایی نگاشت کلاس‌ها به بانک اطلاعاتی در EF Code first

الزامی ندارد که EF Code first حتما با یک بانک اطلاعاتی از نو تهیه شده بر اساس پیش فرض‌های آن کار کند. در اینجا می‌توان از بانک‌های اطلاعاتی موجود نیز استفاده کرد. اما در این حالت نیاز خواهد بود تا مثلا نام جدولی خاص با کلاسی مفروض در برنامه، یا نام فیلدی خاص که مطابق استانداردهای نامگذاری خواص در سی شارپ تعریف نشده، با خاصیتی در یک کلاس تطابق داده شوند. برای مثال اینبار تعاریف کلاس Blog را به نحو زیر تغییر دهید:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample01.Models
{
[Table("tblBlogs")]
public class Blog
{
[Column("MyTableKey")]
public int Id { set; get; }

[MaxLength(100)]
public string Title { set; get; }

[Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
public string AuthorName { set; get; }

public IList<Post> Posts { set; get; }

[Timestamp]
public byte[] RowVersion { set; get; }
}
}

در اینجا فرض بر این است که نام جدول متناظر با کلاس Blog در بانک اطلاعاتی مثلا tblBlogs است و نام خاصیت Id در بانک اطلاعاتی مساوی فیلدی است به نام MyTableKey. چون نام خاصیت را مجددا به Id تغییر داده‌ایم، دیگر ضرورتی به ذکر ویژگی Key وجود نداشته است. برای تعریف این دو از ویژگی‌های Table و Column جهت سفارشی سازی نام‌های خواص و کلاس استفاده شده است.
یا اگر در کلاس خود خاصیتی محاسبه شده بر اساس سایر خواص، تعریف شده است و قصد نداریم آن‌را به فیلدی در بانک اطلاعاتی نگاشت کنیم، می‌توان از ویژگی NotMapped برای مزین سازی و تعریف آن کمک گرفت.
به علاوه اگر از نام پیش فرض کلید خارجی تشکیل شده خرسند نیستید می‌توان به کمک ویژگی ForeignKey، نسبت به تعریف مقداری جدید مطابق تعاریف یک بانک اطلاعاتی موجود، اقدام کرد.
همچنین خاصیت دیگری به نام RowVersion در اینجا اضافه شده که با ویژگی TimeStamp مزین گردیده است. از این خاصیت ویژه برای بررسی مسایل همزمانی ثبت اطلاعات در EF استفاده می‌شود. به علاوه بانک اطلاعاتی می‌تواند به صورت خودکار آن‌را در حین ثبت مقدار دهی کند.
تمام این تغییرات را به کمک Fluent API نیز می‌توان انجام داد:

modelBuilder.Entity<Blog>().ToTable("tblBlogs");
modelBuilder.Entity<Blog>().Property(x => x.Id).HasColumnName("MyTableKey");
modelBuilder.Entity<Blog>().Property(x => x.RowVersion).HasColumnType("Timestamp");



تبدیل پروژه‌های قدیمی EF به کلاس‌های EF Code first به صورت خودکار

روش متداول کار با EF از روز اول آن، مهندسی معکوس خودکار اطلاعات یک بانک اطلاعاتی و تبدیل آن به یک فایل EDMX بوده است. هنوز هم می‌توان از این روش در اینجا نیز بهره جست. برای مثال اگر قصد دارید یک پروژه قدیمی را تبدیل به نمونه جدید Code first کنید، یا یک بانک اطلاعاتی موجود را مهندسی معکوس کنید، بر روی پروژه در Solution explorer کلیک راست کرده و گزینه Add|New Item را انتخاب کنید. سپس از صفحه ظاهر شده، ADO.NET Entity data model را انتخاب کرده و در ادامه گزینه «Generate from database» را انتخاب کنید. این روال مرسوم کار با EF Database first است.
پس از اتمام کار به entity data model designer مراجعه کرده و بر روی صفحه کلیک راست نمائید. از منوی ظاهر شده گزینه «Add code generation item» را انتخاب کنید. سپس در صفحه باز شده از لیست قالب‌های موجود، گزینه «ADO.NET DbContext Generator» را انتخاب نمائید. این گزینه به صورت خودکار اطلاعات فایل EDMX قدیمی یا موجود شما را تبدیل به کلاس‌های مدل Code first معادل به همراه کلاس DbContext معرف آن‌ها خواهد کرد.

روش دیگری نیز برای انجام اینکار وجود دارد. نیاز است افزونه‌ی به نام Entity Framework Power Tools را دریافت کنید. پس از نصب، از منوی Entity Framework آن گزینه‌ی «Reverse Engineer Code First» را انتخاب نمائید. در اینجا می‌توان مشخصات اتصال به بانک اطلاعاتی را تعریف و سپس نسبت به تولید خودکار کدهای مدل‌ها و DbContext مرتبط اقدام کرد.



استراتژی‌های مقدماتی تشکیل بانک اطلاعاتی در EF Code first

اگر مثال این سری را دنبال کرده باشید، مشاهده کرده‌اید که با اولین بار اجرای برنامه، یک بانک اطلاعاتی پیش فرض نیز تولید خواهد شد. یا اگر تعاریف ویژگی‌های یک فیلد را تغییر دادیم، نیاز است تا بانک اطلاعاتی را دستی drop کرده و اجازه دهیم تا بانک اطلاعاتی جدیدی بر اساس تعاریف جدید مدل‌ها تشکیل شود که ... هیچکدام از این‌ها بهینه نیستند.
در اینجا دو استراتژی مقدماتی را در حین آغاز یک برنامه می‌توان تعریف کرد:

System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
// or
System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseAlways<Context>());

می‌توان بانک اطلاعاتی را در صورت تغییر اطلاعات یک مدل به صورت خودکار drop کرده و نسبت به ایجاد نمونه‌ای جدید اقدام کرد (DropCreateDatabaseIfModelChanges)؛ یا در حین آزمایش برنامه همیشه (DropCreateDatabaseAlways) با شروع برنامه، ابتدا باید بانک اطلاعاتی drop شده و سپس نمونه جدیدی تولید گردد.
محل فراخوانی این دستور هم باید در نقطه آغازین برنامه، پیش از وهله سازی اولین DbContext باشد. مثلا در برنامه‌های وب در متد Application_Start فایل global.asax.cs یا در برنامه‌های WPF در متد سازنده کلاس App می‌توان بانک اطلاعاتی را آغاز نمود.
البته الزامی به استفاده از کلاس‌های DropCreateDatabaseIfModelChanges یا DropCreateDatabaseAlways وجود ندارد. می‌توان با پیاده سازی اینترفیس IDatabaseInitializer از نوع کلاس Context تعریف شده در برنامه، همان عملیات را شبیه سازی کرد یا سفارشی نمود:

public class MyInitializer : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
if (context.Database.Exists() ||
context.Database.CompatibleWithModel(throwIfNoMetadata: false))
context.Database.Delete();

context.Database.Create();
}
}

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

System.Data.Entity.Database.SetInitializer(new MyInitializer());


نکته:
اگر از یک بانک اطلاعاتی موجود استفاده می‌کنید (محیط کاری) و نیازی به پیش فرض‌های EF Code first ندارید و همچنین این بانک اطلاعاتی نیز نباید drop شود یا تغییر کند، می‌توانید تمام این پیش فرض‌ها را با دستور زیر غیرفعال کنید:

Database.SetInitializer<Context>(null);

بدیهی است این دستور نیز باید پیش از ایجاد اولین وهله از شیء DbContext فراخوانی شود.


همچنین باید درنظر داشت که در آخرین نگارش‌های پایدار EF Code first، این موارد بهبود یافته‌اند و مبحثی تحت عنوان DB Migration ایجاد شده است تا نیازی نباشد هربار بانک اطلاعاتی drop شود و تمام اطلاعات از دست برود. می‌توان صرفا تغییرات کلاس‌ها را به بانک اطلاعاتی اعمال کرد که به صورت جداگانه، در قسمتی مجزا بررسی خواهد شد. به این ترتیب دیگر نیازی به drop بانک اطلاعاتی نخواهد بود. به صورت پیش فرض در صورت از دست رفتن اطلاعات یک استثناء را سبب خواهد شد (که توسط برنامه نویس قابل تنظیم است) و در حالت خودکار یا دستی با تنظیمات ویژه قابل اعمال است.



تنظیم استراتژی‌های آغاز بانک اطلاعاتی در فایل کانفیگ برنامه

الزامی ندارد که حتما متد Database.SetInitializer را دستی فراخوانی کنیم. با اندکی تنظیم فایل‌های app.config و یا web.config نیز می‌توان نوع استراتژی مورد استفاده را تعیین کرد:

<appSettings>
<add key="DatabaseInitializerForType MyNamespace.MyDbContextClass, MyAssembly"
value="MyNamespace.MyInitializerClass, MyAssembly" />
</appSettings>

<appSettings>
<add key="DatabaseInitializerForType MyNamespace.MyDbContextClass, MyAssembly"
value="Disabled" />
</appSettings>

یکی از دو حالت فوق باید در قسمت appSettings فایل کانفیگ برنامه تنظیم شود. حالت دوم برای غیرفعال کردن پروسه آغاز بانک اطلاعاتی و اعمال تغییرات به آن، بکار می‌رود.
برای نمونه در مثال جاری، جهت استفاده از کلاس MyInitializer فوق، می‌توان از تنظیم زیر نیز استفاده کرد:

<appSettings>
<add key="DatabaseInitializerForType EF_Sample01.Context, EF_Sample01"
value="EF_Sample01.MyInitializer, EF_Sample01" />
</appSettings>



اجرای کدهای ویژه در حین تشکیل یک بانک اطلاعاتی جدید

امکان سفارشی سازی این آغاز کننده‌های پیش فرض نیز وجود دارد. برای مثال:

public class MyCustomInitializer : DropCreateDatabaseIfModelChanges<Context>
{
protected override void Seed(Context context)
{
context.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
context.Database.ExecuteSqlCommand("CREATE INDEX IX_title ON tblBlogs (title)");
base.Seed(context);
}
}

در اینجا با ارث بری از کلاس DropCreateDatabaseIfModelChanges یک آغاز کننده سفارشی را تعریف کرده‌ایم. سپس با تحریف متد Seed آن می‌توان در حین آغاز یک بانک اطلاعاتی، تعدادی رکورد پیش فرض را به آن افزود. کار ذخیره سازی نهایی در متد base.Seed انجام می‌شود.
برای استفاده از آن اینبار در حین فراخوانی متد System.Data.Entity.Database.SetInitializer، از کلاس MyCustomInitializer استفاده خواهیم کرد.
و یا توسط متد context.Database.ExecuteSqlCommand می‌توان دستورات SQL را مستقیما در اینجا اجرا کرد. عموما دستوراتی در اینجا مدنظر هستند که توسط ORMها پشتیبانی نمی‌شوند. برای مثال تغییر collation یک ستون یا افزودن یک ایندکس و مواردی از این دست.


سطح دسترسی مورد نیاز جهت فراخوانی متد Database.SetInitializer

استفاده از متدهای آغاز کننده بانک اطلاعاتی نیاز به سطح دسترسی بر روی بانک اطلاعاتی master را در SQL Server دارند (زیرا با انجام کوئری بر روی این بانک اطلاعاتی مشخص می‌شود، آیا بانک اطلاعاتی مورد نظر پیشتر تعریف شده است یا خیر). البته این مورد حین کار با SQL Server CE شاید اهمیتی نداشته باشد. بنابراین اگر کاربری که با آن به بانک اطلاعاتی متصل می‌شویم سطح دسترسی پایینی دارد نیاز است Persist Security Info=True را به رشته اتصالی اضافه کرد. البته این مورد را پس از انجام تغییرات بر روی بانک اطلاعاتی جهت امنیت بیشتر حذف کنید (یا به عبارتی در محیط کاری Persist Security Info=False باید باشد).

Server=(local);Database=yourDatabase;User ID=yourDBUser;Password=yourDBPassword;Trusted_Connection=False;Persist Security Info=True


تعیین Schema و کاربر فراخوان دستورات SQL

در EF Code first به صورت پیش فرض همه چیز بر مبنای کاربری با دسترسی مدیریتی یا dbo schema در اس کیوال سرور تنظیم شده است. اما اگر کاربر خاصی برای کار با دیتابیس تعریف گردد که در هاست‌های اشتراکی بسیار مرسوم است، دیگر از دسترسی مدیریتی dbo خبری نخواهد بود. اینبار نام جداول ما بجای dbo.tableName مثلا someUser.tableName می‌باشند و عدم دقت به این نکته، اجرای برنامه را غیرممکن می‌سازد.
برای تغییر و تعیین صریح کاربر متصل شده به بانک اطلاعاتی اگر از متادیتا استفاده می‌کنید، روش زیر باید بکارگرفته شود:

[Table("tblBlogs", Schema="someUser")]    
public class Blog

و یا در حالت بکارگیری Fluent API به نحو زیر قابل تنظیم است:

modelBuilder.Entity<Blog>().ToTable("tblBlogs", schemaName:"someUser");






نظرات مطالب
معرفی و استفاده از DDL Triggers در SQL Server
با سلام و احترام؛ همانطور که در متن به عرض رسانده شده:
" از عبارت  ON  برای مشخص کردن محدوده Trigger در سطح SQL Instance (در این صورت ON All SERVER نوشته می‌شود) و یا در سطح Database (در این حالت ON DATABASE نوشته می‌شود)  استفاده می‌شود و از عبارت FOR  برای مشخص کردن رویداد یا گروه رویدادی که سبب فراخوانی  Trigger می‌شود، استفاده  خواهد شد.  "
در خصوص مثالی که اشاره کردید، به نظرم می‌رسد از Trigger برای این منظور استفاده نمی‌شود (در حوزه‌ی بکاپ و ریستور)، شاید اگر قصدتان به منظور ثبت log و ... بایست از Auditing استفاده کنید. به این منظور در Auditing با توجه به جدول زیر می‌توان اقدام به ثبت موارد نمود:
Server-Level Audit Action Groups  
Action group name 
 Event Class   Description 
 BACKUP_RESTORE_GROUP   Audit Backup/Restore  
 یک دستور Backup یا Restore صادر شود  
به طور مختصر Auditing به شرح زیر است:
 بررسی SQL Server Audit
 بازبینی (Auditing) شامل پیگیری و ثبت رویدادهایی است که در سطح SQL Instance و یا Database‌های روی یک سیستم اتفاق می‌افتد. چندین سطح برای Auditing در SQL Server وجود دارد که به صلاحدید و نیازمندی‌های نصب شما وابسته است. شما می‌توانید گروه اقدامات بازبینی سرویس دهنده (server audit action groups) را به ازای هر SQL Instance و گروه اقدامات بازبینی بانک اطلاعاتی (database audit action groups) را  به ازای هر بانک اطلاعاتی ثبت کنید. رویداد Audit هر زمان عملی که مورد رسیدگی قرار گرفته اتفاق افتد، رخ می‌دهد.
تا پیش از SQL SERVER 2008، شما باید از خصیصه‌های متعددی برای انجام یک مجموعه کامل بازبینی (Auditing) برای نمونه DDL Trigger، DML Trigger و SQL Trace، بر روی یک SQL Instance استفاده می‌کردید.
SQL SERVER 2008، همه قابلیت‌های Auditing را روی یک audit specification ترکیب می‌کند. Audit Specification با تعریف یک شی بازبینی (audit object) در سطح سرویس دهنده برای ثبت (logging) یک دنباله بازبینی (audit trial) آغاز می‌شود. توجه شود که بایست یک شیء بازبینی ایجاد کنید پیش از اینکه یک Server Audit Specification و یا Database Audit Specification ایجاد کنید.
Server Audit Specification، گروه اقدامات در سطح سرویس دهنده را جمع آوری می‌کند که با رویدادهای وسیعی فعال می‌شوند، این گروه اقدامات تحت عنوان  Server-Level Audit Action Groups تشریح شده اند. شما می‌توانید یک Server Audit Specification را به ازای هر Audit ایجاد کنید چرا که هر دو در محدوده یک SQL Instance ایجاد می‌شوند.
Database Audit Specification، گروه اقدامات در سطح بانک اطلاعاتی را جمع آوری می‌کند که با رویدادهای وسیعی فعال می‌شود. این گروه اقدامات تحت عنوان‌های Database-Level Audit Action Groups و Database-Level Audit Actions تشریح شده اند.می توانید یک Database Audit Specification را به ازای هر Audit در بانک اطلاعاتی SQL Server ایجاد کنید.
همچنین می‌توانید هر گروه اقدامات بازبینی(audit action groups) یا رویدادهای بازبینی(audit events) را به یک Database Audit Specification اضافه کنید. گروه اقدامات بازبینی، گروه اقدامات از پیش تعریف شده ای هستند و رویدادهای بازبینی اقدامات تجزیه ناپذیری هستند که توسط موتور بانک اطلاعاتی مورد رسیدگی قرار می‌گیرند، هر دو در محدوده بانک اطلاعاتی (Database) هستند. این اقدامات برای Audit فرستاده می‌شوند تا در Target (که می‌تواند یک فایل، Windows Security Log و یا Windows Application Log باشد) ذخیره شوند. برای نوشتن در Windows Security Log لازم است که Service Account سرویس دهنده شما به Policy، Generate security audits اضافه شده باشد، به صورت پیش فرض Local System، Local Service و Network Service بخشی از این Policy می‌باشند. پس از اینکه Audit را ایجاد و فعال کردید، Target ورودی‌ها را دریافت خواهد کرد. 
Server-Level Audit Action Groups (گروه اقدامات بازبینی در سطح سرویس دهنده)
این گروه اقدامات به گروه رویداد Security Audit شبیه هستند. به طور خلاصه این گروه اقدامات، اقداماتی را که در یک SQL Instance شامل می‌شوند، در بر می‌گیرد. برای مثال اگر گروه اقدام مناسب با Server Audit Specification اضافه شده باشد هر شیء در هر Schema که مورد دستیابی قرار می‌گیرد، ثبت می‌شود. اقدامات در سطح سرویس دهنده به شما اجازه نمی‌دهد که جزئیات اقدامات در سطح بانک اطلاعاتی را فیلتر کنید. یک بازبینی در سطح بانک اطلاعاتی برای انجام به جزئیات دقیق فیلتر کردن نیاز دارد، برای مثال اجرای دستور Select روی جدول Customers برای login هایی که در گروه Employee هستند.
Database-Level Audit Action Groups (گروه اقدامات بازبینی در سطح بانک اطلاعاتی)
این گروه اعمال به کلاس‌های رویداد Security Audit  شبیه هستند.
Database-Level Audit Actions
اقدامات در سطح بانک اطلاعاتی، اقدامات بازبینی خاصی را به طور مستقیم روی Database، Schema و اشیاء Schema (از قبیل جداول، View ها، رویه‌های ذخیره شده، توابع و ... ) فراهم می‌کند. این اقدامات برای فیلدها (Columns) صدق نمی‌کنند.
Audit-Level Audit Action Groups
شما می‌توانید اقداماتی را که در فرآیند Auditing هستند، بازبینی کنید که می‌تواند در محدوده سرویس دهنده یا بانک اطلاعاتی باشد. در محدوده بانک اطلاعاتی تنها برای database audit specification رخ می‌دهد.
  جهت بررسی بیشتر به این لینک مراجعه شود.