مطالب
Pipeها در Angular 2 - بخش اول
برای تغییر نحوه نمایش یک عبارت در رابط کاربری، از Pipe استفاده می‌شود. مثلا ممکن است تاریخ تولد به صورت میلادی از سرور دریافت شده باشد، می‌خواهیم بدون تغییری در متغیر حامل تاریخ میلادی و فقط در لایه رابط کاربری، کاربر تاریخ را به صورت شمسی مشاهده کند. به عبارت دیگر برای تغییر نحوه نمایش مقدار نمایشی (display-value) در صفحات HTML خود، از Pipe استفاده می‌شود. 

نحوه استفاده از Pipe

Pipe یک متغیر یا عبارت را به عنوان ورودی دریافت کرده و آن‌را به شکل دیگری برای نمایش تغییر می‌دهد. Pipeها معمولا در صفحات HTML مورد استفاده قرار می‌گیرند. با استفاده از عملگر Pipe (|) به شکل زیر می‌توانید Pipe مورد نظر خود را اعمال کنید.

import { Component } from '@angular/core';

@Component({
  selector: 'hero-birthday',
  template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})
export class HeroBirthdayComponent {
  birthday = new Date(1988, 3, 15); // April 15, 1988
}

در این مثال Pipe از قبل ساخته شده date را بر روی متغییر birthday اعمال می‌کنیم. خروجی کار به شکل زیر خواهد بود.

The hero's birthday is April 15, 1988

لازم به ذکر است Pipe هیچگونه اثری بر روی متغییر birthday نداشته و فقط نحوه نمایش آن را تغییر می‌دهد.

 

Pipeهای از پیش ساخته شده

در انگیولار ۲ یکسری Pipe از پیش ساخته شده مانند DatePipe، UpperCasePipe، LowerCasePipe، CurrencyPipe و PercentPipe وجود دارند که شما در تمامی صفحات HTML می‌توانید بدون هیچ تنظیم اضافه‌ای از آنها استفاده کنید. لیست Pipeهای از پیش ساخته شده را اینجا مشاهده کنید. 


ارسال پارامتر به Pipe

  Pipeها در انگیولار ۲ می‌توانند تعدادی پارامتر ورودی را برای ارائه خروجی مدنظر دریافت کنند. برای فرستادن پارامتر به Pipe، بلافاصله بعد از نام Pipe با استفاده از دو نقطه (:) پارامتر مورد نظر را ارسال می‌کنیم (برای مثال 'currency:'EUR). درصورتیکه Pipe چند پارامتر را دریافت می‌کند، هر پارامتر با یک دو نقطه (:) از هم جدا می‌شوند؛ برای مثال slice:1:5.
  برای نمونه اگر بخواهیم تاریخ موجود در متغیر birthday در مثال قبل را به صورت «04/15/88» نمایش دهیم کافی است پارامتر «MM/dd/yy» را به datePipe ارسال کنیم.
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>

مقدار پارامتر، هر نوع مجازی از Template expressions می‌تواند باشد (از جمله عبارت رشته‌ای یا حتی یک خصوصیت از کامپوننت). بنابراین در سرتاسر برنامه و در هر زمان می‌توانید با تغییر پارامتر در لحظه، خروجی مدنظر خود را به کاربرنهایی نمایش دهید.

 

Template expressions: عباراتی (expressions) که بعد از اجرا توسط انگیولار، تبدیل به یک مقدار جهت نمایش در HTML، کامپوننت یا دایرکتیو می‌شود.


 

استفاده زنجیره‌ای از Pipeها

برای اعمال چند Pipe بر روی یک عبارت، می‌توان از Pipeها به صورت پشت سر هم استفاده کرد. برای مثال در ادامه می‌خواهیم علاوه بر اعمال DatePipe با پارامتر fullDate جهت نمایش تاریخ به صورت Friday, April 15, 1988، حروف را نیز به صورت UpperCase نمایش دهیم. لازم به ذکر است برای نمایش حروف به صورت UpperCase از Pipe به همین نام استفاده می‌کنیم. 

<p>The hero's birthday is {{ birthday | date:"fullDate" | uppercase}} </p>

خروجی کار به شکل زیر خواهد بود. 

The hero's birthday is FRIDAY, APRIL 15, 1988


استفاده از Pipe در TypeScript

برای کسانیکه با انگیولار یک آشنایی دارند، Pipe در انگولار ۲ معادل filter در انگولار یک است. در انگیولار یک با تزریق سرویس filter$ به کنترلرها یا سرویس‌ها، می‌توانستیم filterهای تعریف شده شخصی و از پیش ساخته شده را جهت اعمال بر روی متغیر‌های خود، استفاده کنیم. در انگیولار دو نیز این امکان فراهم شده‌است؛ ولی به سادگی تزریق filter$ نیست. یعنی لازم است علاوه بر تزریق Pipe به سرویس یا کامپوننت‌های خود، Pipe مورد نظر خود را در لیست providers ماژول خود نیز اضافه کنید. برای نمونه اگر بخواهیم DatePipe را در component خود (نه در template) مورد استفاده قرار دهیم به شکل زیر عمل می‌کنیم:

import { Component } from '@angular/core';

// اضافه کردن DatePipe از @angular/common
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  birthDay = new Date(1988, 3, 15);
  strBirthDay = "";

// تزریق DatePipe
  constructor(private datePipe: DatePipe) {
    this.strBirthDay = this.datePipe.transform(this.birthDay, 'yyyy-MM-dd');
  }
}

پس از وارد کردن DatePipe از angular/common@ به کامپوننت و تزریق آن از طریق سازنده کامپوننت، برای اعمال Pipe بر روی عبارت مورد نظر خود، از متد transform استفاده می‌کنیم.

  تمامی Pipeها به واسطه پیاده سازی PipeTransform دارای متد transform هستند. این متد در اولین پارامتر خود، عبارتی را که قرار است Pipe بر روی آن اعمال شود، دریافت می‌کند. در صورتیکه Pipe مورد نظر دارای پارامتر باشد از طریق پارامتر دوم به بعد آنرا دریافت می‌کند.
همانطور که قبلا اشاره شد، علاوه بر تزریق Pipe در کامپوننت یا سرویس، Pipe باید در لیست providers ماژول نیز اضافه شود.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { DatePipe } from '@angular/common'
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  // افزودن Pipe به Providers
  providers: [DatePipe],
  bootstrap: [AppComponent]
})
export class AppModule { }

نکته‌ای در مورد DatePipe و CurrencyPipe

Pipeهای Date و Currency به دلیل استفاده از شی Intl در داخل خود نیاز به ECMAScript Internationalization API دارند. مرورگرهای قدیمی و همچنین مرورگر Safari به دلیل عدم پشتیبانی از این قضیه به هنگام استفاده از این Pipeها دچار مشکل می‌شوند. برای حل این مشکل کافی است اسکریپت زیر را به صفحه خود اضافه کنید. 

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>

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

مسیرراه‌ها
ASP.NET MVC
              مطالب
              روش دیباگ برنامه‌های مبتنی بر Angular CLI در VSCode
              در انتهای مطلب «فرم‌های مبتنی بر قالب‌ها در Angular - قسمت پنجم - ارسال اطلاعات به سرور» در مورد «بارگذاری اطلاعات drop down از سرور» بحث شد. در اینجا می‌خواهیم قبل از نمایش اطلاعات این drop down در رابط کاربری، بر روی سطر this.languages = data در VSCode، یک break-point را قرار دهیم و اطلاعات دریافتی از سرور را بررسی کنیم.


              پیشنیازها

              در اینجا فرض بر این است که موارد ذیل را نصب کرده‌اید:
              - آخرین نگارش مرورگر Chrome
              - افزونه‌ی Debugger for Chrome که از آن برای دیباگ کدهای جاوا اسکریپتی و تایپ‌اسکریپتی یکپارچه‌ی با VSCode می‌توان استفاده کرد.


              تنظیمات VSCode جهت دیباگ برنامه‌های مبتنی بر Angular CLI

              کدهای مطلب «فرم‌های مبتنی بر قالب‌ها در Angular - قسمت پنجم - ارسال اطلاعات به سرور» از انتهای بحث آن قابل دریافت هستند. این کدها را دریافت کرده و سپس پوشه‌ی اصلی آن‌را در VSCode باز کنید. اگر در ویندوز هستید با کلیک راست بر روی پوشه، گزینه‌ی Open With Code ظاهر می‌شود و یا حتی می‌توان از طریق خط فرمان به پوشه‌ی اصلی برنامه مراجعه کرده و سپس دستور . code را صادر نمود.

              در ادامه نیاز است دیباگر VSCode را تنظیم کنیم. اگر پیشتر هیچ تنظیمی را نداشته باشید، پس از مراجعه‌ی به برگه‌ی debug آن، بر روی دکمه‌ی سبز رنگ Start Debugging آن کلیک کنید. در ادامه در منوی باز شده، گزینه‌ی Chrome را انتخاب کنید:


              اینکار سبب می‌شود تا فایل جدید vscode\launch.json. به پوشه‌ی جاری اضافه شده و سپس تنظیمات دیباگر کروم در آن قرار گیرند.

              حالت دوم شبیه به حالتی است که برنامه‌ی مورد بحث جاری دارد: پیشتر دیباگری مانند NET Core. در آن تنظیم شده‌است. در این حالت، منوی drop down مربوط به دیباگرهای تنظیم شده را گشوده و سپس گزینه‌ی آخر آن یعنی Add configuration را انتخاب کنید:


              در اینجا نیز منوی Intellisense آن ظاهر شده و امکان انتخاب گزینه‌ی Chrome را می‌دهد:


              در نهایت هر دو حالت به ایجاد فایل جدید vscode\launch.json. و یا ویرایش آن منتهی می‌شوند. در اینجا تنها کاری را که باید انجام داد، تغییر پورت پیش فرض آن است:
              - اگر از دستور ng serve استفاده می‌کنید، این پورت را به 4200 تغییر دهید (پورت پیش فرض این دستور است؛ برای تغییر آن، از پارامتر port-- در دستور ng serve استفاده کنید):
              {
                "version": "0.2.0",
                "configurations": [
                  {
                    "type": "chrome",
                    "request": "launch",
                    "name": "Launch Chrome with ng serve",
                    "url": "http://localhost:4200/#",
                    "webRoot": "${workspaceRoot}"
                  }
                ]
              }

              - اگر از دستورات dotnet watch run و سپس ng build -watch استفاده می‌کنید (اولی وب سرور آزمایشی NET Core. را راه اندازی می‌کند و دومی کار ساخت پیوسته‌ی برنامه‌ی Angular را انجام می‌دهد)،  این پورت را بر روی 5000 تنظیم کنید:
              {
                "version": "0.2.0",
                "configurations": [
                  {
                    "type": "chrome",
                    "request": "launch",
                    "name": "Launch Chrome for dotnet build & ng build",
                    "url": "http://localhost:5000/#",
                    "webRoot": "${workspaceRoot}"
                  }
                ]
              }


              آزمایش یک break point و بررسی مقادیر دریافتی از سرور

              تا اینجا کاری را که انجام دادیم، به افزودن یک قطعه تنظیم جدید به فایل vscode\launch.json. خلاصه می‌شود.

              در ادامه به کامپوننت employee-register.component.ts مراجعه کرده و سطر this.languages = data را تبدیل به یک سطر مستقل درون {} می‌کنیم تا بتوانیم بر روی آن break point قرار دهیم:
                ngOnInit() {
                  this.formPoster.getLanguages().subscribe(
                    data => {
                      this.languages = data;
                    },
                    err => console.log("get error: ", err)
                  );
                }

              پس از آن از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی دستورات
              >npm install
              >ng build --watch
              و در دومی دستورات ذیل را اجرا کنید:
              >dotnet restore
              >dotnet watch run

              سپس به برگه‌ی دیباگ مراجعه کرده و گزینه‌ی جدید Launch Chrome for dotnet build & ng build را انتخاب و سپس بر روی دکمه‌ی سبز رنگ اجرای آن کلیک کنید:


              اکنون اگر صفحه‌ی مشاهده‌ی فرم را در مرورگر کروم باز شده درخواست کنیم، به این break point خواهیم رسید؛ اما ... حاوی اطلاعات data نیست. برای رفع این مشکل نیاز است تنظیمات پیش‌فرض را به صورت ذیل بهبود بخشید:
              {
                "version": "0.2.0",
                "configurations": [
                  {
                    "type": "chrome",
                    "request": "launch",
                    "name": "Launch Chrome for dotnet build & ng build",
                    "url": "http://localhost:5000",
                    "runtimeArgs": [
                      "--user-data-dir",
                      "--remote-debugging-port=9222",
                      "--disable-session-crashed-bubble"
                    ],
                    "sourceMaps": true,
                    "trace": true,
                    "webRoot": "${workspaceRoot}/wwwroot/",
                    "userDataDir": "${workspaceRoot}/.vscode/chrome"
                  }
                ]
              }
              - در این تنظیمات تکمیلی وجود runtimeArgs و userDataDir جهت مدیریت داشتن چندین وهله‌ی از کروم و باز بودن برگه‌های مختلف آن مفید است.
              - تنظیم sourceMaps و همچنین مشخص سازی محل دقیق پوشه‌ی قرارگیری فایل‌های نهایی ng build که همان پوشه‌ی wwwroot است در webRoot سبب خواهند شد تا اینبار break point ما حاوی اطلاعات واقعی data دریافتی از سرور باشند (با نزدیک کردن اشاره‌گر ماوس به data، اطلاعات تکمیلی آن ظاهر می‌شود):

                - اگر از دستور ng serve استفاده می‌کنید، در این تنظیمات پورت را به 4200 و محل پوشه‌ی wwwroot را به dist تغییر دهید (مطابق تنظیمات فایل angular-cli.json.).
              مطالب
              Blazor 5x - قسمت دوم - بررسی ساختار اولیه‌ی پروژه‌های Blazor
              پس از آشنایی با دو مدل هاستینگ برنامه‌های Blazor در قسمت قبل، اکنون می‌خواهیم ساختار ابتدایی قالب‌های این دو پروژه را بررسی کنیم.


              ایجاد پروژه‌های خالی Blazor

              در انتهای قسمت قبل، با روش ایجاد پروژه‌های خالی Blazor به کمک NET SDK 5x. آشنا شدیم. به همین جهت دو پوشه‌ی جدید BlazorWasmSample و BlazorServerSample را ایجاد کرده و از طریق خط فرمان و با کمک NET CLI.، در پوشه‌ی اولی دستور dotnet new blazorwasm و در پوشه‌ی دومی دستور dotnet new blazorserver را اجرا می‌کنیم.
              البته اجرای این دو دستور، نیاز به اتصال به اینترنت را هم برای بار اول دارند؛ تا فایل‌های مورد نیاز و بسته‌های مرتبط را دریافت و restore کنند. بسته به سرعت اینترنت، حداقل یک ربعی را باید صبر کنید تا دریافت ابتدایی بسته‌های مرتبط به پایان برسد. برای دفعات بعدی، از کش محلی NuGet، برای restore بسته‌های blazor استفاده می‌شود که بسیار سریع است.


              بررسی ساختار پروژه‌ی Blazor Server و اجرای آن

              پس از اجرای دستور dotnet new blazorserver در یک پوشه‌ی خالی و ایجاد پروژه‌ی ابتدایی آن:


              همانطور که مشاهده می‌کنید، ساختار این پروژه، بسیار شبیه به یک پروژه‌ی استاندارد ASP.NET Core از نوع Razor pages است.
              - در پوشه‌ی properties آن، فایل launchSettings.json قرار دارد که برای نمونه، شماره پورت اجرایی برنامه را در حالت اجرای توسط دستور dotnet run و یا توسط IIS Express مشخص می‌کند.

              - پوشه‌ی wwwroot آن، مخصوص ارائه‌ی فایل‌های ایستا مانند wwwroot\css\bootstrap است. در ابتدای کار، این پوشه به همراه فایل‌های CSS بوت استرپ است. در ادامه اگر نیاز باشد، فایل‌های جاوا اسکریپتی را نیز می‌توان به این قسمت اضافه کرد.

              - در پوشه‌ی Data آن، سرویس تامین اطلاعاتی اتفاقی قرار دارد؛ به نام WeatherForecastService که هدف آن، تامین اطلاعات یک جدول نمایشی است که در ادامه در آدرس https://localhost:5001/fetchdata قابل مشاهده است.

              - در پوشه‌ی Pages، تمام کامپوننت‌های Razor برنامه قرار می‌گیرند. یکی از مهم‌ترین صفحات آن، فایل Pages\_Host.cshtml است. کار این صفحه‌ی ریشه، افزودن تمام فایل‌های CSS و JS، به برنامه‌است. بنابراین در آینده نیز از همین صفحه برای افزودن فایل‌های CSS و JS استفاده خواهیم کرد. اگر به قسمت body این صفحه دقت کنیم، تگ جدید کامپوننت قابل مشاهده‌است:
              <body>
                 <component type="typeof(App)" render-mode="ServerPrerendered" />
              کار آن، رندر کامپوننت App.razor واقع در ریشه‌ی پروژه‌است.
              همچنین در همینجا، تگ‌های دیگری نیز قابل مشاهده هستند:
              <body>
                  <component type="typeof(App)" render-mode="ServerPrerendered" />
              
                  <div id="blazor-error-ui">
                      <environment include="Staging,Production">
                          An error has occurred. This application may no longer respond until reloaded.
                      </environment>
                      <environment include="Development">
                          An unhandled exception has occurred. See browser dev tools for details.
                      </environment>
                      <a href="" class="reload">Reload</a>
                      <a class="dismiss">🗙</a>
                  </div>
              
                  <script src="_framework/blazor.server.js"></script>
              </body>
              همانطور که در قسمت قبل نیز عنوان شد، اتصال برنامه‌ی در حال اجرای در مرورگر blazor server با backend، از طریق یک اتصال دائم SignalR صورت می‌گیرد. اگر در این بین خطای اتصالی رخ دهد، div با id مساوی blazor-error-ui فوق، یکی از پیام‌‌هایی را که مشاهده می‌کنید، بسته به اینکه برنامه در حالت توسعه در حال اجرا است و یا در حالت ارائه‌ی نهایی، نمایش می‌دهد. افزودن مدخل blazor.server.js نیز به همین منظور است. کار آن مدیریت اتصال دائم SignalR، به صورت خودکار است و از این لحاظ نیازی به کدنویسی خاصی از سمت ما ندارد. این اتصال، کار به روز رسانی UI و هدایت رخ‌دادها را به سمت سرور، انجام می‌دهد.

              - در پوشه‌ی Shared، یکسری فایل‌های اشتراکی قرار دارند که قرار است در کامپوننت‌های واقع در پوشه‌ی Pages مورد استفاه قرار گیرند. برای نمونه فایل Shared\MainLayout.razor، شبیه به master page برنامه‌های web forms است که قالب کلی سایت را مشخص می‌کند. داخل آن Body@ را مشاهده می‌کنید که به معنای نمایش صفحات دیگر، دقیقا در همین محل است. همچنین در این پوشه فایل Shared\NavMenu.razor نیز قرار دارد که ارجاعی به آن در MainLayout.razor ذکر شده و کار آن نمایش منوی آبی‌رنگ سمت چپ صفحه‌است.

              - در پوشه‌ی ریشه‌ی برنامه، فایل Imports.razor_ قابل مشاهده‌است. مزیت تعریف usingها در اینجا این است که از تکرار آن‌ها در کامپوننت‌های razor ای که در ادامه تهیه خواهیم کرد، جلوگیری می‌کند. هر using تعریف شده‌ی در اینجا، در تمام کامپوننت‌ها، قابل دسترسی است؛ به آن global imports هم گفته می‌شود.

              - در پوشه‌ی ریشه‌ی برنامه، فایل App.razor نیز قابل مشاهده‌است. کار آن تعریف قالب پیش‌فرض برنامه‌است که برای مثال به Shared\MainLayout.razor اشاره می‌کند. همچنین کامپوننت مسیریابی نیز در اینجا ذکر شده‌است:
              <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
                  <Found Context="routeData">
                      <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
                  </Found>
                  <NotFound>
                      <LayoutView Layout="@typeof(MainLayout)">
                          <p>Sorry, there's nothing at this address.</p>
                      </LayoutView>
                  </NotFound>
              </Router>
              اگر شخصی مسیر از پیش تعریف شده‌ای را وارد کند، به قسمت Found وارد می‌شود و در غیر اینصورت به قسمت NotFound. در قسمت Found است که از قالب MainLayout برای رندر یک کامپوننت توسط کامپوننت RouteView، استفاده خواهد شد و در قسمت NotFound، فقط پیام «Sorry, there's nothing at this address» به کاربر نمایش داده می‌شود. قالب‌های هر دو نیز قابل تغییر و سفارشی سازی هستند.

              - فایل appsettings.json نیز همانند برنامه‌های استاندارد ASP.NET Core در اینجا مشاهده می‌شود.

              - فایل Program.cs آن که نقطه‌ی آغازین برنامه‌است و کار فراخوانی Startup.cs را انجام می‌دهد، دقیقا با یک فایل Program.cs برنامه‌ی استاندارد ASP.NET Core یکی است.

              - در فایل Startup.cs آن، همانند قبل دو متد تنظیم سرویس‌ها و تنظیم میان‌افزارها قابل مشاهده‌است.
              public void ConfigureServices(IServiceCollection services)
              {
                  services.AddRazorPages();
                  services.AddServerSideBlazor();
                  services.AddSingleton<WeatherForecastService>();
              }
              در ConfigureServices آن، سرویس‌های صفحات razor و ServerSideBlazor اضافه شده‌اند. همچنین سرویس نمونه‌ی WeatherForecastService نیز در اینجا ثبت شده‌است.
              قسمت‌های جدید متد Configure آن، ثبت مسیریابی توکار BlazorHub است که مرتبط است با اتصال دائم SignalR برنامه و اگر مسیری پیدا نشد، به Host_ هدایت می‌شود:
              public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
              {
                  // ...
              
                  app.UseEndpoints(endpoints =>
                  {
                     endpoints.MapBlazorHub();
                     endpoints.MapFallbackToPage("/_Host");
                  });
              }
              در ادامه در همان پوشه، دستور dotnet run را اجرا می‌کنیم تا پروژه، کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.


              که به همراه 13 درخواست و نزدیک به 600 KB دریافت اطلاعات از سمت سرور است.

              این برنامه‌ی نمونه، به همراه سه صفحه‌ی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شده‌است. اگر صفحه‌ی شمارشگر آن‌را باز کنیم، با کلیک بر روی دکمه‌ی آن، هرچند مقدار current count افزایش می‌یابد، اما شاهد post-back متداولی به سمت سرور نیستیم و این صفحه بسیار شبیه به صفحات برنامه‌های SPA (تک صفحه‌ای وب) به نظر می‌رسد:


              همانطور که عنوان شد، مدخل blazor.server.js فایل Pages\_Host.cshtml، کار به روز رسانی UI و هدایت رخ‌دادها را به سمت سرور به صورت خودکار انجام می‌دهد. به همین جهت است که post-back ای را مشاهده نمی‌کنیم و برنامه، شبیه به یک برنامه‌ی SPA به نظر می‌رسد؛ هر چند تمام رندرهای آن سمت سرور انجام می‌شوند و توسط SignalR به سمت کلاینت بازگشت داده خواهند شد.
              برای نمونه اگر بر روی دکمه‌ی شمارشگر کلیک کنیم، در برگه‌ی network مرورگر، هیچ اثری از آن مشاهده نمی‌شود (هیچ رفت و برگشتی را مشاهده نمی‌کنیم). علت اینجا است که اطلاعات متناظر با این کلیک، از طریق web socket باز شده‌ی توسط SignalR، به سمت سرور ارسال شده و نتیجه‌ی واکنش به این کلیک‌ها و رندر HTML نهایی سمت سرور آن، از همین طریق به سمت کلاینت بازگشت داده می‌شود.


              بررسی ساختار پروژه‌ی Blazor WASM و اجرای آن

              پس از اجرای دستور dotnet new blazorwasm در یک پوشه‌ی خالی و ایجاد پروژه‌ی ابتدایی آن:


              همان صفحات پروژه‌ی خالی Blazor Server در اینجا نیز قابل مشاهده هستند. این برنامه‌ی نمونه، به همراه سه صفحه‌ی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شده‌است. صفحات و کامپوننت‌های پوشه‌های Pages و Shared نیز دقیقا همانند پروژه‌ی Blazor Server قابل مشاهده هستند. مفاهیمی مانند فایل‌های Imports.razor_ و App.razor نیز مانند قبل هستند.

              البته در اینجا فایل Startup ای مشاهده نمی‌شود و تمام تنظیمات آغازین برنامه، داخل فایل Program.cs انجام خواهند شد:
              namespace BlazorWasmSample
              {
                  public class Program
                  {
                      public static async Task Main(string[] args)
                      {
                          var builder = WebAssemblyHostBuilder.CreateDefault(args);
                          builder.RootComponents.Add<App>("#app");
              
                          builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
              
                          await builder.Build().RunAsync();
                      }
                  }
              }
              در اینجا ابتدا کامپوننت App.razor را به برنامه معرفی می‌کند که ساختار آن با نمونه‌ی مشابه Blazor Server دقیقا یکی است. سپس سرویسی را برای دسترسی به HttpClient، به سیستم تزریق وابستگی‌های برنامه معرفی می‌کند. هدف از آن، دسترسی ساده‌تر به endpoint‌های یک ASP.NET Core Web API است. از این جهت که در یک برنامه‌ی سمت کلاینت، دیگر دسترسی مستقیمی به سرویس‌های سمت سرور را نداریم و برای کار با آن‌ها همانند سایر برنامه‌های SPA که از Ajax استفاده می‌کنند، در اینجا از HttpClient برای کار با Web API‌های مختلف استفاده می‌شود.

              تفاوت ساختاری دیگر این پروژه‌ی WASM، با نمونه‌ی Blazor Server، ساختار پوشه‌ی wwwroot آن است:


              که به همراه فایل جدید نمونه‌ی wwwroot\sample-data\weather.json است؛ بجای سرویس متناظر سمت سرور آن در برنامه‌ی blazor server. همچنین فایل جدید wwwroot\index.html نیز قابل مشاهده‌است و محتوای تگ body آن به صورت زیر است:
              <body>
                  <div id="app">Loading...</div>
              
                  <div id="blazor-error-ui">
                      An unhandled error has occurred.
                      <a href="" class="reload">Reload</a>
                      <a class="dismiss">🗙</a>
                  </div>
                  <script src="_framework/blazor.webassembly.js"></script>
              </body>
              - چون این برنامه، یک برنامه‌ی سمت کلاینت است، اینبار بجای فایل Host_ سمت سرور، فایل index.html سمت کلاینت را برای ارائه‌ی آغازین برنامه داریم که وابستگی به دات نت ندارد و توسط مرورگر قابل درک است.
              - در ابتدای بارگذاری برنامه، یک loading نمایش داده می‌شود که در اینجا نحوه‌ی تعریف آن مشخص است. همچنین اگر خطایی رخ دهد نیز توسط div ای با id مساوی blazor-error-ui اطلاع رسانی می‌شود.
              - مدخل blazor.webassembly.js در اینجا، کار دریافت وب اسمبلی و فایل‌های NET runtime. را انجام می‌دهد؛ برخلاف برنامه‌های Blazor Server که توسط فایل blazor.server.js، یک ارتباط دائم SignalR را با سرور برقرار می‌کردند تا کدهای رندر شده‌ی سمت سرور را دریافت و نمایش دهند و یا اطلاعاتی را به سمت سرور ارسال کنند: برای مثال بر روی دکمه‌ای کلیک شده‌است، اطلاعات مربوطه را در سمت سرور پردازش کن و نتیجه‌ی نهایی رندر شده را بازگشت بده. اما در اینجا همه چیز داخل مرورگر اجرا می‌شود و برای این نوع اعمال، رفت و برگشتی به سمت سرور صورت نمی‌گیرد. به همین جهت تمام کدهای #C ما به سمت کلاینت ارسال شده و داخل مرورگر به کمک فناوری وب اسمبلی، اجرا می‌شوند. در اینجا از لحاظ ارسال تمام کدهای مرتبط با UI برنامه‌ی سمت کلاینت به مرورگر کاربر، تفاوتی با فریم‌ورک‌هایی مانند Angular و یا React نیست و آن‌ها هم تمام کدهای UI برنامه را کامپایل کرده و یکجا ارسال می‌کنند.

              در ادامه در همان پوشه، دستور dotnet run را اجرا می‌کنیم تا پروژه کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.


              که به همراه 205 درخواست و نزدیک به 9.6 MB دریافت اطلاعات از سمت سرور است. البته اگر همین صفحه را refresh کنیم، دیگر شاهد دریافت مجدد فایل‌های DLL مربوط به NET Runtime. نخواهیم بود و اینبار از کش مرورگر خوانده می‌شوند:


              در این برنامه‌ی سمت کلاینت، ابتدا تمام فایل‌های NET Runtime. و وب اسمبلی دریافت شده و سپس اجرای تغییرات UI، در همین سمت و بدون نیاز به اتصال دائم SignalR ای به سمت سرور، پردازش و نمایش داده می‌شوند. به همین جهت زمانیکه بر روی دکمه‌ی شمارشگر آن کلیک می‌کنیم، اتفاقی در برگه‌ی network مرورگر ثبت نمی‌شود و رفت و برگشتی به سمت سرور صورت نمی‌گیرد.

              عدم وجود اتصال SignalR، مزیت امکان اجرای آفلاین برنامه‌ی WASM را نیز میسر می‌کند. برای مثال یکبار دیگر همان برنامه‌ی Blazor Server را به کمک دستور dotnet run اجرا کنید. سپس آن‌را در مرورگر در آدرس https://localhost:5001 باز کنید. اکنون پنجره‌ی کنسولی که dotnet run را اجرا کرده، خاتمه دهید (قسمت اجرای سمت سرور آن‌را ببندید).


              بلافاصله تصویر «سعی در اتصال مجدد» فوق را مشاهده خواهیم کرد که به دلیل قطع اتصال SignalR رخ داده‌است. یعنی یک برنامه‌ی Blazor Server، بدون این اتصال دائم، قادر به ادامه‌ی فعالیت نیست. اما چنین محدودیتی با برنامه‌های Blazor WASM وجود ندارد.
              البته بدیهی است اگر یک Web API سمت سرور برای ارائه‌ی اطلاعاتی به برنامه‌ی WASM نیاز باشد، API سمت سرور برنامه نیز باید جهت کار و نمایش اطلاعات در سمت کلاینت در دسترس باشد؛ وگرنه قسمت‌های متناظر با آن، قادر به نمایش و یا ثبت اطلاعات نخواهند بود.
              مطالب
              شروع به کار با EF Core 1.0 - قسمت 2 - به روز رسانی ساختار بانک اطلاعاتی
              پس از برپایی تنظیمات اولیه‌ی کار با EF Core در ASP.NET Core، اکنون نوبت به تبدیل کلاس Person، به جدول معادل آن در بانک اطلاعاتی برنامه است. در EF Core نیز همانند EF Code First 6.x، برای انجام یک چنین اعمالی از مفهومی به نام Migrations استفاده می‌شود که در ادامه به آن خواهیم پرداخت.


              پیشنیازهای کار با EF Core Migrations

              در قسمت قبل در حین بررسی «برپایی تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0»، چهار مدخل جدید را به فایل project.json برنامه اضافه کردیم. مدخل جدید Microsoft.EntityFrameworkCore.Tools که به قسمت tools آن اضافه شد، پیشنیاز اصلی کار با EF Core Migrations است.


              بررسی ابزارهای خط فرمان EF Core و تشکیل ساختار بانک اطلاعاتی بر اساس کلاس‌های برنامه

              پس از تکمیل پیشنیازهای کار با EF Core، از طریق خط فرمان به پوشه‌ی جاری پروژه وارد شده و دستور dotnet ef را صادر کنید.
              یک نکته: در ویندوز اگر در پوشه‌ای، کلید shift را نگه دارید و در آن پوشه کلیک راست کنید، در منوی باز شده، گزینه‌ی جدیدی را به نام Open command window here مشاهده خواهید کرد که می‌تواند به سرعت خط فرمان را از پوشه‌ی جاری شروع کند.

              خروجی صدور فرمان dotnet ef را در ذیل مشاهده می‌کنید:
              D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef
                                   _/\__
                             ---==/    \\
                       ___  ___   |.    \|\
                      | __|| __|  |  )   \\\
                      | _| | _|   \_/ |  //|\\
                      |___||_|       /   \\\/\\
              Entity Framework .NET Core CLI Commands 1.0.0-preview2-21431
              Usage: dotnet ef [options] [command]
              Options:
                -h|--help                      Show help information
                -v|--verbose                   Enable verbose output
                --version                      Show version information
                --assembly <ASSEMBLY>          The assembly file to load.
                --startup-assembly <ASSEMBLY>  The assembly file containing the startup class.
                --data-dir <DIR>               The folder used as the data directory (defaults to current working directory).
                --project-dir <DIR>            The folder used as the project directory (defaults to current working directory).
                --content-root-path <DIR>      The folder used as the content root path for the application (defaults to application base directory).
                --root-namespace <NAMESPACE>   The root namespace of the target project (defaults to the project assembly name).
              Commands:
                database    Commands to manage your database
                dbcontext   Commands to manage your DbContext types
                migrations  Commands to manage your migrations
              Use "dotnet ef [command] --help" for more information about a command.
              در قسمت Commands آن در انتهای لیست، از فرمان migrations آن استفاده خواهیم کرد. برای این منظور در همین پوشه‌ی جاری، دستور ذیل را صادر کنید:
               D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations add InitialDatabase
              دستورات migrations با dotnet ef migrations شروع شده و سپس یک سری پارامتر را دریافت می‌کنند برای مثال در اینجا سوئیچ add، به همراه یک نام دلخواه ذکر شده‌است (نام این مرحله را InitialDatabase گذاشته‌ایم). پس از فراخوانی این دستور، اگر به Solution explorer مراجعه کنید، پوشه‌ی جدید Migrations، قابل مشاهده است:


              نام دلخواه InitialDatabase را در انتهای نام فایل 13950526050417_InitialDatabase مشاهده می‌کنید.
              اگر قصد حذف این مرحله را داشته باشیم، می‌توان دستور dotnet ef migrations remove را مجددا صادر کرد.

              فایل 13950526050417_InitialDatabase به همراه کلاسی است که در آن دو متد Up و Down قابل مشاهده هستند. متد Up نحوه‌ی ایجاد جدول جدیدی را از کلاس Person بیان می‌کند و متد Down نحوه‌ی Drop این جدول را پیاده سازی کرده‌است.
              فایل ApplicationDbContextModelSnapshot.cs دارای کلاسی است که خلاصه‌ای از تعاریف موجودیت‌های ذکر شده‌ی در DB Context برنامه را به همراه دارد و تفسیر آن‌ها را از دیدگاه  EF در اینجا می‌توان مشاهده کرد.

              پس از مرحله‌ی افزودن migrations، نوبت به اعمال آن به بانک اطلاعاتی است. تا اینجا EF تنها متدهای Up و Down مربوط به ساخت و حذف ساختار جداول را ایجاد کرده‌است. اما هنوز آن‌ها را به بانک اطلاعاتی برنامه اعمال نکرده‌است. برای اینکار در پوشه‌ی جاری دستور ذیل را صادر کنید:
               D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef database update
              Applying migration '13950526050417_InitialDatabase'.
              Done.
              همانطور که ملاحظه می‌کنید، دستور dotnet ef database update سبب اعمال اطلاعات فایل 13950526050417_InitialDatabase به بانک اطلاعاتی شده‌است.
              اکنون اگر به لیست بانک‌های اطلاعاتی مراجعه کنیم، بانک اطلاعاتی جدید TestDbCore2016 را به همراه جدول متناظر کلاس Person می‌توان مشاهده کرد:


              در اینجا جدول دیگری به نام __EFMigrationsHistory نیز قابل مشاهده‌است که کار آن ذخیره سازی وضعیت فعلی Migrations در بانک اطلاعاتی، جهت مقایسه‌های آتی است. این جدول صرفا توسط ابزارهای EF استفاده می‌شود و نباید به صورت مستقیم تغییری در آن ایجاد کنید.


              مقدار دهی اولیه‌ی جداول بانک‌های اطلاعاتی در EF Core

              در همین حالت اگر کنترلر TestDB مطرح شده‌ی در انتهای بحث قسمت قبل را اجرا کنیم، به این استثناء خواهیم رسید:


              این تصویر بدین معنا است که کار Migrations موفقیت آمیز بوده‌است و اینبار امکان اتصال و کار با بانک اطلاعاتی وجود دارد، اما این جدول حاوی اطلاعات اولیه‌ای برای نمایش نیست.
              در نگارش قبلی EF Code First، امکانات Migrations به همراه یک متد Seed نیز بود که توسط آن کار مقدار دهی اولیه‌ی جداول را می‌توان انجام داد (زمانیکه جدولی ایجاد می‌شود، در همان هنگام، چند رکورد خاص نیز به آن اضافه شوند. برای مثال به جدول کاربران، رکورد اولین کاربر یا همان Admin اضافه شود). این متد در EF Core 1.0 وجود ندارد.
              برای این منظور کلاس جدیدی را به نام ApplicationDbContextSeedData به همان پوشه‌ی جدید Migrations اضافه کنید؛ با این محتوا:
              using System.Collections.Generic;
              using System.Linq;
              using Core1RtmEmptyTest.Entities;
              using Microsoft.Extensions.DependencyInjection;
              
              namespace Core1RtmEmptyTest.Migrations
              {
                  public static class ApplicationDbContextSeedData
                  {
                      public static void SeedData(this IServiceScopeFactory scopeFactory)
                      {
                          using (var serviceScope = scopeFactory.CreateScope())
                          {
                              var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
                              if (!context.Persons.Any())
                              {
                                  var persons = new List<Person>
                                  {
                                      new Person
                                      {
                                          FirstName = "Admin",
                                          LastName = "User"
                                      }
                                  };
                                  context.AddRange(persons);
                                  context.SaveChanges();
                              }
                          }
                      }
                  }
              }
              و سپس نحوه‌ی فراخوانی آن در متد Configure کلاس آغازین برنامه به صورت زیر است:
              public void Configure(IServiceScopeFactory scopeFactory)
              {
                  scopeFactory.SeedData();
              به همراه این تغییر در نحوه‌ی معرفی Db Context برنامه:
              public void ConfigureServices(IServiceCollection services)
              {
                 services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
              توضیحات:
              - برای پیاده سازی الگوی واحد کار، اولین قدم، مشخص سازی طول عمر Db Context برنامه است. برای اینکه تنها یک Context در طول یک درخواست وهله سازی شود، نیاز است به نحو صریحی طول عمر آن‌را به حالت Scoped تنظیم کرد. متد AddDbContext دارای پارامتری است که این طول عمر را دریافت می‌کند. بنابراین در اینجا ServiceLifetime.Scoped ذکر شده‌است. همچنین در این مثال از نمونه‌ای که IConfigurationRoot به سازنده‌ی کلاس ApplicationDbContext تزریق شده (نکته‌ی انتهای بحث قسمت قبل)، استفاده شده‌است. به همین جهت تنظیمات options آن‌را ملاحظه نمی‌کنید.
              - مرحله‌ی بعد نحوه‌ی دسترسی به این سرویس ثبت شده در یک کلاس static دارای متدی الحاقی است. در اینجا دیگر دسترسی مستقیمی به تزریق وابستگی‌ها نداریم و باید کار را با  IServiceScopeFactory شروع کنیم. در اینجا می‌توانیم به صورت دستی یک Scope را ایجاد کرده، سپس توسط ServiceProvider آن، به سرویس ApplicationDbContext دسترسی پیدا کنیم و در ادامه از آن به نحو متداولی استفاده نمائیم. IServiceScopeFactory جزو سرویس‌های توکار ASP.NET Core است و در صورت ذکر آن به عنوان پارامتر جدیدی در متد Configure، به صورت خودکار وهله سازی شده و در اختیار ما قرار می‌گیرد.
              - نکته‌ی مهمی که در اینجا بکار رفته‌است، ایجاد Scope و dispose خودکار آن توسط عبارت using است. باید دقت داشت که ایجاد Scope و تخریب آن به صورت خودکار در ابتدا و انتهای درخواست‌ها توسط ASP.NET Core انجام می‌شود. اما چون شروع کار ما از متد Configure است، در اینجا خارج از Scope قرار داریم و باید مدیریت ایجاد و تخریب آن‌را به صورت دستی انجام دهیم که نمونه‌ای از آن‌را در متد SeedData کلاس ApplicationDbContextSeedData ملاحظه می‌کنید. در اینجا Scope ایی ایجاد شده‌است. سپس داده‌های اولیه‌ی مدنظر به بانک اطلاعاتی اضافه گردیده و در آخر این Scope تخریب شده‌است.
              - اگر کار ایجاد و تخریب scope، به نحوی که مشخص شده‌است انجام نگیرد، طول عمر درخواستی خارج از Scope، همواره Singleton خواهد بود. چون خارج از طول عمر درخواست جاری قرار داریم و هنوز کار به سرویس دهی درخواست‌ها نرسیده‌است. بنابراین مدیریت Scopeها هنوز شروع نشده‌است و باید به صورت دستی انجام شود.

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


              که به معنای کار کردن متد SeedData و ثبت اطلاعات اولیه‌ای در بانک اطلاعاتی است.


              اعمال تغییرات به مدل‌های برنامه و به روز رسانی ساختار بانک اطلاعاتی

              فرض کنید به کلاس Person قسمت قبل، خاصیت Age را هم اضافه کرده‌ایم:
              namespace Core1RtmEmptyTest.Entities
              {
                  public class Person
                  {
                      public int PersonId { get; set; }
                      public string FirstName { get; set; }
                      public string LastName { get; set; }
              
                      public int Age { get; set; }
                  }
              }
              در این حالت اگر برنامه را اجرا کنیم، به استثنای ذیل خواهیم رسید:
               An unhandled exception occurred while processing the request.
              SqlException: Invalid column name 'Age'.
              برای رفع این مشکل نیاز است مجددا مراحل Migrations را اجرا کرد:
              D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations add v2
              D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef database update
              در اینجا همان دستورات قبل را مجددا اجرا می‌کنیم. با این تفاوت که اینبار نام دلخواه این مرحله را مثلا v2، به معنای نگارش دوم وارد کرده‌ایم.
              با اجرا این دستورات، فایل جدید 13950526073248_v2 به پوشه‌ی Migrations اضافه می‌شود. این فایل حاوی نحوه‌ی به روز رسانی بانک اطلاعاتی، بر اساس خاصیت جدید Age است. سپس با اجرای دستور dotnet ef database update، کار به روز رسانی بانک اطلاعاتی بر اساس مرحله‌ی v2 انجام می‌شود.


              بنابراین هر بار که تغییری را در مدل‌های خود ایجاد می‌کنید، یکبار باید کلاس مهاجرت آن‌را ایجاد کنید و سپس آن‌را به بانک اطلاعاتی اعمال نمائید.


              تهیه اسکریپت تغییرات بجای اعمال تغییرات توسط ابزارهای EF

              شاید علاقمند باشید که پیش از اعمال تغییرات به بانک اطلاعاتی، یک اسکریپت SQL از آن تهیه کنید (جهت مطالعه و یا اعمال دستی آن توسط خودتان). برای اینکار می‌توانید دستور ذیل را در پوشه‌ی جاری پروژه اجرا کنید:
               D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations script -o v2.sql
              در این حالت اسکریپت SQL تغییرات، در فایلی به نام v2.sql، در ریشه‌ی جاری پروژه تولید می‌شود.


              تغییرات ساختار جدول __EFMigrationsHistory در EF Core 1.0


              در EF 6.x، ساختار اطلاعات جدول نگهداری تاریخچه‌ی تغییرات، بسیار پیچیده بود و شامل رشته‌ای gzip شده‌ی حاوی یک snapshot از کل ساختار دیتابیس در هر مرحله‌ی migration بود. در این نگارش، این snapshot حذف شده‌است و بجای آن فایل ApplicationDbContextModelSnapshot.cs را مشاهده می‌کنید (تنها یک snapshot به ازای کل context برنامه). همچنین در اینجا کاملا مشخص است که چه مراحلی به بانک اطلاعاتی اعمال شده‌اند و دیگر خبری از رشته‌ی gzip شده‌ی قبلی نیست (تصویر فوق).

              در شکل زیر ساختار قبلی این جدول را در EF 6.x مشاهده می‌کنید. در EF 6.x حتی فضای نام کلاس‌های موجودیت‌های برنامه هم مهم هستند و در صورت تغییر، مشکل ایجاد می‌شود:



              مهاجرت خودکار از EF Core حذف شده‌است

              در EF 6.x در کنار کلاس Db Context یک کلاس Configuration هم وجود داشت که برای مثال امکان چنین تعریفی در آن میسر هست:
              public Configuration()
              {
                 AutomaticMigrationsEnabled = true;
              }
              کار آن مهاجرت خودکار اطلاعات context به بانک اطلاعاتی بود؛ بدون نیازی به استفاده از دستورات خط فرمان مرتبط. تمام این موارد از EF Core حذف شده‌اند و علت آن‌را می‌توانید در توضیحات یکی از اعضای تیم EF Core در اینجا مطالعه کنید و خلاصه‌ی آن به این شرح است:
              با حذف مهاجرت خودکار:
              - دیگر نیازی نیست تا model snapshots در بانک اطلاعاتی ذخیره شوند (همان ساده شدن ساختار جدول ذخیره سازی تاریخچه‌ی مهاجرت‌های فوق).
              - در حالت افزودن یک مرحله‌ی مهاجرت، دیگر نیازی به کوئری گرفتن از بانک اطلاعاتی نخواهد بود (سرعت بیشتر).
              - می‌توان چندین مرحله‌ی مهاجرت را افزود بدون اینکه الزاما مجبور به اعمال آن‌ها به بانک اطلاعاتی باشیم.
              - کاهش کدهای مدیریت ساختار بانک اطلاعاتی.
              - تیم‌ها برای یکی کردن تغییرات خود مشکلی نخواهند داشت چون دیگر snapshot مدل‌ها در جدول __EFMigrationsHistory ذخیره نمی‌شود.

              بنابراین در EF Core می‌توان مهاجرت v1 را اضافه کرد. سپس تغییراتی را در کدها اعمال کرد. در ادامه مهاجرت v2 را تولید کرد و در آخر کار اعمال یکجای این‌ها را به بانک اطلاعاتی انجام داد.

              هرچند در اینجا اگر می‌خواهید مرحله‌ی اجرای دستور dotnet ef database update را حذف کنید، می‌توانید از کدهای ذیل نیز استفاده نمائید:
              using Core1RtmEmptyTest.Entities;
              using Microsoft.EntityFrameworkCore;
              using Microsoft.Extensions.DependencyInjection;
              
              namespace Core1RtmEmptyTest.Migrations
              {
                  public static class DbInitialization
                  {
                      public static void Initialize(this IServiceScopeFactory scopeFactory)
                      {
                          using (var serviceScope = scopeFactory.CreateScope())
                          {
                              var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
                              // Applies any pending migrations for the context to the database.
                              // Will create the database if it does not already exist.
                              context.Database.Migrate();
                          }
                      }
                  }
              }
              روش فراخوانی آن نیز همانند روش فراخوانی متد SeedData است که پیشتر بحث شد.
              کار متد Migrate، ایجاد بانک اطلاعاتی در صورت عدم وجود و سپس اعمال تمام مراحل migration ایی است که در جدول __EFMigrationsHistory ثبت نشده‌اند (دقیقا همان کار دستور dotnet ef database update را انجام می‌دهد).


              تفاوت متد Database.EnsureCreated با متد Database.Migrate

              اگر به متدهای context.Database دقت کنید، یکی از آن‌ها EnsureCreated نام دارد. این متد نیز سبب تولید بانک اطلاعاتی بر اساس ساختار Context برنامه می‌شود. اما هدف آن صرفا استفاده‌ی از آن در آزمون‌های واحد سریع است. از این جهت که جدول __EFMigrationsHistory را تولید نمی‌کند (برخلاف متد Migrate). بنابراین بجز آزمون‌های واحد، در جای دیگری از آن استفاده نکنید چون به دلیل عدم تولید جدول __EFMigrationsHistory توسط آن، قابلیت استفاده‌ی از بانک اطلاعاتی تولید شده‌ی توسط آن با امکانات migrations وجود ندارد. در پایان آزمون واحد نیز می‌توان از متد EnsureDeleted برای حذف این بانک اطلاعاتی موقتی استفاده کرد.



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

              برای این کار روش استاندارد و مستقیمی وجود ندارد؛ اما  با استفاده از بعضی از روشها مانند ارسال عکس مخفی، برخی تگ‌هایی که یک آدرس را فراخوانی می‌کنند (مانند bgsound)، کلیک روی لینک و یا ترکیبی از چند روش می‌توان تا حدود زیادی به اهداف فوق رسید.

              روشی که بررسی خواهیم کرد، مبتنی بر ارسال عکس بوده و برای شروع، به یک عکس بسیار کوچک (1X1) با حجم کم و غیرقابل دید (Transparent) نیاز داریم که می‌توانید یکی از آنها را از این آدرس انتخاب کنید. در این روش به همراه ایمیل، این عکس را ارسال خواهیم کرد (در تگ img). عکس باید بگونه‌ای باشد که ظاهر ایمیل را تحت تأثیر قرار ندهد و آدرس عکس باید طوری تنظیم شود که به برنامه ما یک درخواست را ارسال کند و برنامه با توجه به درخواست ارسال شده، عملیات لازم را انجام دهد. 

              مثالی از عکسی که باید ارسال شود: 

              <img src='http://www.example.com/1x1.gif' style='width:1px;height:1px;'>

              برای شروع یک برنامه‌ی ASP.NET Core را ایجاد کرده و مراحل ارسال ایمیل را طی نمایید:
              private bool SendEmail(string mail_to, string mail_subject, string mail_body)
              {
                  bool result = false;
                  try
                  {
                    SmtpClient client = new SmtpClient("smtp.gmail.com");
                    client.UseDefaultCredentials = false;
                    client.EnableSsl = true;
                    client.Port = 587;
                    client.Credentials = new NetworkCredential("from@mail.com", "your_gmail_password");
                    MailMessage mailMessage = new MailMessage();       mailMessage.From = new MailAddress("from@mail.com");       mailMessage.To.Add(mail_to);       mailMessage.Body = mail_body;       mailMessage.Subject = mail_subject;       client.Send(mailMessage);       result = true;      }      catch (Exception ex)      {       result = false;      }      return result; }
              public IActionResult SendEmailWithTransparentImage()
              {
                 var email_body = "your_email_body";            
                 var imageUrl = Url.Action("ImageRequestFromEmail", "Home", new {user_id=12345}, protocol: Url.ActionContext.HttpContext.Request.Scheme);            
                 var imageTag = $"<img src='{imageUrl}' style='width:1px;height:1px;'>";            
                 email_body += imageTag;
                 var result = SendEmail("to@email.com", "test image", email_body);
                 return View(result);
              }

              سپس اکشن متد پاسخ به درخواست عکس از طرف سرویس دهنده ایمیل را ایجاد نمایید:
              [Route("1x1.gif")]
              public IActionResult ImageResponse()
              {
                 //درخواست عکس ارسال شده و اینجا باید عملیات دلخواه را انجام دهیم
                 var emailOpenDate = DateTime.Now;
                          
                 //سپس عکس را در جواب درخواست ارسال میکنیم
                 byte[] imagegBytes = Convert.FromBase64String("R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAI=");
                 return File(imagegBytes, "image/gif");
              }
              اگر اکشن SendEmailWithTransparentImage فراخوانی شود، یک ایمیل که حاوی عکس مورد نظر است، ارسال می‌شود. کاربر با باز کردن ایمیل باعث می‌شود سرویس دهنده ایمیل، درخواست خواندن عکس را به برنامه شما داده و اکشنی را که برای پاسخ به درخواست ارسال عکس نوشته شده، فراخوانی کند. از این طریق متوجه خواهیم شد که ایمیل توسط کاربر باز و مشاهده شده‌است.
              بدیهی است می‌توان یک سری کوئری استرینگ را برای بهتر شدن فرآیند، به آدرس عکس مورد نظر اضافه کرد:
              <img src='http://www.example.com/1x1.gif?user_id=12345' style='width:1px;height:1px;'>

              در این صورت باید تغییراتی را در کدها لحاظ کنیم:
              در اکشن SendEmailWithTransparentImage  :
              var imageUrl = Url.Action("ImageResponse, "Home", new {user_id=12345},protocol: Url.ActionContext.HttpContext.Request.Scheme);
              و امضای اکشن ImageResponse را به‌صورت زیر تغییر می‌دهیم:
              public IActionResult ImageResponse(int user_id)

              استفاده از عکس‌های کوچک به علت سربار کم و پشتیبانی مرورگرها، بسیار مرسوم بوده و بسیاری از آنالیزورهای سایت مانند google analytics، سایت‌های ارسال انبوه ایمیل و ...  برای رهگیری رفتار کاربران از این روش استفاده می‌کنند. 
              برای مثال اگر در همین سایت Developer Tools مرورگر را باز کنید و صفحه را رفرش کنید در تب Network، با کمی دقت عکسی با کوئری استرینگ پیچیده خواهید یافت که مربوط به google analytics است و تقریبا تمام اطلاعات مورد نیاز خود در رابطه با رفتار کاربران در سایت جاری را با استفاده از این روش جمع آوری می‌کند.
              مطالب
              راهبری در 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 فراخوانی خواهد گردید.
              کدهای این مثال را از اینجا می‌توانید دریافت کنید.

              بازخوردهای دوره
              آشنایی با مدل برنامه نویسی TAP
              استفاده از async به معنای خالی کردن ترد جاری کدهای مدیریت شده‌ی دات نت است و انجام سایر کارهای برنامه و صبر کردن برای دریافت پاسخی است که در سمت کدهای مدیریت شده نیازی به پردازش و محاسبه ندارد.
              برای مثال در حالت کار با یک دیتابیس، این موتور بانک اطلاعاتی است که کوئری رسیده را پردازش می‌کند و برنامه‌ی ما صرفا درخواستی را به آن ارائه داده است. به این ترتیب در اینجا استفاده از async برای خالی کردن ترد جاری و صبر کردن جهت دریافت نتیجه‌ی اطلاعات از سرور مفید است و میزان پاسخ‌دهی برنامه را بالا می‌برد.
              بنابراین استفاده از async در سمت کدهای دات نتی، تاثیری بر روی عملکرد یک بانک اطلاعاتی ندارد. فقط در سمت کدهای ما است که برنامه تا رسیدن و محاسبه‌ی درخواست توسط بانک اطلاعاتی، هنگ نمی‌کند.
              در این حالت اگر برنامه‌ی شما ویندوزی است، ترد UI آن آزاد شده و برنامه مدام در حال هنگ به نظر نمی‌رسد. اگر برنامه‌ی وب است، ترد جاری آن آزاد شده و thread pool برنامه می‌تواند از این ترد آزاد شده، برای پردازش سایر درخواست‌های رسیده توسط کاربران استفاده کند. به این ترتیب بازدهی و اصطلاحا throughput سرور افزایش پیدا می‌کند.
              در حال حاضر تمام APIهای جدید مایکروسافت نسخه‌ی async را هم اضافه کرده‌اند. برای مثال اگر از EF استفاده می‌کنید، از نسخه‌ی 6 آن به بعد، متدهایی مانند ToListAsync برای کوئری گرفتن معمولی غیرهمزمان و SaveChangesAsync برای ذخیره سازی اطلاعات به صورت غیرهمزمان، اضافه شده‌اند. یک مثال کامل در این مورد در اینجا
              البته بدیهی است تمام ORMهای دات نتی در سطح پایین خودشان از ADO.NET استفاده می‌کنند. ADO.NET نیز Async API سازگار با دات نت 4.5 به بعد را مدتی است که اضافه کرده‌است. برای مثال متدهایی مانند ExecuteReaderAsync ، GetFieldValueAsync و ExecuteNonQueryAsync و امثال آن به زیر ساخت ADO.NET اضافه شده‌اند. اطلاعات بیشتر
              مطالب
              ASP.NET Web API - قسمت دوم
              در قسمت اول به دلایل ایجاد ASP.NET Web API پرداخته شد. در این قسمت، یک مثال ساده از Web API را بررسی می‌کنیم.
              تلاش‌های بسیاری توسط توسعه گران صورت پذیرفته است تا فرایند ایجاد وب سرویس WCF در بستر HTTP آسان شود. امروزه وب سرویس هایی که از قالب REST استفاده می‌کنند مطرح هستند.
              ASP.NET Web API از مفاهیم موجود در ASP.NET MVC مانند Controllerها استفاده می‌کند و بر مبنای آنها ساخته شده است. بدین شکل، توسعه گر می‌تواند با دانش موجود خود به سادگی وب سرویس‌های مورد نظر را ایجاد کند. Web API، پروتوکل SOAP را به کتاب‌های تاریخی! سپرده است تا از آن به عنوان روشی برای تعامل بین سیستم‌ها یاد شود. امروزه به دلیل فراگیری پروتوکل HTTP، بیشتر محیط‌های برنامه نویسی و سیستم ها، از مبانی اولیه‌ی پروتوکل HTTP مانند اَفعال آن پشتیبانی می‌کنند.
              حال قصد داریم تا وب سرویسی را که در قسمت اول با WCF ایجاد کردیم، این بار با استفاده از Web API ایجاد کنیم. به تفاوت این دو دقت کنید.

              using System.Web.Http;
              
              namespace MvcApplication1.Controllers
              {
                  public class ValuesController : ApiController        
                  {
                      // GET api/values/5
                      public string Get(int id)                         
                      {
                          return string.Format("You entered: {0}", id);
                      }
                  }
              }
              
              اولین تفاوتی که مشهود است، تعداد خطوط کمتر مورد نیاز برای ایجاد وب سرویس با استفاده از Web API است، چون نیاز به interface و کلاس پیاده ساز آن وجود ندارد. در Controller، Web APIهایی که در نقش وب سرویس هستند از کلاس ApiController ارث می‌برند. اَعمال مورد نظر در قالب متدها در Controller تعریف می‌شوند. در مثال قبل، متد Get، یکی از اَعمال است.
              نحوه‌ی برگشت یک مقدار از متدها در Web API، مانند WCF است. می‌توانید خروجی متد Get را با اجرای پروژه‌ی قبل در Visual Studio و تست آن با یک مرورگر ملاحظه کنید. دقت داشته باشید که یکی از اصولی که Web API به آن معتقد است این است که وب سرویس‌ها می‌توانند ساده باشند. در Web API، تست و دیباگ وب سرویس‌ها بسیار راحت است. با مرورگر Internet Explorer به آدرس http://localhost:{port}/api/values/3 بروید. پیش از آن، برنامه‌ی Fiddler را اجرا کنید. شکل ذیل، نتیجه را نشان می‌دهد.

              در اینجا نتیجه، عبارت "You entered: 3" است که به صورت یک متن ساده برگشت داده شده است.

              ایجاد یک پروژه‌ی Web API
              در Visual Studio، مسیر ذیل را طی کنید.

              File> New> Project> Installed Templates> Visual C#> Web> ASP.NET MVC 4 Web Application 

                نام پروژه را HelloWebAPI بگذارید و بر روی دکمه‌ی OK کلیک کنید (شکل ذیل)

              در فرمی که باز می‌شود، گزینه‌ی Web API را انتخاب و بر روی دکمه‌ی OK کلیک کنید (شکل ذیل). البته دقت داشته باشید که ما همیشه مجبور به استفاده از قالب Web API برای ایجاد پروژه‌های خود نیستیم. می‌توان در هر نوع پروژه ای از Web API استفاده کرد.

              اضافه کردن مدل
              مدل، شی ای است که نمایانگر داده‌ها در برنامه است. Web API می‌تواند به طور خودکار، مدل را به فرمت JSON، XML یا فرمت دلخواهی که خود می‌توانید برای آن ایجاد کنید تبدیل و سپس داده‌های تبدیل شده را در بدنه‌ی پاسخ HTTP به Client ارسال کند. تا زمانی که Client بتواند فرمت دریافتی را بخواند، می‌تواند از آن استفاده کند. بیشتر Clientها می‌توانند فرمت JSON یا XML را پردازش کنند. به علاوه، Client می‌تواند نوع فرمت درخواستی از Server را با تنظیم مقدار هدر Accept در درخواست ارسالی تعیین کند. اجازه بدهید کار خود را با ایجاد یک مدل ساده که نمایانگر یک محصول است آغاز کنیم.
              بر روی پوشه‌ی Models کلیک راست کرده و از منوی Add، گزینه‌ی Class را انتخاب کنید.

              نام کلاس را Product گذاشته و کدهای ذیل را در آن بنویسید.

              namespace HelloWebAPI.Models
              {
                  public class Product
                  {
                      public int Id { get; set; }
                      public string Name { get; set; }
                      public string Category { get; set; }
                      public decimal Price { get; set; }
                  }
              }

              مدل ما، چهار Property دارد که در کدهای قبل ملاحظه می‌کنید.

              اضافه کردن Controller
              در پروژه ای که با استفاده از قالب پیش فرض Web API ایجاد می‌شود، دو Controller نیز به طور خودکار در پروژه‌ی Controller قرار می‌گیرند:

              • HomeController: یک Controller معمول ASP.NET MVC است که ارتباطی با Web API ندارد.
              • ValuesController: یک Controller مختص Web API است که به عنوان یک مثال در پروژه قرار داده می‌شود.


              توجه: Controllerها در Web API بسیار شبیه به Controllerها در ASP.NET MVC هستند، با این تفاوت که به جای کلاس Controller، از کلاس ApiController ارث می‌برند و بزرگترین تفاوتی که در نگاه اول در متدهای این نوع کلاس‌ها به چشم می‌خورد این است که به جای برگشت Viewها، داده برگشت می‌دهند.

              کلاس ValuesController را حذف و یک Controller به پروژه اضافه کنید. بدین منظور، بر روی پوشه‌ی Controllers، کلیک راست کرده و از منوی Add، گزینه‌ی Controller را انتخاب کنید.

              توجه: در ASP.NET MVC 4 می‌توانید بر روی هر پوشه‌ی دلخواه در پروژه کلیک راست کرده و از منوی Add، گزینه‌ی Controller را انتخاب کنید. پیشتر فقط با کلیک راست بر روی پوشه‌ی Controller، این گزینه در دسترس بود. حال می‌توان کلاس‌های مرتبط با Controllerهای معمول را در یک پوشه و Controllerهای مربوط به قابلیت Web API را در پوشه‌ی دیگری قرار داد.

              نام Controller را ProductsController بگذارید، از قسمت Template، گزینه‌ی Empty API Controller را انتخاب و بر روی دکمه‌ی OK کلیک کنید (شکل ذیل).

              فایلی با نام ProductsController.cs در پوشه‌ی Controllers قرار می‌گیرد. آن را باز کنید و کدهای ذیل را در آن قرار دهید. 

              namespace HelloWebAPI.Controllers
              {
                  using System;
                  using System.Collections.Generic;
                  using System.Linq;
                  using System.Net;
                  using System.Net.Http;
                  using System.Web.Http;
                  using HelloWebAPI.Models;
              
                  public class ProductsController : ApiController
                  {
              
                      Product[] products = new Product[] 
                      { 
                          new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1.39M }, 
                          new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
                          new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
                      };
              
                      public IEnumerable<Product> GetAllProducts()
                      {
                          return products;
                      }
              
                      public Product GetProductById(int id)
                      {
                          var product = products.FirstOrDefault((p) => p.Id == id);
                          if (product == null)
                          {
                              var resp = new HttpResponseMessage(HttpStatusCode.NotFound);
                              throw new HttpResponseException(resp);
                          }
                          return product;
                      }
              
                      public IEnumerable<Product> GetProductsByCategory(string category)
                      {
                          return products.Where(
                              (p) => string.Equals(p.Category, category, 
                                  StringComparison.OrdinalIgnoreCase));
                      }
                  }
              }

              برای ساده نگهداشتن مثال، لیستی از محصولات را در یک آرایه قرار داده ایم اما واضح است که در یک پروژه‌ی واقعی، این لیست از پایگاه داده بازیابی می‌شود. در مورد کلاس‌های HttpResponseMessage و HttpResponseException بعداً توضیح می‌دهیم.
              در کدهای Controller قبل، سه متد تعریف شده اند: 

              • متد GetAllProducts که کل محصولات را در قالب نوع <IEnumerable<Product برگشت می‌دهد.
              • متد GetProductById که یک محصول را با استفاده از مشخصه‌ی آن (خصیصه‌ی Id) برگشت می‌دهد.
              • متد GetProductsByCategory که تمامی محصولات موجود در یک دسته‌ی خاص را برگشت می‌دهد.

              تمام شد! حال شما یک وب سرویس با استفاده از Web API ایجاد کرده اید. هر یک از متدهای قبل در Controller، به یک آدرس به شرح ذیل تناظر دارند.

              GetAllProducts به api/products/

              GetProductById به api/products/id/

              GetProductsByCategory به api/products/?category=category/

              در آدرس‌های قبل، id و category، مقادیری هستند که همراه با آدرس وارد می‌شوند و در پارامترهای متناظر خود در متدهای مربوطه قرار می‌گیرند. یک Client می‌تواند هر یک از متدها را با ارسال یک درخواست از نوع GET اجرا کند.

              در قسمت بعد، کار خود را با تست پروژه و نحوه‌ی تعامل jQuery با آن ادامه می‌دهیم.

              مطالب
              آشنایی با ساختار 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‌ها صورت نمی‌گیرد