اشتراک‌ها
قالب معماری پیاز برای net core.

این قالب که توسط آقای علیرضا ارومند تهیه شده به شما این امکان رو میده تا سولوشن یا پروژه‌ها را بتونید با استفاده از دستور dotnet new ایجاد نمایید.

dotnet new --install Aro.SolutionTemplates::1.0.0


قالب معماری پیاز برای net core.
مطالب
مسیریابی در Angular - قسمت اول - معرفی
مسیریابی در +Angular 2 به همراه قابلیت‌های فراوان و ویژه‌ای است که تعدادی از آن‌ها را تابحال در این سایت بررسی کرده‌ایم. مورد مقدماتی اول، نیاز به بازنویسی کامل دارد، مورد دوم جهت آشنایی با ساختار Angular CLI مفید است و مورد سوم یکی از مباحث تکمیلی آن است. به همین جهت در طی یک سری، ویژگی‌های متعدد سیستم مسیریابی +Angular 2 را از ابتدا بررسی خواهیم کرد.


مسیریابی در +Angular 2

عموما از مسیریابی جهت حرکت بین Viewهای مختلف برنامه استفاده می‌شود، اما کارهای بیشتری را نیز می‌توان با آن انجام داد؛ مانند ارسال اطلاعات، به مسیریابی‌ها، پیش بارگذاری اطلاعات، جهت نمایش در Viewها، گروه بندی و محافظت از مسیریابی‌ها، پویانمایی و انیمیشن و همچنین بهبود کارآیی، با بارگذاری async مسیرهای مختلف.
کار سیستم مسیریاب +Angular 2 زمانی شروع می‌شود که تغییری را در آدرس درخواستی از برنامه مشاهده می‌کند؛ یا از طریق درخواست آدرسی توسط مرورگر و یا هدایت به قسمتی خاص، از طریق کدنویسی. سپس مسیریاب به آرایه‌ی تنظیم شده‌ی مسیرهای سیستم مراجعه می‌کند تا بتواند تطابقی را بین آدرس درخواستی و یکی از کلیدهای تنظیم شده‌ی در آن پیدا کند. در این حالت اگر تطابقی یافت نشود، کارمسیریابی خاتمه خواهد یافت. در غیراینصورت کار ادامه یافته و سپس مسیریاب، محافظ‌های مسیر درخواستی را بررسی می‌کند تا مشخص شود که آیا کاربر مجاز به هدایت به این قسمت خاص از برنامه هست یا خیر؟ در صورت مثبت بودن پاسخ، مرحله‌ی بعد، پیش بارگذاری اطلاعات درخواستی جهت نمایش View مرتبط است. در ادامه کامپوننت متناظر با مسیریابی فعالسازی می‌شود. سپس قالب این کامپوننت را در قسمتی که توسط router-outlet مشخص می‌گردد، جایگذاری کرده و نمایش می‌دهد.


تعریف مسیر پایه یا Base path

اولین مرحله‌ی کار با سیستم مسیریابی +Angular 2، تعریف یک base path است. مسیرپایه، به زیرپوشه‌ای اشاره می‌کند که برنامه‌ی ما در آن قرار گرفته‌است:
 www.mysite.com/myapp
در این مثال، مسیرپایه برنامه، /myapp/ است.
مسیریاب از این مسیرپایه جهت ساخت آدرس‌های مسیریابی استفاده می‌کند. مقدار آن نیز به صورت ذیل در فایل index.html برنامه، درست پس از تگ head تعیین می‌گردد:
<!DOCTYPE html>
<html>
<head>
    <base href="/">
در اینجا با ذکر / ، به عنوان مسیرپایه، به ریشه‌ی سایت اشاره شده‌است. اگر برنامه‌ی شما قرار است در یک زیرپوشه قرار بگیرد، در این حالت نحوه‌ی تعیین این زیر پوشه به صورت ذیل خواهد بود:
<base href="/myapp/">


تعیین مسیرپایه جهت ارائه‌ی نهایی

استفاده از مسیر پایه / برای حالت توسعه و همچنین زمانیکه برنامه‌ی نهایی شما در ریشه‌ی سایت توزیع می‌شود، بسیار مناسب است. اما اگر برای حالت توسعه از مقدار / و برای حالت توزیع از مقدار /myapp/ بخواهید استفاده کنید، مدام نیاز خواهید داشت تا فایل index.html نهایی سایت را ویرایش کنید. برای این منظور Angular CLI دارای پرچمی است به نام base-href:
 > ng build --base-href /myapp/
به این ترتیب در زمان ساخت و توزیع نهایی برنامه توسط Angular CLI، کار تنظیم خودکار مسیرپایه نیز صورت خواهد گرفت.
حالت پیش فرض تولید برنامه‌های Angular توسط Angular CLI، تنظیم مسیرپایه در فایل src\index.html به صورت خودکار به / می‌باشد.


تعریف مسیریاب Angular

مسیریاب Angular در ماژولی به نام RouterModule قرار گرفته‌است و باید در ابتدای کار import شود. این ماژول شامل سرویسی است جهت هدایت کاربران به صفحات دیگر و مدیریت URLها، تنظیماتی برای تعریف جزئیات مسیریابی‌ها و تعدادی دایرکتیو که برای فعالسازی و نمایش مسیرها از آن‌ها استفاده می‌شود. برای مثال دایرکتیو RouterLink آن یک المان قابل کلیک HTML را به مسیر و کامپوننتی خاص در برنامه متصل می‌کند. RouterLinkActive، شیوه‌نامه‌ها را به لینک فعال انتساب می‌دهد و RouterOutlet محل نمایش قالب کامپوننت فعال شده را مشخص می‌کند.

یک مثال: در ادامه، یک پروژه‌ی جدید مبتنی بر Angular CLI را به نام angular-routing-lab به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
 > ng new angular-routing-lab --routing
به علاوه قصد استفاده‌ی از بوت استرپ را نیز داریم. به همین جهت ابتدا به ریشه‌ی پروژه وارد شده و سپس دستور ذیل را صادر کنید تا بوت استرپ نیز نصب شود و پرچم save آن سبب به روز رسانی فایل package.json نیز گردد:
 > npm install bootstrap --save
پس از آن نیاز است به فایل angular-cli.json. مراجعه کرده و شیوه‌نامه‌ی بوت استرپ را تعریف کنیم:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
به این ترتیب به صورت خودکار این شیوه نامه به همراه توزیع برنامه حضور خواهد داشت و نیازی به تعریف مستقیم آن در فایل index.html نیست.

در ادامه اگر به فایل src\app\app-routing.module.ts مراجعه کنید، یک چنین محتوایی را خواهید یافت:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    children: []
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا برای شروع به کار با سیستم مسیریابی، RouterModule معرفی شده‌است. ابتدا import آن‌را مشاهده می‌کنید و سپس این ماژول به قسمت imports مربوط به NgModule اضافه شده‌است.
می‌توان این قسمت را خلاصه کرد و فایل app-routing.module.ts را نیز حذف کرد و سپس import لازم و تعریف ماژول آن‌را به ماژول آغازین برنامه یا همان src\app\app.module.ts نیز منتقل کرد. اما پس از مدتی تنظیمات مسیریابی آن، فایل ماژول اصلی برنامه را بیش از اندازه شلوغ خواهند کرد. بنابراین Angular-CLI تصمیم به ایجاد یک ماژول مستقل را برای تعریف تنظیمات مسیریابی برنامه گرفته‌است. سپس تعریف آن را به فایل src\app\app.module.ts به صورت خودکار اضافه می‌کند:
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  imports: [
    AppRoutingModule
  ],

اگر به قسمت import مربوط به NgModule فایل src\app\app-routing.module.ts دقت کنید، این ماژول به همراه متد forRoot معرفی شده‌است.
@NgModule({
  imports: [RouterModule.forRoot(routes)],
ماژول مسیریابی به همراه دو متد ذیل است:
الف) forRoot
- کار آن تعریف دایرکتیوهای مسیریابی، مدیریت تنظیمات مسیریابی و ثبت سرویس مسیریابی است.
- نکته‌ی مهم اینجا است که متد forRoot تنها یکبار باید در طول عمر یک برنامه تعریف شود.
- این متد آرایه‌ای از تنظیمات مسیریابی‌های تعریف شده را دریافت می‌کند.
ب) forChild
- کار آن تعریف دایرکتیوهای مسیریابی و مدیریت تنظیمات مسیریابی است؛ اما سرویس مسیریابی را مجددا ثبت نمی‌کند.
- از این متد در جهت تعریف مسیریابی‌های ماژول‌های ویژگی‌های مختلف برنامه و نظم بخشیدن به آن‌ها استفاده می‌شود.

بنابراین زمانیکه از forRoot استفاده می‌شود، سرویس مسیریابی تنها یکبار ثبت خواهد شد و تنها یک وهله از آن موجود خواهد بود. در ادامه هر کدام از ماژول‌های دیگر برنامه می‌توانند forChild خاص خودشان را داشته باشند.

اکنون تمام کامپوننت‌های قید شده‌ی در قسمت declaration، امکان دسترسی به دایرکتیوهای مسیریابی را پیدا می‌کنند. همچنین از آنجائیکه AppRoutingModule به همراه متد forRoot است، سرویس مسیریابی نیز در کل برنامه قابل دسترسی است.


تنظیمات اولیه مسیریابی برنامه

آرایه‌ی const routes: Routes فایل src\app\app-routing.module.ts در ابتدای کار خالی است. در اینجا کار تعریف URL segments و سپس اتصال آن‌ها به کامپوننت‌های متناظری جهت فعالسازی و نمایش قالب آن‌ها صورت می‌گیرد. این نمایش نیز در محل router-outlet تعریف شده‌ی در فایل src\app\app.component.html انجام می‌شود:
<h1>
  {{title}}
</h1>
<router-outlet></router-outlet>
به ازای هر router-outlet، تنها یک مسیر را در هر زمان می‌توان نمایش داد. بنابراین با فعال شدن هر کامپوننت، قالب آن، قالب کامپوننت پیشین را در router-outlet جایگزین خواهد کرد. امکان تعریف router-outletهای دیگری نیز وجود دارند که به آن‌ها secondary routes گفته می‌شود.

در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوش‌آمد گویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه می‌کنیم:
>ng g c welcome
>ng g c PageNotFound
که سبب ایجاد کامپوننت‌های src\app\welcome\welcome.component.ts و src\app\page-not-found\page-not-found.component.ts خواهند شد، به همراه به روز رسانی خودکار فایل src\app\app.module.ts جهت تکمیل قسمت declarations آن:
@NgModule({
  declarations: [
    AppComponent,
    WelcomeComponent,
    PageNotFoundComponent
  ],

سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { WelcomeComponent } from './welcome/welcome.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: 'welcome', component: WelcomeComponent },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا در ابتدا دو کامپوننت جدید تعریف شده، import شده‌اند.

یک نکته: افزونه‌ی auto import، کار تعریف کامپوننت‌ها را در VSCode بسیار ساده می‌کند و امکان تشکیل خودکار قسمت import را با ارائه‌ی یک intellisense به همراه دارد.

سپس کار تکمیل آرایه‌ی Routes انجام شده‌است. همانطور که مشاهده می‌کنید، این آرایه متشکل است از اشیایی که به همراه خاصیت path و سایر پارامترهای مورد نیاز هستند.
کار خاصیت path، تعیین URL segment متناظری است که این مسیریابی را فعال می‌کند. برای مثال اولین شیء تعریف شده با آدرس‌هایی مانند www.mysite.com/welcome متناظر است.
 { path: 'welcome', component: WelcomeComponent },
در اغلب حالات به همراه path، خاصیت کامپوننت متناظر با آن مسیر نیز ذکر می‌شود؛ مانندWelcomeComponent ایی که در اینجا مقدار دهی شده‌است. هنگامیکه این مسیریابی فعال می‌شود، قالب این کامپوننت‌است که در router-outlet نمایش داده خواهد شد (فایل src\app\welcome\welcome.component.html).

چند نکته:
- در حین تعریف مقدار خاصیت path، هیچ / آغاز کننده‌ای تعریف نشده‌است.
- مقدار خاصیت path، حساس به کوچکی و بزرگی حروف است.
- WelcomeComponent تعریف شده، یک رشته نیست و ارجاعی را به کامپوننت مرتبط دارد. به همین جهت نیاز به import statement ابتدایی را دارد و وجود آن توسط کامپایلر بررسی می‌شود.


تعیین مسیریابی پیش فرض سایت

اما زمانیکه برنامه برای بار اول بارگذاری می‌شود، چطور؟ در این حالت هیچ URL segment ایی وجود ندارد. بنابراین برای تنظیم مسیرپیش فرض سایت، خاصیت path، به یک رشته‌ی خالی همانند دومین شیء تنظیمات مسیریابی، تنظیم می‌شود:
   { path: '', redirectTo: 'welcome', pathMatch: 'full' },
در اینجا زمانیکه کاربر، ریشه‌ی سایت را درخواست می‌کند، به کامپوننت welcome هدایت خواهد شد. در این تنظیم خاصیت pathMatch نحوه‌ی تطابق با خاصیت path را مشخص می‌کند. مقدار full نیز به این معنا است که اگر تمام URL segment دریافتی خالی بود، از این تنظیم استفاده کن.


مدیریت مسیریابی آدرس‌های ناموجود در سایت

تنظیم سومی را نیز در اینجا مشاهده می‌کنید:
 { path: '**', component: PageNotFoundComponent },
** به این معنا است که اگر مسیریاب موفق به تطابق URL segment دریافتی، با هیچکدام از pathهای تعریف شده نبود، از این تنظیم استفاده کند (catch all). برای مثال اگر کاربر آدرس www.mysite.com/someAddr را درخواست کند، چون URL segment آن در آرایه‌ی تنظیمات، تعریف نشده‌است، می‌خواهیم یک صفحه‌ی یافت نشد را نمایش دهیم.

یک نکته: ترتیب مسیریابی‌ها در آرایه‌ی تعریف آن‌ها اهمیت دارد. در اینجا از استراتژی «اولین تطابق یافته‌، برنده خواهد بود» استفاده می‌شود. بنابراین تنظیم ** باید در انتهای لیست ذکر شود؛ در غیراینصورت هیچکدام از مسیریابی‌های تعریف شده‌ی پس از آن پردازش نخواهند شد.


مدیریت تغییرات آدرس‌های برنامه

در طول عمر برنامه ممکن است نیاز به تغییر آدرس‌های برنامه باشد. برای مثال بجای مسیر welcome مسیر home نمایش داده شود:
const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
در این تنظیمات جدید برنامه، مسیر home به WelcomeComponent اشاره می‌کند. اما جهت هدایت آدرس‌های قدیمی welcome، به این آدرس جدید home می‌توان از redirectTo، مانند دومین شیء مسیریابی اضافه شده‌ی به آرایه‌ی تنظیمات استفاده کرد. به این ترتیب هم تغییرات آرایه‌ی Routes به حداقل می‌رسد و هم آدرس‌های ذخیره شده‌ی توسط کاربران هنوز هم معتبر خواهند بود.

نکته: redirectToها قابلیت تعریف زنجیره‌ای را ندارند. به این معنا که اگر ریشه‌ی سایت درخواست شود، ابتدا به مسیر welcome هدایت خواهیم شد. مسیر welcome هم یک redirectTo دیگر به مسیر home را دارد. اما در اینجا کار به این redirectTo دوم نخواهد رسید و این پردازش، زنجیره‌ای نیست. بنابراین مسیریابی پیش‌فرض را نیز باید ویرایش کرد و به home تغییر داد:
const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
به این ترتیب دیگر درخواست زنجیره‌وار redirectToها رخ نخواهد داد.

نکته: redirectToها می‌توانند local و یا absolute باشند. تعریف محلی آن‌ها مانند ذکر home و welcome در اینجا است و تنها سبب تغییر یک URL segment می‌شود. اما اگر در ابتدای مقادیر redirectToها یک / قرار دهیم، به معنای تعریف یک مسیر مطلق است و کل URL را جایگزین می‌کند.


تعیین محل نمایش قالب‌های کامپوننت‌ها

زمانیکه یک کامپوننت فعالسازی می‌شود، قالب آن در router-outlet نمایش داده خواهد شد. برای این منظور فایل src\app\app.component.html را گشوده و به نحو ذیل تغییر دهید:
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <a class="navbar-brand">{{title}}</a>
    <ul class="nav navbar-nav">
      <li>
        <a [routerLink]="['/home']">Home</a>
      </li>
    </ul>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>
در اینجا دایرکتیو router-outlet محلی است که قالب کامپوننت فعالسازی شده، نمایش داده می‌شود. برای مثال اگر کاربر بر روی لینک Home کلیک کند، کامپوننت متناظر با آن از طریق تنظیمات مسیریابی یافت شده و فعالسازی می‌شود و سپس قالب آن در محل router-outlet رندر خواهد شد.

یک نکته: چون کامپوننت welcome از طریق مسیریابی نمایش داده می‌شود و دیگر به صورت مستقیم با درج تگ selector آن در صفحه فعالسازی نخواهد شد، می‌توان به تعریف کامپوننت آن مراجعه کرده و selector آن‌را حذف کرد.
 @Component({
//selector: 'app-welcome',
templateUrl: './welcome.component.html',
styleUrls: ['./welcome.component.css']
})

تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد:


اگر به آدرس تنظیم شده‌ی در مرورگر دقت کنید، http://localhost:4200/home آدرسی است که در ابتدای نمایش سایت نمایان خواهد شد. علت آن نیز به تنظیم مسیریابی پیش فرض سایت برمی‌گردد.
و اگر یک مسیر غیرموجود را درخواست دهیم، قالب کامپوننت PageNotFound ظاهر می‌شود:



هدایت کاربران به قسمت‌های مختلف برنامه

کاربران را می‌توان به روش‌های مختلفی به قسمت‌های گوناگون برنامه هدایت کرد؛ برای مثال با کلیک بر روی المان‌های قابل کلیک HTML و سپس اتصال آن‌ها به کامپوننت‌های برنامه. استفاده‌ی کاربر از bookmark مرورگر و یا ورود مستقیم و دستی آدرس قسمتی از برنامه و یا کلیک بر روی دکمه‌های forward و back مرورگر. تنها مورد اول است که نیاز به تنظیم دارد و سایر قسمت‌ها به صورت خودکار مدیریت خواهند شد. نمونه‌ی آن‌را نیز با تعریف لینک Home پیشتر مشاهده کردید:
  <a [routerLink]="['/home']">Home</a>
- در اینجا با استفاده از یک دایرکتیو ویژه به نام routerLink، المان‌های قابل کلیک HTML را به خاصیت‌های path تعریف شده‌ی در تنظیمات مسیریابی برنامه متصل می‌کنیم. routerLink یک attribute directive است؛ بنابراین می‌توان آن‌را به یک المان قابل کلیک anchor tag، مانند مثال فوق نسبت داد.
- زمانیکه کاربر بر روی این لینک کلیک می‌کند، اولین path متناظر با routerLink یافت شده و فعالسازی خواهد شد.
- علت تعریف مقدار routerLink به صورت [] این است که آرایه‌ی پارامترهای لینک را مشخص می‌کند. بنابراین چون آرایه‌است، نیاز به [] دارد. اولین پارامتر این آرایه مفهوم root URL segment را دارد. در اینجا حتما نیاز است URL segment را با یک / شروع کرد. به علاوه باید دقت داشت که خاصیت path تنظیمات مسیریابی، حساس به حروف کوچک و بزرگ است. بنابراین این مورد را باید در اینجا نیز مدنظر داشت.
- پارامترهای دیگر routerLink می‌توانند مفهوم پارامترهای این segment و یا حتی segments دیگری باشند.

یک نکته: چون در مثال فوق، آرایه‌ی تعریف شده تنها دارای یک عضو است، آن‌را می‌توان به صورت ذیل نیز خلاصه نویسی کرد (one-time binding):
 <a routerLink="/home">Home</a>


تفاوت بین آدرس‌های HTML 5 و Hash-based

زمانیکه مسیریاب Angular کار پردازش آدرس‌های رسیده را انجام می‌دهد، اینکار در سمت کلاینت صورت می‌گیرد و تنها URL segment مدنظر را تغییر داده و این درخواست را به سمت سرور ارسال نمی‌کند. به همین جهت سبب reload صفحه نمی‌شود. دو روش در اینجا جهت مدیریت سمت کلاینت آدرس‌ها قابل استفاده است:
الف) HTML 5 Style
- آدرسی مانند http://localhost:4200/home، یک آدرس به شیوه‌ی HTML 5 است. در اینجا مسیریاب Angular با استفاده از HTML 5 history pushState سبب به روز رسانی History مرورگر شده و آدرس‌ها را بدون ارسال درخواستی به سمت سرور، در همان سمت کلاینت تغییر می‌دهد.
- این روش حالت پیش فرض Angular است و نحوه‌ی نمایش آن بسیار طبیعی به نظر می‌رسد.
- در اینجا URL rewriting سمت سرور نیز جهت هدایت آدرس‌ها، به برنامه‌ی Angular ضروری است. برای مثال زمانیکه کاربری آدرس http://localhost:4200/home را مستقیما در مرورگر وارد می‌کند، این درخواست ابتدا به سمت سرور ارسال خواهد شد و چون چنین صفحه‌ای در سمت سرور وجود ندارد، پیغام خطای 404 را دریافت می‌کند. اینجا است که URL rewriting سمت سرور به فایل index.html برنامه، جهت مدیریت یک چنین حالت‌هایی ضروری است.
برای نمونه اگر از وب سرور IIS استفاده می‌کنید، تنظیم ذیل را به فایل web.config در قسمت system.webServer اضافه کنید (کار کرد آن هم وابسته‌است به نصب و فعالسازی ماژول URL Rewrite بر روی IIS):
<rewrite>
    <rules>
        <rule name="Angular 2+ pushState routing" stopProcessing="true">
            <match url=".*" />
                <conditions logicalGrouping="MatchAll">
                    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                    <add input="{REQUEST_FILENAME}" pattern=".*\.[\d\w]+$" negate="true" />
                    <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
                </conditions>
                <action type="Rewrite" url="/index.html" />
        </rule>
    </rules>
</rewrite>

ب) Hash-based
- آدرسی مانند http://localhost:4200/#/home یک آدرس به شیوه‌ی Hash-based بوده و مخصوص مرورگرهایی است بسیار قدیمی که از HTML 5 پشتیبانی نمی‌کنند. اینبار قطعات قرار گرفته‌ی پس از علامت # دارای نام URL fragments بوده و قابلیت پردازش در سمت کلاینت را دارا می‌باشند.
- اگر علاقمند به استفاده‌ی از این روش هستید، نیاز است خاصیت useHash را به true تنظیم کنید:
@NgModule({
   imports: [RouterModule.forRoot(routes, { useHash: true })],
-  این روش نیازی به URL rewriting سمت سرور ندارد. از آنجائیکه سرور هرچیزی را که پس # باشد ندید خواهد گرفت و سعی در یافتن آن‌، در سمت سرور نخواهد کرد.
مطالب
ساخت کتابخانه‌های عمومی جاوا اسکریپتی توسط Angular CLI
این روزها ساخت کتابخانه‌های مدرن جاوا اسکریپتی که نیازهای انواع و اقسام توسعه دهندگان آن‌ها را پوشش دهند، مشکل است. این کتابخانه‌ها باید دارای بسته‌های مختلفی با پشتیبانی از ES5 و ES2015 به بعد باشند؛ همچنین ارائه‌ی متادیتای مخصوص TypeScript را نیز پشتیبانی کنند. به علاوه مباحث کارآیی، minification و tree shaking (حذف کدهای مرده) را نیز مدنظر داشته باشید.
پیشتر مطلبی را در مورد ساخت کتابخانه‌های مخصوص Angular را به کمک Angular CLI، در این سایت مطالعه کرده بودید. در این مطلب فرض بر این است که شما توسعه دهنده‌ی Angular «نیستید»، اما قصد دارید با استفاده از ابزار Angular CLI، کتابخانه‌ی جاوا اسکریپتی عمومی بسیار مدرنی را با پشتیبانی از تمام موارد یاد شده، تولید کنید.


ساخت قالب آغازین کتابخانه به کمک Angular CLI

برای تبدیل کتابخانه‌های جاوا اسکریپتی خود به قالب مدرنی که در مقدمه عنوان شد، نیاز به ابزاری جهت خودکارسازی فرآیند‌های آن داریم و این ویژگی‌ها مدتی است که به ابزار Angular CLI اضافه شده‌اند و همانطور که عنوان شد، مخاطب این مطلب، توسعه دهندگان عمومی JavaScript است و نه صرفا توسعه دهندگان Angular. به همین جهت نیاز است ابتدا این ابزار را نصب کرد:
npm install -g @angular/cli
برای اجرای دستور فوق در خط فرمان، ابتدا باید آخرین نگارش nodejs را نیز نصب کرده باشید.
پس از نصب Angular CLI، از آن جهت ساخت قالب تولید کتابخانه‌های TypeScript ای استفاده می‌کنیم:
 ng new my-math-app
این دستور یک قالب پروژه‌ی آغازین Angular را ایجاد کرده و همچنین وابستگی‌های npm آن‌را نیز نصب می‌کند (بنابراین نیاز است به اینترنت نیز متصل باشید). البته ممکن است در حین اجرای این دستور سؤالاتی مبنی بر ایجاد مسیریابی و یا انتخاب بین css و sass نیز پرسیده شود. این موارد برای کار ما در اینجا مهم نیستند و هر پاسخی را که مایل بودید، ارائه دهید. در این مطلب ما کاری به این قالب نخواهیم داشت. فقط هدف ما افزودن یک کتابخانه‌ی جدید به آن است.
بنابراین پس از اجرای دستور فوق، از طریق خط فرمان به پوشه‌ی my-math-app وارد شده و سپس دستور زیر را اجرا کنید:
 ng generate library ts-math-example
این دستور، قالب آغازین یک کتابخانه‌ی جدید TypeScript ای را به پروژه‌ی Angular ما با نام ts-math-example اضافه می‌کند. اکنون می‌توانیم از این قالب جهت توسعه‌ی کتابخانه‌ی مدرن جاوا اسکریپتی خود استفاده کنیم.


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

اکنون که به لطف Angular CLI، قالب آغازین ساخت یک کتابخانه‌ی TypeScript ای را داریم، می‌توانیم شروع به تکمیل آن کنیم. برای این منظور به پوشه‌ی my-math-app\projects\ts-math-example\src\lib مراجعه کرده و تمام فایل‌های پیش‌فرض آن‌را حذف کنید. این‌ها قالب‌های ساخت کتابخانه‌های Angularای هستند که ما در اینجا کاری به آن‌ها نداریم:


همچنین می‌توان به فایل my-math-app\projects\ts-math-example\package.json نیز مراجعه کرد (فایل package.json پروژه‌ی کتابخانه) و قسمت peerDependencies آن را که به Angular اشاره می‌کند نیز حذف نمود.

سپس یک فایل خالی math.ts را به پوشه‌ی یاد شده اضافه می‌کنیم:


با این محتوا:
export function add(num1: number, num2: number) {
    return num1 + num2;
}
کتابخانه‌ی ما کار ساده‌ی جمع زدن اعداد را انجام می‌دهد.

در ادامه نیاز است این ماژول را به فایل my-math-app\projects\ts-math-example\src\public-api.ts معرفی کرد تا به عنوان API قابل دسترسی کتابخانه، در دسترس قرار گیرد:
/*
* Public API Surface of ts-math-example
*/
export * from './lib/math';
هر فایلی که قرار است توسط کتابخانه‌ی ما در معرض دید عموم قرارگیرد، باید در فایل public_api.ts عمومی شود.

در حین توسعه‌ی کتابخانه خود،‌جهت اطمینان از صحت کامپایل برنامه، دستور ng build ts-math-example --watch را در پوشه‌ی my-math-app صادر کنید. کار آن کامپایل مداوم پروژه‌ی کتابخانه بر اساس تغییرات داده شده‌است. حاصل این کامپایل نیز در پوشه‌ی my-math-app\dist\ts-math-example قرار می‌گیرد:


این همان خروجی مدرنی است که در ابتدای بحث از آن صحبت کردیم و شامل کتابخانه‌های ES5 و ES2015 به بعد و همچنین ارائه‌ی متادیتای مخصوص TypeScript نیز هست.


کامپایل و انتشار نهایی کتابخانه

پس از تکمیل کتابخانه‌ی خود، اکنون می‌توانیم آن‌را به سایت npm، برای استفاده‌ی سایرین ارسال کنیم. برای این منظور باید مراحل زیر طی شوند:
ابتدا فایل package.json واقع در ریشه‌ی پوشه‌ی ts-math-example را جهت تعریف اطلاعات این کتابخانه، تکمیل کنید. سپس دستورات زیر را در ریشه‌ی پروژه‌ی اصلی صادر کنید:
ng build ts-math-example --prod
cd dist/ts-math-example
npm publish
دستور اول کتابخانه را در حالت production تولید می‌کند که حداکثر بهینه سازی‌ها را به همراه دارد.
با دستور دوم به پوشه‌ی خروجی کتابخانه وارد شده و دستور سوم، آن‌را به سایت npm ارسال می‌کند.

استفاده کننده‌ی از کتابخانه‌ی ما (این استفاده کننده می‌تواند هر نوع پروژه‌ی جاوا اسکریپتی اعم از Angular ،React ،Vue ،ES6 ،TypeScript و غیره باشد) ابتدا با دستور npm install ts-math-example --save آن‌را نصب و به پروژه‌ی خود اضافه کرده و سپس به نحو زیر می‌تواند از آن استفاده کند:
 import { add } from '@myuser/ts-math-example';
مطالب
اجرای برنامه‌های ASP.NET توسط Mono در Ubuntu
در ادامه مباحث بررسی اجرای برنامه‌های دات نت بر روی لینوکس، قصد داریم برنامه‌های ASP.NET را به کمک Mono 3.0 و یک وب سرور لینوکسی، بر روی Ubuntu اجرا کنیم.


پیشنیازها
دو پروژه خالی ASP.NET Web forms و ASP.NET MVC را در VS.NET تحت ویندوز ایجاد نمائید. آن‌ها را یکبار کامپایل کرده و اجرا کنید. سپس فایل‌‌های آن‌ها را به ubuntu منتقل کنید (پوشه‌های bin پروژه‌ها فراموش نشوند؛ خصوصا نگارش MVC که به همراه یک سری کتابخانه جانبی است).
برای انتقال فایل‌ها به لینوکس، اگر از VMWare workstation برای اجرا و آزمایش Ubuntu استفاده می‌کنید، کپی و paste مستقیم فایل‌ها از ویندوز به درون ماشین مجازی لینوکس پشتیبانی می‌شود.


نصب وب سرور آزمایشی مونو یا XSP
اگر نیاز به یک وب سرور آزمایشی، چیزی شبیه به وب سرور توکار VS.NET داشتید، پروژه XSP جهت این نوع آزمایشات ایجاد شده است.
پس از نصب آن (که به همراه همان بسته PPA قسمت قبل، هم اکنون بر روی سیستم شما نصب است)، در ترمینال لینوکس، با استفاده از دستور cd به ریشه وب سایت خود وارد شوید، سپس دستور xsp4 را اجرا کنید تا وب سرور xsp4 مشغول هاست سایت شما شود (برای اجرا در مسیر  /opt/bin/xsp4 نصب شده است).


اجرای برنامه ASP.NET Web forms 4 توسط XSP
بدون هیچ مشکل خاصی در همان ابتدای کار اجرا شد (البته باید دقت داشت که لینوکس به کوچکی و بزرگی حروف حساس است. یعنی حتما باید Default.aspx وارد شود و نه default.aspx):



اجرای برنامه ASP.NET MVC 4 توسط XSP
اجرا نشد! پیام می‌دهد که
 "Missing method System.Web.Security.FormsAuthentication::get_IsEnabled() in assembly System.Web.dll
و یا
Compiler Error Message: CS1703: An assembly with the same identity `mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' 
has already been imported. Consider removing one of the references
علت اینجا است که XSP4 همراه با نسخه PPA، قدیمی است. بنابراین باید نسخه اصلی را از مخزن کد آن دریافت و کامپایل کنیم. پیشنیازهای اینکار مانند Git و Mono، در قسمت قبل دریافت شدند. سپس فرامین ذیل را در خط فرمان لینوکس اجرا کنید:
 git clone git://github.com/mono/xsp.git
cd xsp
./autogen.sh --prefix=/opt
make
sudo make install
پس از کامپایل، اگر این نگارش جدید را اجرا کنید، به خطای ذیل خواهید رسید:
 System.IO.FileNotFoundException: Could not load file or assembly XSP, Version=3.0.0.0
برای رفع این مشکل باید اینبار وب سرور جدید را با فرمان sudo یا دسترسی مدیریتی اجرا کنید تا مشکل برطرف شود.
البته من سورس دریافت شده را در خود monodevelop کامپایل کردم (فایل sln آن‌را در monodevelop باز کرده و پروژه را build کنید). در این حالت دو فایل Mono.WebServer.dll و Mono.WebServer.XSP.exe در پوشه  xsp/src/Mono.WebServer.XSP/bin/Debug ظاهر می‌شوند.
یکی دیگر از دلایل ظاهر شدن خطای فوق، نیاز به نصب این دو فایل در GAC است که به نحو زیر قابل انجام می‌باشد:
 cd xsp/src/Mono.WebServer.XSP/bin/Debug
sudo gacutil -i Mono.WebServer.XSP.exe
sudo gacutil -i Mono.WebServer.dll
بعد این دو فایل dll و exe را در پوشه برنامه MVC خود کپی کنید و سپس دستور ذیل را اجرا نمائید:
 cd myMvcAppPath
sudo mono Mono.WebServer.XSP.exe
اینبار وب سرور جدید، روی پورت 9000 شروع به کار می‌کند. اکنون اگر در فایرفاکس آدرس http://localhost:9000 را باز کنید، برنامه اجرا شده اما به خطای ذیل خواهید رسید:
 CS0234: The type or namespace name `Helpers' does not exist in the namespace `System.Web'.
Are you missing an assembly reference?
برای رفع این مشکل باید اندکی فایل web.config برنامه را ویرایش کرد:
  <system.web> 
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Helpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />        
        <add assembly="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />        
      </assemblies>
    </compilation>
سعی بعدی ... اجرا نشد! با هر بار refresh صفحه یک خطای جدید نمایش می‌داد که ... Type خاصی را نمی‌تواند بارگذاری کند (به همراه نام اسمبلی مربوطه). برای رفع این مشکل dllهای ذیل را از پوشه bin پروژه MVC خود که از ویندوز به لینوکس کپی کرده‌اید، حذف کنید:
Microsoft.Web.Infrastructure.dll
System.Net.Http.dll
System.Net.Http.Formatting.dll
System.Web.Http.dll
System.Web.Http.WebHost.dll
این فایل‌ها توسط تیم Mono به صورت مستقل پیاده سازی شده‌اند و نیازی نیست تا از ویندوز به لینوکس کپی شوند.
بعد از حذف این فایل‌های اضافی، برنامه ASP.NET MVC نیز اجرا شد:



چند نکته تکمیلی
- نحوه تشخیص موجود بودن یک DLL خاص، در نگارش جاری Mono نصب شده:
 $ gacutil -l Microsoft.Web.Infrastructure
The following assemblies are installed into the GAC:
Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
Number of items = 1
- اگر می‌خواهید مطمئن شوید که تمام اسمبلی‌های موجود در GAC درست نصب شده‌اند یا خیر، فرمان ذیل را اجرا کنید:
cd /opt/lib/mono/gac # assuming this is your main gac
sudo find . */*/*.dll -exec gacutil -i '{}' \;
- در نسخه لینوکسی System.Web ممکن است یک سری از فضاهای نام هنوز موجود نباشند. لیست آن‌ها را در این آدرس می‌توانید مشاهده کنید:
مطالب
CoffeeScript #11

کامپایل خودکار CoffeeScript

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

در قسمت اول گفته شد، برای کامپایل فایل CoffeeScript با استفاده از coffee به صورت زیر عمل می‌کردیم:

coffee --compile --output lib src
همانطور که در مثال بالا مشاهده می‌کنید، تمامی فایل‌های coffee. در داخل پوشه src را کامپایل می‌کنید و فایل‌های جاوااسکریپت تولید شده را در پوشه lib ذخیره می‌کنید.
حال به کامپایل خودکار CoffeeScript توجه کنید.

Cake

Cake یک سیستم فوق العاده ساده برای کامپایل خودکار است که مانند Make و Rake عمل می‌کند. این کتابخانه همراه پکیج coffee-script npm نصب می‌شود و برای استفاده با فراخوانی cake اجرا می‌شود.

برای ایجاد فایل tasks در cake که Cakefile نامیده می‌شود، می‌توان از خود CoffeeScript استفاده کرد. برای اجرای cake با استفاده از دستور [cake [task] [options می‌توان عمل کرد. برای اطلاع از لیست امکانات cake کافی است دستور cake را به تنهایی اجرا کنید.

وظایف را می‌توان با استفاده از تابع task، با ارسال نام و توضیحات (اختیاری) و تابع callback، تعریف کرد. به مثال زیر توجه کنید:

fs = require 'fs'

{print} = require 'sys'
{spawn} = require 'child_process'

build = (callback) ->
  coffee = spawn 'coffee', ['-c', '-o', 'lib', 'src']
  coffee.stderr.on 'data', (data) ->
    process.stderr.write data.toString()
  coffee.stdout.on 'data', (data) ->
    print data.toString()
  coffee.on 'exit', (code) ->
    callback?() if code is 0

task 'build', 'Build lib/ from src/', ->
  build()
همانطور که در مثال بالا مشاهده می‌کنید، تابع task را با نام build تعریف کردیم و با استفاده از دستور cake build می‌توان آن را اجرا نمود. پس از اجرا همانند مثال قبل تمامی فایل‌های CoffeeScript در پوشه‌ی src به فایل‌های جاوااسکریپت در پوشه lib تبدیل می‌شوند.
همان طور که مشاهده می‌کنید پس از تغییر در فایل CoffeeScript باید به صورت دستی cake build را فراخوانی کنیم که این دور از حالت ایده آل است.
خوشبختانه دستور coffee پارامتر دیگری به نام watch-- دارد که به وسیله آن می‌توان تمامی تغییرات یک پوشه را زیر نظر گرفت و در صورت نیاز دوباره کامپایل انجام شود. به مثال زیر توجه کنید:
 task 'watch', 'Watch src/ for changes', ->
    coffee = spawn 'coffee', ['-w', '-c', '-o', 'lib', 'src']
    coffee.stderr.on 'data', (data) ->
      process.stderr.write data.toString()
    coffee.stdout.on 'data', (data) ->
      print data.toString()
در صورتی که task ایی وابسته به task دیگری باشد، می‌توانید برای اجرای taskهای دیگر از دستور (invoke(name استفاده کنید. برای مثال یک task را به فایل Cakefile اضافه می‌کنیم که در آن ابتدا فایل index.html را باز کرده و سپس شروع به زیر نظر گرفتن پوشه src می‌کنیم.
task 'open', 'Open index.html', ->
  # First open, then watch
  spawn 'open', 'index.html'
  invoke 'watch'
همچنین می‌توانید با استفاده از تابع ()options ،option را برای taskها تعریف کنید.
option '-o', '--output [DIR]', 'output dir'

task 'build', 'Build lib/ from src/', ->
  # Now we have access to a `options` object
  coffee = spawn 'coffee', ['-c', '-o', options.output or 'lib', 'src']
  coffee.stderr.on 'data', (data) ->
    process.stderr.write data.toString()
  coffee.stdout.on 'data', (data) ->
    print data.toString()

Cake یک روش عالی برای انجام وظایف معمول به صورت خودکار است، مانند کامپایل فایل‌های CoffeeScript است. همچنین برای آشنایی بیشتر می‌توانید به سورس cake نگاهی کنید.

مطالب
طراحی گزارش در Stimulsoft Reports.Net – بخش 2
در این بخش هم به معرفی ابزار و امکانات این گزارش‌ساز خواهم پرداخت، که شامل بند Group , فیلد محاسباتی و کامپوننتهای Panel,Clone و همچنین نحوه ایجاد یک گزارش به صورت Master-Detail خواهد بود.
ابتدا برای شروع به شیوه‌ای که در بخش 1 بیان شد یک دیکشنری ایجاد کنید. بر روی صفحه طراحی رایت کلیک کنید و گزینه Design را انتخاب نمایید. فرم Page Setup ظاهر می‌شود، در پنل سمت چپ این فرم گزینه Columns را انتخاب نمایید. سپس مقادیر 2 و 0.1 را به ترتیب برای "Number of Columns" و "Column Gaps" ست نمایید. سپس بندهای Header , Group و Data را به ترتیب بر روی صفحه طراحی قرار دهید. بر روی بند Group دابل کلیک کنید و همانند تصویر زیر فیلد EmployeeID را از جدول Orders انتخاب نمایید.

حال بر روی بند Data دابل کلیک کنید و در بخش Data Source جدول Orders را انتخاب نمایید. از پنل ابزار یک کامپوننت Text بر روی بند Group قرار دهید و بر روی آن دابل کلیک کنید و در بخش Expression عبارت زیر را وارد نمایید.
Employee ID: {Orders.EmployeeID}  --- GLine: {GroupLine}
*GroupLine یک متغیر سیستمی است که شماره سطر جاری گروه را برمی‌گرداند.
از پنل Dictionary همانند تصویر زیر فیلدهای ShipName و ShipAddress را از جدول Orders بر روی بندهای Header و Data قرار دهید.

حال با مشاهده خروجی گزارش از سربرگ Preview شما شاهد یک صفحه دو ستونه خواهید بود که بر اساس فیلد EployeeID گروه بندی شده است. 
فیلد محاسباتی:
این نوع فیلد زمانی استفاده می‌شود که شما بخواهید در هر سطر از اطلاعات با توجه به مقادیر رکورد جاری محاسباتی را انجام داده و در گزارش نمایش دهید. برای اضافه کردن فیلد محاسباتی در پنل Dictionary بر روی جدول Order Details رایت کلیک کنید و گزینه New Calculated Column را انتخاب نمایید، همانند تصویر زیر

در فرم ظاهر شده مقادیر را به صورت زیر وارد نمایید:

Name: TotalPrice 
Alias: TotalPrice 
Type: decimal
Value: Order_Details.UnitPrice * Order_Details.Quantity
حال یک فیلد به نام TotalPrice به فیلدهای جدول Order Details اضافه شده است.

ایجاد گزارش به صورت Master-Detail
:
برای ایجاد چنین گزارشی نیاز به ارتباط بین جدولها می‌باشد. با توجه به نحوه ایجاد Connection برای این مثال، روابط بین جدولها انتقال داده نشده است ولی شما میتوانید رابطه بین جدولها را اضافه نمایید حتی اگر این رابطه در منبع اطلاعات وجود نداشته باشد. برای این مثال نیاز به دو رابطه بین جدول Orders Detail و جدولهای Orders, Products می‌باشد. برای انجام این کار کافیست در پنل Dictionary بر روی جدول Orders Detail رایت کلیک کنید و گزینه New Relation را انتخاب نماید. همانند تصاویر زیر مقادیر را ست نمایید.

حال بر روی صفحه طراحی بعد از بند DataBand1 به ترتیب بندهای Header و Data و Footer را اضافه نمایید. در بند HeaderBand2 چهار کامپوننت Text قرار دهید و به ترتیب از سمت چپ مقادیر زیر را در خصوصیت Text کامپوننتها قرار دهید.

ProductName
UnitPrice 
Quantity
TotalPrice
سپس بر روی بند DataBand2 دابل کلیک کنید در فرم Data Setup ابتدا در بخش Data Source جدول Orders Detail را انتخاب نمایید؛ در بخش Relation رابطه Orders را انتخاب نمایید و در نهایت در بخش Master Component بند DataBand1 را انتخاب نمایید. حال در بند DataBand2 چهار کامپوننت Text قرار دهید و به ترتیب از سمت چپ مقادیر زیر را در خصوصیت Text کامپوننتها قرار دهید.
{Order_Details.Products.ProductName}
{Order_Details.UnitPrice}
{Order_Details.Quantity}
{Order_Details.TotalPrice}
در بند FooterBand1 یک کامپوننت Text در زیر ستون TotalPrice قرار دهید و مقدار زیر را در خصوصیت Text آن قرار دهید.
{Sum(DataBand2,Order_Details.TotalPrice)}
نتیجه کار طراحی گزارش به شکل زیر خواهد بود.

حال میتوانید خروجی گزارش Master-Detail را از سربرگ Preview مشاهده نمایید. در صورتی که همانند تصویر بالا گزارش را طراحی کرده باشید در خروجی گزارش فاصله‌ای بین سطرها ایجاد شده است که علت آن ارتفاع کمتر کامپوننتهای Text نسبت به بندهای خود می‌باشد. برای رفع این مشکل، شما سه راه حل دارید.

الف: یکسان سازی ارتفاع کاپوننتها با بند دربرگیرنده آنها

ب: ست کردن خصوصیت Can Shrink بند دربرگیرنده کامپوننتها به مقدار true

ج: ست کردن خصوصیت Grow to Height کامپوننتهای Text به مقدار true

در این مثال ما از روش دوم استفاده میکنیم و خصوصیت Can Shrink بندهای HeaderBand2 و DataBand2 را به مقدار true ست میکنیم.

کامپوننتهای Panel و Clone :

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

Clone: به شما امکان کپی کردن یک بخش از گزارش را میدهد که البته آن بخش فقط میتواند از نوع Panel باشد.

این دو کامپوننت یکی از عوامل قدرت این گزارش‌ساز می‌باشد. برای شروع یک Page (صفحه طراحی) دیگر به گزارش اضافه کنید. می‌توانید با رایت کلیک بر روی نوار سربرگهای محیط طراحی گزینه New Page را انتخاب نمایید. بر روی Page2 یک کامپوننت Panel قرار دهید، سپس از پنل Dictionary جدول Countries را دراگ کرده و در Panel1 رها کنید. در فرم Data تمامی فیلدها و بند Header را انتخاب نمایید، سپس یک کامپوننت Clone به صفحه طراحی اضافه کنید بلافاصله فرم Select Container ظاهر می‌شود، Panel1 را انتخاب کنید. حال شما می‌توانید خروجی گزارش را مشاهده کنید، خروجی Page2 بعد از خروجی Page1 ظاهر خواهد شد.

Report2-1.mrt 

مطالب
MongoDB #14
عمل تکثیر در MongoDB
عمل تکثیر (Replication) به فرآیند همزمان سازی داده در میان چند سرور گفته می‌شود. تکثیر، افزونگی را فراهم می‌آورد و دسترسی پذیری داده‌ها را توسط کپی داده در چندین سرور مختلف افزایش می‌دهد. این کار، یک پایگاه داده را در مقابل از دسترس خارج شدن یک سرور مفرد، محافظت می‌کند. همچنین امکان بازیابی از خرابی سخت افزار و وقفه‌های سرویس را به کاربر می‌دهد. توسط کپی برداری از اطلاعات، می‌توانید یکی از آنها را برای بازیابی، گزارشگیری و پشتیبان گیری اختصاص دهید.

چرا تکثیر؟
  • برای ایمن نگه داری اطلاعات
  • دسترسی پذیری بالای اطلاعات (شبانه روزی)
  • بازیابی اطلاعات
  • نیازی به از کار افتادن هنگام انجام عملیات نگه‌داری ندارد
  • مقایس پذیری خواندن داده‌ها (کپی برداری‌های اضافه برای عمل خواندن)
  • کپی اطلاعات برای نرم افزارها شفاف و قابل دستیابی است.

تکثیر در MongoDB چگونه کار می‌کند
MongoDB عمل تکثیر را با استفاده از مجموعه کپی یا المثنی (Replica set) انجام می‌دهد. مجموعه کپی یک گروه از نمونه‌های mongodb هستند که مجموعه داده یا دیتاست مشابهی را میزبانی (Host) می‌کنند. در یک کپی داده، یک گره، گره اصلی است که تمام عملیات نوشتن را دریافت می‌کند. بقیه‌ی نمونه‌های ثانویه، عملیات را از گره اصلی، دریافت و اعمال می‌کنند؛ بنابراین آنها هم دیتاست مشابهی دارند. مجموعه‌ی کپی تنها می‌تواند یک گره‌ی اصلی داشته باشد.
  1. یک مجموعه‌ی کپی، یک گروه از دویا چند گره است. (عموما حداقل 3 گره نیاز است.)
  2. در یک مجموعه‌ی کپی، یک گره، گره اصلی است و بقیه گره‌ها گره‌های ثانویه هستند.
  3. همه‌ی داده‌ها از گره‌ی اصلی به گره‌های ثانویه تکثیر می‌شوند.
  4. هنگام انجام عملیات نگه داری یا ازدسترس خارج شدن سرور، گزینش برای گره اصلی و انتخاب گره اصلی جدید آغاز می‌شود.
  5. گره از کار افتاده، بعد از بازیابی دوباره، به مجموعه کپی ملحق می‌شود و بعنوان یک گره ثانویه کار می‌کند.
در زیر یک نوع دیاگرام از تکثیر در MongoDB نشان داده شده است که در آن برنامه‌ی سمت کلاینت همیشه با گره‌ی اصلی در ارتباط است و گره‌ی اصلی، داده‌ها را گره‌های ثانویه تکثیر می‌کند.


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

ساختن یک مجموعه کپی
در اینجا می‌خواهیم یک نمونه از mongodb را به یک مجموعه‌ی کپی تبدیل کنیم. برای این کار مراحل زیر را انجام دهید:
  • همه‌ی نمونه‌های در حال اجرای mongod را در سمت سرور، متوقف کنید.
  • اکنون mongod سمت سرور را با سوئیچ –replSet راه اندازی کنید.
گرامر پایه --replSet به شکل زیر است:
mongod --port "PORT" --dbpath "YOUR_DB_DATA_PATH" --replSet "REPLICA_SET_INSTANCE_NAME"
مثال
mongod --port 27017 --dbpath "D:\set up\mongodb\data" --replSet rs0
دستور فوق یک نمونه از mongod را با نام rs0، روی پورت 27017 راه اندازی می‌کند. اکنون command prompt را باز کنید و به این نمونه mongod متصل شوید. در سمت کلاینت، دستور ()rs.initiate را برای شروع کردن یک مجموعه‌ی کپی جدید صادر کنید. برای چک کردن تنظیمات مجموعه‌ی کپی، دستور ()rs.conf را صادر کنید. برای چک کردن وضعیت مجموعه کپی نیز دستور ()rs.status را صادر کنید.

افزودن اعضا به مجموعه‌ی کپی
برای افرودن اعضا به مجموعه‌ی کپی، چند نمونه mongodb را در چندین کامپیوتر راه اندازی کنید. اکنون برنامه‌ی سمت کلاینت را اجرا و دستور ()rs.add را اجرا کنید.

گرامر
گرامر پایه دستور ()rs.add به شکل زیر است:
>rs.add(HOST_NAME:PORT)
مثال
فرض کنید نام نمونه‌ی mongodb شما mongod1.net و بر روی پورت 27017 در حال اجراست. برای افزودن این نمونه به مجموعه کپی، دستور () rs.add را در سمت کلاینت اجرا کنید.
>rs.add("mongod1.net:27017")
>
توجه کنید که فقط وقتی می‌توانید یک نمونه mongodb را برای مجموعه کپی اضافه کنید که به گره اصلی متصل باشید. برای چک کردن اینکه به گره اصلی متصل هستید، دستور ()db.isMaster را در سمت کلاینت صادر کنید.
بازخوردهای دوره
آشنایی با AOP Interceptors
راهکاری برای پیاده سازی این دوره در ASP.NET Core بدون استفاده از StructureMap یا هر  IoC Container دیگر و فقط    به کمک IoC Container توکار ASP.NET Core   هست ؟ در غیر این صورت کدام  Container را پیشنهاد می‌دهید ؟  
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 2 - ثبت اولین سرویس
یک پروژه‌ی ASP.NET Core را با قرار دادن نسخه‌ی NET Core. بر روی 3.1 و با استفاده از قالب Model View Controller ایجاد کنید. در اینجا نام پروژه را AspNetCoreDependencyInjection گذاشته‌ام. حالا در  پوشه‌ی Models، فایلی را با نام HomeViewModel.cs با محتویاتی به صورت زیر اضافه کنید:
public class HomeViewModel
{
     public string Id { get; set; }
     public string Message { get; set; }
     public DateTime DateTime { get; set; }
}

اکنون به پوشه‌ی Views بروید و فایل Index.cshtml را به این صورت تغییر دهید:

@model AspNetCoreDependencyInjection.Models.HomeViewModel
@{
ViewData["Title"] = "Home";
}

<div>
<div>
<div>
<p>
<b>Id : </b><span>@Model.Id</span> <br />
<b>Date And Time : </b><span> @Model.DateTime </span> <br/>
<b>Message : </b><span>@Model.Message</span>
</p>
</div>
</div>
</div>
و فایل MessageServiceA.cs را به پروژه اضافه کنید:
using AspNetCoreDependencyInjection.Services;

namespace AspNetCoreDependencyInjection.ServicesImplentaions
{
    public class MessageServiceAA 
    {
        public string Message()
        {
            return "A message from MessageServiceAA";
        }
    }
}
و همچنین فایل GuidHelper.cs را نیز اضافه می‌کنیم:
namespace AspNetCoreDependencyInjection.Helpers
{
    public class GuidProvider
    {
        private readonly Guid _serviceGuid;

        public GuidProvider()
        {
            _serviceGuid = Guid.NewGuid();
        }

        public Guid GetNewGuid() => Guid.NewGuid();

        public string GetGuidAsFormatedString(string prefix = "") => getFormatedGuid(_serviceGuid, prefix);

        private string getFormatedGuid(Guid guid, string prefix = "")
        {
            var guidString = guid.GetHashCode().ToString("x");
            if (string.IsNullOrEmpty(prefix) == false)
                guidString = new StringBuilder($"{prefix}-").Append(guidString).ToString();
            return guidString;
        }
    }
}

حالا درون کنترل HomeController، این تغییرات را انجام می‌دهیم:

private readonly ILogger<HomeController> _logger;
private readonly MessageServiceAA _messageService;
private readonly GuidProvider _ guidProvider;

public HomeController(ILogger<HomeController> logger)
{
            _logger = logger;
            _messageService = new MessageServiceAA();
            _guidProvider = new GuidProvider();
}

public IActionResult Index()
{
            var model = new HomeViewModel()
            {
                Id = _ guidProvider.GetGuidAsFormatedString(),
                Message = _messageService.Message(),
                DateTime = DateTime.Now,
            };
            return View(model);
}

همانطور که می‌بینید، در کد بالا، کنترلر HomeController، به دو شیء از کلاس‌ها و یا سرویس‌های GuidProvider و MessageServiceAA به صورت مستقیم وابسته شده‌است و با هر تغییری در هر کدام از این سرویسها، باید دوباره کامپایل شود. علاوه بر این اگر بخواهیم پیاده سازی‌های مختلفی را برای هر کدام از این موارد، ارائه دهیم، به مشکل بر می‌خوریم. خب بیاید تغییراتی را در کد بالا بدهیم تا مشکلات ذکر شده را حل کنیم.

برای این منظور پوشه‌ای را به نام Services می‌سازیم و اینترفیسی را به نام IMessageBrokerA ایجاد می‌کنیم و سپس کاری می‌کنیم که MessageServiceAA از این اینترفیس ارث بری کند:

namespace AspNetCoreDependencyInjection.Services
{
    public interface IMessageServiceA
    {
        string Message();
    }
}

و حالا می‌خواهیم با استفاده از تزریق وابستگی، وابستگی کنترلر HomeController را از کلاس MessageBrokerAA لغو کرده و آن را به اینترفیس IMessageBrokerA (انتزاع) وابسته کنیم. در اینجا ما از تکنیک تزریق درون سازنده یا Constructor Injection استفاده می‌کنیم.


تزریق درون سازنده

در این تکنیک، ما لیستی از وابستگی‌های مورد نیاز را به عنوان پارامترهای ورودی سازنده‌ی کلاس، تعریف می‌کنیم:
private readonly ILogger<HomeController> _logger;
private readonly IMessageServiceA _messageService;
private readonly GuidProvider _guidHelper;
public HomeController(ILogger<HomeController> logger , IMessageServiceA messageService)
{
        _logger = logger;
        _messageService = messageService;
        _messageService = new MessageServiceAA();
        _guidHelper = new GuidProvider();
}
و حالا اگر برنامه را اجرا کنیم، با خطایی روبه رو می‌شویم که در آن می‌گوید امکان واکشی (Resolve) سرویس‌های مورد نظر وجود ندارد. این خطا به دلیل ثبت نشدن اینترفیس IMessageServiceA و پیاده سازی آن، درون Microsoft Dependency Injection Container است   DI Container‌ها معمولا باید در زمان شروع برنامه، پیکربندی و مقدار دهی شودند، تا در ادامه‌ی چرخه‌ی حیات برنامه، بتوانند سرویس‌ها و اشیاء مورد نیاز را به کلاس‌هایی که نیاز دارند، واکشی و تزریق کنند. اولین مرحله از کار با DI Container‌ها، ثبت کردن سرویس‌ها درون آنهاست.
در ASP.NET Core از IServiceCollection برای ثبت کردن سرویس‌های برنامه‌ی خودمان استفاده می‌کنیم. تمامی سرویس‌هایی را که انتظار داریم توسط DI Container به کلاس‌هایی تزریق شوند، باید درون IServiceCollection ثبت گردند. تمام سرویس‌هایی که به وسیله‌ی IServiceCollection ثبت شده‌اند، پس از ساخته شدن، توسط اینترفیس IServiceProvider قابل واکشی هستند.

بنابراین دو اینترفیس حیاتی برای کار کرد صحیح Microsoft Dependency Injection Container درون ASP.NET Core وجود دارند:
  • IServiceCollection : برای ثبت سرویس‌ها
  • IServiceProvider : برای واکشی سرویس‌ها

در ASP.NET Core معمول‌ترین مکان برای ثبت کردن سرویس‌ها درون Container، به صورت پیش فرض درون کلاس Startup و درون متد ConfigureServices انجام می‌گیرد.
به صورت پیش فرض کلاس Startup دو متد دارد:
  • ConfigureServices : برای پیکربندی و ثبت سرویس‌های درونی DI Container استفاده می‌شود.
  • Configure : برای تنظیمات pipeline میان افزارها ( Middlewares ) بکار می‌رود.

در اینجا پیاده سازی پیش فرض کلاس Startup را می‌بینیم که البته کدهای درون متد Configure را برای درگیر نکردن ذهن شما، مخفی کرده‌ایم: 
public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //  کدها جهت خوانایی بیشتر مخفی شده اند
        }
    }

همانطورکه می‌بینید، متد ConfigureService پارامتر IServiceCollection را می‌گیرد که به وسیله‌ی WebHost در زمان اجرای برنامه، مقدار دهی می‌شود.

تعداد زیادی Extension method برای IServiceCollection وجود دارند که برای پشتیبانی از ثبت کردن سرویس‌های مختلف در سناریوهای گوناگون به کار می‌روند. در اینجا ما از نسخه‌ی 3.1 چارچوب ASP.NET Core استفاده می‌کنیم. برای همین هم برای ثبت سرویس‌های پیش فرض فریمورک MVC از متد توسعه‌ی services.AddControllersWithViews()    استفاده می‌کنیم.  متد توسعه‌ی AddControllersWithViews() سرویس‌هایی را که معمولا در فریم ورک MVC استفاده می‌شوند، درون IServiceCollection ثبت می‌کند. در نسخه‌های قبلی چارچوب ASP.NET Core،  مانند نسخه‌های 2.1 و 2.2 برای این کار از متد توسعه‌ی AddMvc() استفاده می‌شد.

در Microsoft Dependency Injection Container ، معمولا  ترتیب ثبت سرویس‌ها مهم نیست.

خب، اولین سرویس اختصاصی برنامه‌ی خودمان را با چرخه‌ی حیات Transient و زیر سرویس پیشین، به شکل زیر ثبت می‌کنیم :

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddTransient<IMessageServiceA, MessageServiceAA>();
        }
همانطور که می‌بینید، در اینجا ما از متد AddTransient() استفاده کرده‌ایم. متد AddTransient() درون فضای نام Microsoft.Extensions.DependencyInjection قرار دارد. این متد Overload ‌های گوناگونی دارد و ما از نوعی از آن استفاده کرده‌ایم که دو نوع generic را می‌پذیرد و تعریف آن به صورت زیر است: 
public static IServiceCollection AddTransient<TService, TImplementation>(this IServiceCollection services)
در اینجا TService ، اینترفیس سرویس ماست. این نوع، همان نوعی است که کلاس‌های ما می‌توانند به آن وابسته باشند. پارامتر دوم، از نوع TImplemention است که پیاده سازی مورد نظر برای TService را ثبت می‌کند. TImplmention   نوعی است که Container در زمان واکشی و تزریق TService از آن نمونه سازی کرده و به کلاس مورد نظر تزریق می‌کند.

در اینجا وقتی ما برای IMessageServiceA ، پیاده سازی MessageServiceA را ثبت می‌کنیم، از این به بعد DI Container، هر زمانیکه در لیست پارامترهای سازنده‌ی یک کلاس، IMessageServiceA را مشاهده کند، بررسی می‌کند که چه کلاسی به عنوانی پیاده سازی این اینترفیس ثبت شده‌است، سپس از آن نمونه سازی می‌کند و درون سازنده‌ی مورد نظر تزریق می‌کند. خب، حالا برنامه را دوباره اجرا کنید؛ می‌بینید که برنامه اجرا می‌شود.

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