نظرات مطالب
EF Code First #4
سلام و بسیار ممنون از مطالب آموزشی زیبا و واضح شما . امیدوارم خداوند در هر دو جهان پاداش این کار خیر شما را بدهد.
در مورد اینکه هاست‌ها از دات نت 4.5 پشتیبانی نمی‌کنند  و ما نمی‌توانیم به عنوان مثال از «Migrations»  یا برخی امکانت دیگر استفاده کنیم .آیا راه حلی برای این مساله وجود دارد یا فعلا باید این امکانات را در برنامه‌های تحت وب استفاده نکنیم ؟
نظرات مطالب
جایگزین کردن StructureMap با سیستم توکار تزریق وابستگی‌ها در ASP.NET Core 1.0
با سلام؛ آیا امکان دارد StructureMap را در یک کلاس دیگر بکار برد و در ConfigureServices  بصورت  services اضافه کرد و متد ConfigurationServices مقدار  Void را برگرداند؟ زیرا در تمامی مقاله‌های مطالعه شده همین روش بکار رفته. با تشکر
مطالب
نکات ویژه کار با عملیات نامتقارن در Blazor Server
 در برنامه‌های Blazor Server، تنها از یک نخ رابط کاربری واحد ( single UI thread ) استفاده نمی‌شود؛ بلکه هر نخی که در دسترس باشد، می‌تواند در موقع رندر، استفاده شود. علاوه بر این اگر از عملیات نامتقارن استفاده شود، زمانیکه به کلمه‌ی کلیدی await می‌رسیم، آنگاه نخ اختصاص داده شده‌ی برای ادامه پردازش متد، ممکن است لزوما همان چیزی نباشد که آن را شروع کرده است. برای نشان دادن این موضوع مثالی را در پیش می‌گیریم.
کامپوننتی را با نام  SynchronousInitComponent با کد زیر درنظر می‌گیریم. همانطور که از اسم آن مشخص است این کامپوننت به صورت متقارن یا همزمان پیاده‌سازی شده است:
<p>Sync rendered by thread @IdOfRenderingThread</p>

@code
{
  int IdOfRenderingThread;

  protected override void OnInitialized()
  {
    base.OnInitialized();
    IdOfRenderingThread =
      System.Threading.Thread.CurrentThread.ManagedThreadId;
  }
}
در حقیقت در متد OnInitialized آن، مقدار نخ جاری را توسط Thread.ManagedThreadId به دست می‌آوریم. بنابراین شماره نخ جاری پس از رندر شدن کامپوننت، در صفحه نمایش داده می‌شود.
حال در کامپوننت دیگری برای مثال کامپوننت index، کامپوننت همزمان فوق را به شکل زیر فراخوانی می‌کنیم:
@page "/"

<h1>Components with synchronous OnInitialized()</h1>
@for (int i = 0; i < 5; i++)
{
  <SynchronousInitComponent />
}
با این نتیجه:
Components with synchronous OnInitialized()
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
همانطور که ملاحظه می‌نمایید شناسه نخ یکسانی برای هر فراخوانی کامپوننت نشان داده می‌شود. بدیهی است در صورتیکه شما همین کد را اجرا کنید، ممکن است شماره نخ برنامه شما با کد من یکی نباشد؛ اما نتیجه یکی است. یعنی در تمامی موارد، یک عدد مشاهده می‌شود.
حال همین آزمایش را با متدهای نامتقارن یا ناهمزمان انجام می‌دهیم. کامپوننت AsynchronousInitComponent را با کد زیر درنظر بگیرید:
<p>Async rendered by thread @IdOfRenderingThread</p>

@code
{
  int IdOfRenderingThread;

  protected override async Task OnInitializedAsync()
  {
    // Runs synchronously as there is no code in base.OnInitialized(),
    // so the same thread is used
    await base.OnInitializedAsync().ConfigureAwait(false);
    IdOfRenderingThread =
      System.Threading.Thread.CurrentThread.ManagedThreadId;

    // Awaiting will schedule a job for later, and we will be assigned
    // whichever worker thread is next available
    await Task.Delay(1000).ConfigureAwait(false);
    IdOfRenderingThread =
      System.Threading.Thread.CurrentThread.ManagedThreadId;
  }
}
این کامپوننت هم دقیقا شبیه کامپوننت قبلی است؛ با این تفاوت که IdOfRenderingThread، مجددا بعد از یک تاخیر یک ثانیه‌ای مقداردهی شده‌است. این مقداردهی سبب رندر مجدد کامپوننت با تاخیر یک ثانیه می‌شود. حال در کامپوننت دیگری، کامپوننت غیرمتقارن فوق را به شکل زیر فراخوانی می‌کنیم:
@page "/async-init"

<h1>Components with asynchronous OnInitializedAsync()</h1>
@for (int i = 0; i < 5; i++)
{
  <AsynchronousInitComponent/>
}
با اجرا کردن برنامه، دقیقا نتایج، شبیه نتایج نتایج کامپوننت متقارن می‌باشد:
Components with asynchronous OnInitializedAsync()
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
اما تنها بعد از یک ثانیه (await Task.Delay(1000)) ، متدهای OnInitializedAsync کامپوننت‌ها، به پایان می‌رسند و مقدار IdOfRenderingThread را پیش از به پایان رسیدن رندر صفحه، به روز رسانی می‌نمایند. اینبار، دیگر مقادیر نخ‌ها متفاوت خواهند بود:
Components with asynchronous OnInitializedAsync()
Async rendered by thread 7
Async rendered by thread 18
Async rendered by thread 10
Async rendered by thread 13
Async rendered by thread 11
با توجه به مطالب مطرح شده به این نتیجه می‌رسیم که این موضوع می‌تواند هنگام استفاده از یک وابستگی غیر ایمن ( non-thread-safe dependency ) مانند DBContext در چندین کامپوننت باعث بروز مشکل شود. نمونه‌ای از نحوه‌ی رویارویی با مشکلات احتمالی آن، در اینجا و اینجا بررسی شده‌است.  
مطالب
ایجاد Attribute برای کامپوننت های Angular2
شما در انگیولار میتوانید با استفاده از روش‌های databinding برای یک تگ، خواصی را مقداردهی کنید. کد زیر یک نمونه از این خواص هاست:
<img [src]="...." />
و از طریق دایرکتیوهای موجود در انگیولار هم به شکل زیر عمل می‌کنید:
<img [ngClass]='..' />
تمامی این حالات از قبل در انگیولار تعریف شده و شما میتوانید از آن‌ها استفاده کنید. ولی اگر بخواهیم یک ویژگی دلخواه را تعریف کنیم، این کار توسط دایرکتیوها امکان پذیر است و به این نوع دایرکتیوها، Attribute Directive میگویند.
import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({
    selector: '[appHighlight]',
})
export class HighlightDirective implements OnInit {

    _dval = 'green';

    constructor(private _ref: ElementRef) {
    }

    ngOnInit(): void {
        this._ref.nativeElement.style.backgroundColor = this._dval;
    }
}
در کد بالا یک دایرکتیو ایجاد کرده‌ایم و در سازنده آن نوع ElementRef را دریافت میکنیم. این نکته به ما نشان میدهد که این دایرکتیو باید داخل یک تگ، استفاده شود، تا بتواند تگ مورد نظر را از این طریق برای ما بازگرداند. وقتی که تگ مورد  نظر را دریافت کردیم، از طریق رخ‌دادگردان OnInit، ویژگی رنگ پس زمینه را به مقدار پیش فرضی که رنگ سبز در آن تعریف شده است، تنظیم میکنیم و از این پس اگر این دایرکتیو بر روی هر تگی قرار بگیرد، رنگ پس زمینه آن سبز خواهد بود:
<span appHighlight>Attibute Directive</span>
لازم است که دایرکتیو تعریف شده در بالا، به فایل AppModules معرفی شود تا استفاده از آن در کامپوننت‌ها میسر گردد. خروجی نهایی به شکل زیر نمایش داده می‌شود:

حال ممکن است که بخواهید به این ویژگی مقداری را نسبت دهید و از طریق این مقدار، عملیات مورد نظر را انجام دهید. به عنوان نمونه در اینجا میخواهیم رنگ پس زمینه را در همان تگ معرفی کنیم:

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

import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({
    selector: '[appHighlight]',
    inputs: ["hc"]
})
export class HighlightDirective implements OnInit {

    hc: string;
    _dval = 'green';

    constructor(private _ref: ElementRef) {
    }

    ngOnInit(): void {
        this._ref.nativeElement.style.backgroundColor = this.hc || this._dval;
    }
}
در اینجا ما متغیری را به نام hc که مخفف highlightColor می‌باشد، تعریف میکنیم و سپس از طریق خصوصیت inputs در بالا آن را به یک ورودی برای کلاس تبدیل میکنیم تا از این طریق بتوانیم آن‌را مقداردهی نماییم.
معرفی کردن یک فیلد به عنوان ورودی در انگیولار، از دو طریق امکان پذیر است:
1. در اولین حالت شما متغیری را تعریف میکنید و آن متغیر را همانند کد بالا داخل ویژگی inputs متادیتای Directive معرفی میکنید.
2. در شیوه بعد که در این مقاله هم ذکر شده است میتوانید از طریق متادیتایی به نام Input@ اینکار را انجام دهید:
@Input() hc:string;

اینکه از کدام شیوه استفاده کنید تفاوتی ندارد و به خودتان بستگی دارد. ولی شیوه‌ی اول به دلیل اینکه همه یکجا تعریف می‌شوند، نظم بهتری داشته و در یک نگاه میتوان ورودی‌ها را تشخیص داد؛ ولی در شیوه‌ی دوم باید کل کلاس را برای یافتن آن‌ها مشاهده کرد.
مزیت روش دوم این است که در حین کدنویسی بسیار راحت است تا در همانجا ورودی را تعریف کنیم؛ به جای اینکه مرتب به ابتدای کلاس بازگردیم تا آن را تعریف کنیم.
this._ref.nativeElement.style.backgroundColor=this.hc || this._dval;
در رخ‌دادگردان هم به این شکل کد را تغییر دادیم تا اگر مقدار ورودی، دریافت شده‌است، آن را به عنوان مقدار اصلی در نظر بگیرد و در غیر اینصورت اگر نال بود، همان مقدار پیش فرض را انتساب دهد.
<h1 appHighlight [hc]="'red'">
  َApp Works!
</h1>
در خط بالا ابتدا دایرکتیو تعیین شده و سپس نام متغیر ورودی داده می‌شود. اگر قرار باشد ورودی‌های بیشتری داشته باشید مقداردهی آن به شکل زیر خواهد بود:
<h1 appHighlight [hc]="'red'"  [hc2]="....."  [hc3]="....."   ....>
  App Works! 
</h1>

گاهی اوقات ممکن است بخواهید عمل مورد نظر را بر اساس رویدادهای یک المان انجام دهید. پس کلاس را مجددا بازنویسی کرده و کدهای جدید را به آن اضافه میکنیم:
import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({
    selector: '[appHighlight]',
    inputs: ["hc"],
    host: {
        '(mouseenter)': 'onMouseEnter()',
        '(mouseleave)': 'onMouseLeave()',
    }
})
export class HighlightDirective implements OnInit {
    hc: string;
    _dval = 'green';
    constructor(private _ref: ElementRef) {}

    ngOnInit(): void {
        this._ref.nativeElement.style.backgroundColor = this.hc || this._dval;
    }

    onMouseEnter() {
        this._ref.nativeElement.style.backgroundColor = 'blue';
    }

    onMouseLeave() {
        this._ref.nativeElement.style.backgroundColor = 'pink';
    }
}
در بخش host دو رویداد را تعریف کرده و به متدهای کلاس اعمال میکنیم و در این حالت موقعیکه ماوس بر روی المان میرود، رنگ المان آبی شده و موقعیکه ماوس از روی المان کنار می‌رود، رنگ صورتی جایگزین آن می‌شود.
مطالب
Bundling and Minifying Inline Css and Js
افزایش Performance یک سایت از موارد بسیار مهمی است که هر برنامه نویسی باید به آن توجه ویژه‌ای داشته باشد و در این زمینه لینک Best Practices می‌تواند بسیار کاربردی باشد. 
حال در این پست قصد داریم Style‌ها و Js‌های نوشته شده در سطح هر View را با Bundling and Minifying در Asp.Net MVC 4 بهینه نماییم .
در ابتدا با استفاده از  Nuget پکیج BundleMinifyInlineJsCss  را به پروژه MVC خود مطابق شکل زیر اضافه می‌نماییم .  

در مرحله بعدی کلاسی را با نام BundleMinifyingInlineCssJSAttribute ایجاد کرده و با ارث بردن از کلاس ActionFilterAttribute متد OnActionExecuting را override می‌نماییم . اکنون کلاس ما به شکل زیر است :
using System.Web;
using System.Web.Mvc;
using BundlingAndMinifyingInlineCssJs.ResponseFilters;
namespace UILayer.Filters
{
    public class BundleMinifyingInlineCssJSAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Response.Filter = new BundleAndMinifyResponseFilter(filterContext.HttpContext.Response.Filter);
        }
    }
}
و برای استفاده می‌توانیم بالای کنترلر خود کد زیر را اضافه نماییم .
[BundleMinifyingInlineCssJS]
    public partial class HomeController : Controller
    {
}
در ادامه پروژه را اجرا می‌کنیم. Style‌ها و Js‌های نوشته شده در سطح هر View به صورت زیر در می‌آیند.

مطالب
کار با SignalR Core از طریق یک کلاینت جاوا اسکریپتی
کلاینت جاوا اسکریپتی SignalR Core، بازنویسی کامل شده‌است و دیگر وابستگی به jQuery ندارد. این کلاینت از طریق npm توزیع می‌شود:
 npm install @aspnet/signalr-client --save
فایل‌های آن نیز شامل فایل‌های جاوا اسکریپتی مرتبط و همچنین Typings مورد استفاده‌ی در TypeScript است که نمونه‌ای از نحوه‌ی استفاده از این Typings را در مطلب «کار با SignalR Core از طریق یک کلاینت Angular» مطالعه کردید.


بررسی محتوای پوشه‌ی node_modules\@aspnet\signalr-client

پس از نصب بسته‌ی «aspnet/signalr-client@»، در مسیر node_modules\@aspnet\signalr-client\dist دو پوشه‌ی src و browser را خواهید یافت. پوشه‌ی src حاوی منبع کامل این کلاینت و همچنین فایل‌های Typings مخصوص تایپ‌اسکریپت است.


و پوشه‌ی browser آن شامل دو گروه فایل است:


- در اینجا گروهی از فایل‌ها، حاوی عبارت ES5 هستند و تعدادی خیر. SignalR JavaScript بر اساس ES 6 یا EcmaScript 2015 تهیه شده‌است و از مفاهیمی مانند Promises  و  arrow functions استفاده می‌کند. باید دقت داشت که تعدادی از مرورگرها مانند IE از این قابلیت‌ها پیشتیبانی نمی‌کنند. در بین این فایل‌ها، آن‌هایی که حاوی عبارت ES5 نیستند، یعنی بر اساس ES 6 تهیه شده‌اند. سایر فایل‌ها توسط قابلیت Transpile مربوط به TypeScript به ES5 ترجمه شده‌اند. به علاوه حجم این فایل‌ها نیز بیشتر می‌باشد؛ چون حاوی تعاریف وابستگی‌هایی هستند که در ES 5 وجود خارجی ندارند. بنابراین بسته به نوع مرورگر مدنظر، یکی از این دو گروه را باید انتخاب کرد؛ ES 6 برای مرورگرهای جدید و ES 5 برای مرورگرهای قدیمی.
- به علاوه در اینجا تعدادی از فایل‌ها حاوی عبارت msgpackprotocol هستند. نگارش جدید SignalR از پروتکل‌های هاب سفارشی مانند پروتکل‌های باینری نیز پشتیبانی می‌کند. همچنین حاوی یک پیاده سازی توکار از پروتکل‌های باینری بر اساس MessagePack نیز هست. چون حجم کدهای پشتیبانی کننده‌ی از این پروتکل ویژه بالا است، آن‌را به یک فایل مجزا انتقال داده‌اند تا در صورت نیاز مورد استفاده قرار گیرد. بنابراین اگر از این پروتکل استفاده نمی‌کنید، نیازی هم به الحاق آن در صفحات خود نخواهید داشت. فایل third-party-notices.txt نیز مربوط است به یادآوری مجوز استفاده‌ی از MessagePack که MIT می‌باشد.
- در هر گروه نیز، دو فایل min و معمولی قابل مشاهده‌است. فایل‌های min برای توزیع نهایی مناسب هستند و فایل‌های غیرفشرده شده برای حالت دیباگ.


استفاده از کلاینت جاوا اسکریپتی SignalR Core

برای کار با کلاینت جاوا اسکریپتی SignalR Core از همان فایل‌های موجود در پوشه‌ی node_modules/@aspnet/signalr-client/dist/browser استفاده می‌کنیم. تفاوت این کلاینت با نگارش قبلی SignalR به صورت یک ذیل است:
1) ارجاع به فایل قدیمی signalR-2.2.1.min.js با فایل جدید signalR-client-1.0.0-alpha1.js جایگزین می‌شود. اگر می‌خواهید مرورگرهای قدیمی را پشتیبانی کنید، نگارش ES5 آن‌را لحاظ کنید.
2) پروکسی‌ها با new HubConnection جایگزین شده‌اند.
3) برای ثبت callbackهای سمت کلاینت، از متد جدید on استفاده می‌شود.
4) بجای متد done مربوط به jQuery، در اینجا از متد then مربوط به ES6 کمک گرفته شده‌است.
5) کار فراخوانی متدهای هاب توسط متد invoke انجام می‌شود.


یک مثال: بازنویسی قسمت سمت کلاینت مثال «کار با  SignalR Core از طریق یک کلاینت Angular» با jQuery

هرچند کلاینت جدید SignalR Core وابستگی به jQuery ندارد، اما جهت سهولت کار با DOM، کدهای سمت کلاینت مثال قبلی را با jQuery بازنویسی می‌کنیم. تمام کدهای سمت سرور این مثال با مطلب «کار با SignalR Core از طریق یک کلاینت Angular» یکی است؛ مانند ایجاد هاب، فعالسازی SiganlR در فایل آغازین برنامه و ثبت مسیرهاب. بنابراین در اینجا، این قسمت از کدهای سمت سرور را مجددا تکرار نمی‌کنیم و تمام نکات آن یکی هستند.

برای کار با کلاینت جاوا اسکریپتی SignalR Core، اینبار دستور ذیل را در ریشه‌ی پروژه‌ی وب اجرا می‌کنیم (یا هر پروژه‌ای که قرار است مدیریت فایل‌های سمت کلاینت و Viewهای برنامه را انجام دهد):
npm init
npm install @aspnet/signalr-client --save
bower install
دستور اول یک فایل package.json خالی را ایجاد می‌کند و دستور دوم بسته‌ی جاوا اسکریپتی SiganlR Core را نصب خواهد کرد. به علاوه این وابستگی را در فایل package.json نیز ثبت می‌کند. دستور سوم نیز وابستگی‌های قید شده‌ی در فایل bower.json را نصب می‌کند.

مرحله‌ی بعدی کار، تنظیمات فایل bundleconfig.json است؛ تا تمام اسکریپت‌های مورد نیاز جمع‌آوری و یکی شوند:
[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
      "wwwroot/css/site.css"
    ]
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/lib/jquery/dist/jquery.min.js",
      "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
      "node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha1-final.min.js",
      "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
      "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js",
      "wwwroot/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js",
      "wwwroot/js/site.js"
    ],
    "minify": {
      "enabled": false,
      "renameLocals": false
    },
    "sourceMap": false
  }
]
در اینجا نحوه‌ی ثبت فایل signalr-client-1.0.0-alpha1-final.min.js مبتنی بر ES 6 را مشاهده می‌کنید. اگر می‌خواهید نگارش ES 5 آن‌را ذکر کنید، از فایل signalr-clientES5-1.0.0-alpha1-final.min.js استفاده نمائید.
با توجه به خروجی‌های نهایی فایل bundleconfig.json، تنها نیاز است مداخل ذیل را به فایل layout برنامه اضافه کرد:
<link href="~/css/site.min.css" rel="stylesheet" asp-append-version="true" />
<script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>

مرحله‌ی بعد، تغییر نام متد send قسمت قبل به broadcastMessage است:
public class MessageHub : Hub
{
   public Task Send(string message)
   {
     return Clients.All.InvokeAsync("broadcastMessage", message);
   }
}
به این ترتیب می‌توان به تمایز بهتری بین نام callback سمت کلاینت و متد Send سمت سرور رسید. بهتر است این‌دو هم‌نام نباشند.
در ادامه یک کنترلر ساده را به نام JsClientController با View ذیل ایجاد می‌کنیم:
<form method="post"
      asp-action="Index"
      asp-controller="Home"
      data-ajax="true"
      role="form">
  <div class="form-group">
     <label label-for="message">Message: </label>
     <input id="message" name="message" class="form-control"/>
  </div>
  <button class="btn btn-primary" type="submit">Send To Home/Index</button>
  <button class="btn btn-success" id="sendmessageDirect" type="button">Send To /message hub directly</button>
</form>

<div id="discussion">
</div>
کار آن نمایش فرم ذیل است:


از اولین دکمه برای ارسال یک پیام به کنترلر Home که در آن توسط <IHubContext<MessageHub پیامی به تمام کلاینت‌ها ارسال می‌شود، استفاده شده‌است. دومین دکمه متد Send هاب را مستقیما فراخوانی می‌کند؛ با این کدهای سمت کلاینت:
@section Scripts
{
   <script type="text/javascript" asp-append-version="true">
   $(function() {
      var connection = new signalR.HubConnection('/message');
      connection.on('broadcastMessage', function (message) {
          // Add the message to the page.
          var encodedMsg = $('<div />').text(message).html();
          $('#discussion').append('<li>' + encodedMsg + '</li>');
      });

      connection.start().then(function () {
          console.log('connected.');
          $('#sendmessageDirect').click(function () {
              // Call the Send method on the hub.
              connection.invoke('send', $('#message').val());
          });
      });
   });
   </script>
}
- ابتدا یک شیء جدید signalR.HubConnection ایجاد می‌شود. این شیء به آدرس Hub تعریف شده‌ی در فایل آغازین برنامه اشاره می‌کند.
- سپس در متد on هست که مشخص می‌کنیم متد سمت کلاینتی که قرار است از سمت سرور فراخوانی شود، چه نامی دارد. نام آن‌را در این مثال broadcastMessage درنظر گرفته‌ایم. در اینجا پارامتر message از سمت سرور دریافت شده و سپس در صفحه‌ی جاری نمایش داده می‌شود.
بدیهی است متد Send می‌تواند تعداد پارامترهای بیشتری را بپذیرد و همچنین متد broadcastMessage نیز محدودیتی از لحاظ تعداد پارامتر ندارد. اگر پارامترهای بیشتری را تعریف کردید، در همینجا باید قید شوند.
- در ادامه کار شروع این اتصال آغاز می‌شود. در متد then هست که باید کار اتصال دکمه‌ی sendmessageDirect صورت گیرد. چون عملیات اتصال ممکن است زمانبر باشد و connection ارسالی هنوز آغاز نشده باشد. در اینجا نحوه‌ی فراخوانی مستقیم متد Send سمت سرور را با یک پارامتر ملاحظه می‌کنید. این متد نیز می‌تواند بر اساس امضای متد Send سمت سرور، تعداد پارامترهای بیشتری را قبول کند.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: SignalRCore2WebApp02.zip
برای اجرا آن باید این دستورات را به ترتیب وارد کنید:
dotnet restore
npm install
npm install -g bower
bower install
dotnet watch run
مطالب
React 16x - قسمت 22 - ارتباط با سرور - بخش 1 - برپایی تنظیمات
هر برنامه‌ی وب، دارای یک frontend و یک backend است. تا اینجا، تمام تمرکز این سری، بر روی پیاده سازی frontend بود و هیچکدام از برنامه‌هایی را که تکمیل کردیم، تبادل اطلاعاتی را با وب سرویس‌های backend نداشتند؛ اما به عنوان یک توسعه دهنده‌ی React، نیاز است با نحوه‌ی ارتباط با سرور آشنایی داشت که در طی چند قسمت به آن می‌پردازیم.


ایجاد برنامه‌ی backend ارائه دهنده‌ی REST API

در اینجا یک برنامه‌ی ساده‌ی ASP.NET Core Web API را جهت تدارک سرویس‌های backend، مورد استفاده قرار می‌دهیم. هرچند این مورد الزامی نبوده و اگر علاقمند بودید که مستقل از آن کار کنید، حتی می‌توانید از سرویس آنلاین JSONPlaceholder نیز برای این منظور استفاده کنید که یک Fake Online REST API است. کار آن ارائه‌ی یک سری endpoint است که به صورت عمومی از طریق وب قابل دسترسی هستند. می‌توان به این endpintها درخواست‌های HTTP خود را مانند GET/POST/DELETE/UPDATE ارسال کرد و از آن اطلاعاتی را دریافت نمود و یا تغییر داد. به هر کدام از این endpointها یک API گفته می‌شود که جهت آزمایش برنامه‌ها بسیار مناسب هستند. برای نمونه در قسمت resources آن اگر به آدرس https://jsonplaceholder.typicode.com/posts مراجعه کنید، می‌توان لیستی از مطالب را با فرمت JSON مشاهده کرد. کار آن ارائه‌ی آرایه‌ای از اشیاء جاوا اسکریپتی قابل استفاده‌ی در برنامه‌های frontend است. بنابراین زمانیکه یک HTTP GET را به این endpoint ارسال می‌کنیم، آرایه‌ای از اشیاء مطالب را دریافت خواهیم کرد. همین endpoint، امکان تغییر این اطلاعات را توسط برای مثال HTTP Delete نیز میسر کرده‌است.

اگر علاقمندید بودید می‌توانید از JSONPlaceholder استفاده کنید و یا در ادامه دقیقا ساختار همین endpoint ارائه‌ی مطالب آن‌را با ASP.NET Core Web API نیز پیاده سازی می‌کنیم (برای مطالعه‌ی قسمت «ارتباط با سرور» اختیاری است و از هر REST API مشابهی که توسط nodejs یا PHP و غیره تولید شده باشد نیز می‌توان استفاده کرد):

مدل مطالب
namespace sample_22_backend.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }

        public int UserId { set; get; }
    }
}
ساختار این مدل، با ساختار مدل مطالب JSONPlaceholder یکی درنظر گرفته شده‌است، تا مطلب قابلیت پیگیری بیشتری را پیدا کند.


منبع داده‌ی فرضی مطالب

برای ارائه‌ی ساده‌تر برنامه، یک منبع داده‌ی درون حافظه‌ای را به همراه یک سرویس، در اختیار کنترلر مطالب، قرار می‌دهیم:
using System;
using System.Collections.Generic;
using System.Linq;
using sample_22_backend.Models;

namespace sample_22_backend.Services
{
    public interface IPostsDataSource
    {
        List<Post> GetAllPosts();
        bool DeletePost(int id);
        Post AddPost(Post post);
        bool UpdatePost(int id, Post post);
        Post GetPost(int id);
    }

    /// <summary>
    /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است
    /// </summary>
    public class PostsDataSource : IPostsDataSource
    {
        private readonly List<Post> _allPosts;

        public PostsDataSource()
        {
            _allPosts = createDataSource();
        }

        public List<Post> GetAllPosts()
        {
            return _allPosts;
        }

        public Post GetPost(int id)
        {
            return _allPosts.Find(x => x.Id == id);
        }

        public bool DeletePost(int id)
        {
            var item = _allPosts.Find(x => x.Id == id);
            if (item == null)
            {
                return false;
            }

            _allPosts.Remove(item);
            return true;
        }

        public Post AddPost(Post post)
        {
            var id = 1;
            var lastItem = _allPosts.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }

            post.Id = id;
            _allPosts.Add(post);
            return post;
        }

        public bool UpdatePost(int id, Post post)
        {
            var item = _allPosts
                .Select((pst, index) => new { Item = pst, Index = index })
                .FirstOrDefault(x => x.Item.Id == id);
            if (item == null || id != post.Id)
            {
                return false;
            }

            _allPosts[item.Index] = post;
            return true;
        }

        private static List<Post> createDataSource()
        {
            var list = new List<Post>();
            var rnd = new Random();
            for (var i = 1; i < 10; i++)
            {
                list.Add(new Post { Id = i, UserId = rnd.Next(1, 1000), Title = $"Title {i} ...", Body = $"Body {i} ..." });
            }
            return list;
        }
    }
}
در این سرویس، نیازمندی‌های کنترلر مطالب مانند ارائه لیست تمام مطالب، نمایش اطلاعات یک مطلب، به روز رسانی، ایجاد و حذف یک مطلب، تدارک دیده شده‌اند. سپس از این سرویس در کنترلر زیر استفاده می‌کنیم:


کنترلر Web API برنامه‌ی backend

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using sample_22_backend.Models;
using sample_22_backend.Services;

namespace sample_22_backend.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class PostsController : ControllerBase
    {
        private readonly IPostsDataSource _postsDataSource;

        public PostsController(IPostsDataSource postsDataSource)
        {
            _postsDataSource = postsDataSource;
        }

        [HttpGet]
        public ActionResult<List<Post>> GetPosts()
        {
            return _postsDataSource.GetAllPosts();
        }

        [HttpGet("{id}")]
        public ActionResult<Post> GetPost(int id)
        {
            var post = _postsDataSource.GetPost(id);
            if (post == null)
            {
                return NotFound();
            }
            return Ok(post);
        }

        [HttpDelete("{id}")]
        public ActionResult DeletePost(int id)
        {
            var deleted = _postsDataSource.DeletePost(id);
            if (deleted)
            {
                return Ok();
            }
            return NotFound();
        }

        [HttpPost]
        public ActionResult<Post> CreatePost([FromBody]Post post)
        {
            post = _postsDataSource.AddPost(post);
            return CreatedAtRoute(nameof(GetPost), new { post.Id }, post);
        }

        [HttpPut("{id}")]
        public ActionResult<Post> UpdatePost(int id, [FromBody]Post post)
        {
            var updated = _postsDataSource.UpdatePost(id, post);
            if (updated)
            {
                return Ok(post);
            }
            return NotFound();
        }
    }
}
این کنترلر که در مسیر شروع شده‌ی با https://localhost:5001/api قرار می‌گیرد، جهت پشتیبانی از افعال مختلف HTTP مانند Get/Post/Delete/Update طراحی شده‌است که در ادامه، در برنامه‌ی React خود از آن‌ها استفاده خواهیم کرد. پس از ایجاد این پروژه‌ی web api، یک نمونه خروجی آن در مسیر https://localhost:5001/api/posts، به صورت زیر خواهد بود:


البته نمایش فرمت شده‌ی JSON در مرورگر کروم، نیاز به نصب این افزونه را دارد.


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

در اینجا برای بررسی کار با سرور، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app sample-22-frontend
> cd sample-22-frontend
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

سپس فایل app.js را به شکل زیر تکمیل می‌کنیم:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = {
    posts: []
  };

  handleAdd = () => {
    console.log("Add");
  };

  handleUpdate = post => {
    console.log("Update", post);
  };

  handleDelete = post => {
    console.log("Delete", post);
  };

  render() {
    return (
      <React.Fragment>
        <button className="btn btn-primary mt-1 mb-1" onClick={this.handleAdd}>
          Add
        </button>
        <table className="table">
          <thead>
            <tr>
              <th>Title</th>
              <th>Update</th>
              <th>Delete</th>
            </tr>
          </thead>
          <tbody>
            {this.state.posts.map(post => (
              <tr key={post.id}>
                <td>{post.title}</td>
                <td>
                  <button
                    className="btn btn-info btn-sm"
                    onClick={() => this.handleUpdate(post)}
                  >
                    Update
                  </button>
                </td>
                <td>
                  <button
                    className="btn btn-danger btn-sm"
                    onClick={() => this.handleDelete(post)}
                  >
                    Delete
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}

export default App;
که حاصل آن، یک دکمه، برای افزودن مطلبی جدید، به همراه جدولی است از مطالب که قصد داریم در ادامه، اطلاعات آن‌را از سرور دریافت کرده و حذف و یا به روز رسانی کنیم:



نگاهی به انواع و اقسام HTTP Client‌های مهیا

در ادامه نیاز خواهیم داشت تا از طریق برنامه‌های React خود، درخواست‌های HTTP را به سمت سرور (یا همان برنامه‌ی backend) ارسال کنیم، تا بتوان اطلاعاتی را از آن دریافت کرد و یا تغییری را در اطلاعات موجود، ایجاد نمود. همانطور که پیشتر نیز در این سری عنوان شد، React برای این مورد نیز راه‌حل توکاری را به همراه ندارد و تنها کار آن، رندر کردن View و مدیریت DOM است. البته شاید این مورد یکی از مزایای کار با React نیز باشد! چون در این حالت می‌توانید از کتابخانه‌هایی که خودتان ترجیح می‌دهید، نسبت به کتابخانه‌هایی که به شما ارائه/تحمیل (!) می‌شوند (مانند برنامه‌های Angular) آزادی انتخاب کاملی را داشته باشید. برای مثال هرچند Angular به همراه یک HTTP Module توکار است، اما تاکنون چندین بار بازنویسی از ابتدا شده‌است! ابتدا با یک کتابخانه‌ی HTTP مقدماتی شروع کردند. بعدی آن‌را منسوخ شده اعلام و با یک ماژول جدید جایگزین کردند. بعد در نگارشی دیگر، چون این کتابخانه وابسته‌است به RxJS و خود RxJS نیز بازنویسی کامل شد، روش کار کردن با این HTTP Module نیز مجددا تغییر پیدا کرد! بنابراین اگر با Angular کار می‌کنید، باید کارها را آنگونه که Angular می‌پسندد، انجام دهید؛ اما در اینجا خیر و آزادی انتخاب کاملی برقرار است.
بنابراین اکنون این سؤال مطرح می‌شود که در React، برای برقراری ارتباط با سرور، چه باید کرد؟ در اینجا آزاد هستید برای مثال از Fetch API جدید مرورگرها و یا روش Ajax ای مبتنی بر XML قدیمی‌تر آن‌ها، استفاده کنید (اطلاعات بیشتر) و یا حتی اگر علاقمند باشید می‌توانید از محصور کننده‌های آن مانند jQuery Ajax استفاده کنید. بنابراین اگر با jQuery Ajax راحت هستید، به سادگی می‌توانید از آن در برنامه‌های React نیز استفاده کنید. اما ... ما در اینجا از یک کتابخانه‌ی بسیار محبوب و قدرتمند HTTP Client، به نام Axios (اکسیوس/ یک واژه‌ی یونانی به معنای «سودمند») استفاده خواهیم کرد که فقط تعداد بار دانلود هفتگی آن، 6 میلیون بار است!


نصب Axios در برنامه‌ی React این قسمت

برای نصب کتابخانه‌ی Axios، در ریشه‌ی پروژه‌ی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
پس از برپایی این مقدمات، ادامه‌ی مطلب «ارتباط با سرور» را در قسمت بعدی پیگیری می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-frontend-part-01.zip و sample-22-backend-part-01.zip