اشتراکها
gRPC + ASP.NET Core as a Migration Path for WCFs in .NET Core
Feature | WCF | ASP.Net Core + gRPC |
Platforms | Windows | Windows, Linux, MacOS |
Protocols | LRPC/Named Pipes/HTTP/TCP/MSMQ | Binary (GRPC) + HTTP2 (TCP/Named Pipes/LRPC) .AddProtocol(“ncacn_ip_tcp”, “8080”) .AddProtocol(“ncacn_np”, @”\pipe\MyService”) .AddProtocol(“ncalrpc”, “MyService”) By removing the ASP.NET Core stack and just using .NET Core |
Injected Aspects | Behaviors | ASP.NET Core DI Middleware/ gRPC interceptors |
Distributed Transactions | *Yes – [TransactionFlow], transactionscopes, and supported bindings | *No |
Transport Security | SSL/TLS | SSL/TLS |
Message Security | Certificates/credentials | Certificates/credentials https://docs.microsoft.com/en-us/aspnet/core/grpc/authn-and-authz?view=aspnetcore-3.0 |
Windows Authentication | Kerberos/NTLM | AAD Sync/ASFS + ASP.NET Core middleware |
Proxies/Contracts | Service Contracts/Data Contracts | Protocol Buffers |
Proxy-less Communication | WCF Channel Factory | † Protobuf-Net.GRPC |
وقتی تغییری را در اشیاء خود به وجود میآورید، Angular بلافاصله متوجه آنها شده و viewها را به روز رسانی میکند. هدف از این مکانیزم، اطمینان حاصل کردن از همگام بودن اشیاء مدلها و viewها هستند. آگاهی از نحوهی انجام این عملیات، کمک شایانی را به بالابردن کارآیی یک برنامهی با رابط کاربری پیچیدهای میکند. یک شیء مدل در Angular عموما به سه طریق تغییر میکند:
- بروز رخدادهای DOM مانند کلیک
- صدور درخواستهای Ajax ایی
- استفاده از تایمرها (setTimer, setInterval)
ردیابهای تغییرات در Angular
تمام برنامههای Angular در حقیقت سلسله مراتبی از کامپوننتها هستند. در زمان اجرای برنامه، Angular به ازای هر کامپوننت، یک تشخیص دهندهی تغییرات را ایجاد میکند که در نهایت سلسله مراتب ردیابها را همانند سلسله مراتب کامپوننتها ایجاد خواهد کرد. هر زمانیکه ردیابی فعال میشود، Angular این درخت را پیموده و مواردی را که تغییراتی را گزارش دادهاند، بررسی میکند. این پیمایش به ازای هر تغییر رخ دادهی در مدلهای برنامه صورت میگیرد و همواره از بالای درخت شروع شده و به صورت ترتیبی تا پایین آن ادامه پیدا میکند:
این پیمایش ترتیبی از بالا به پایین، از این جهت صورت میگیرد که اطلاعات کامپوننتها از والدین آنها تامین میشوند. تشخیص دهندههای تغییرات، روشی را جهت ردیابی وضعیت پیشین و فعلی یک کامپوننت ارائه میدهد تا Angular بتواند تغییرات رخداده را منعکس کند. اگر Angular گزارش تغییری را از یک تشخیص دهندهی تغییر دریافت کند، کامپوننت مرتبط را مجددا ترسیم کرده و DOM را به روز رسانی میکند.
استراتژیهای تشخیص تغییرات در Angular
برای درک نحوهی عملکرد سیستم تشخیص تغییرات نیاز است با مفهوم value types و reference types در JavaScript آشنا شویم. در JavaScript نوعهای زیر value type هستند:
و نوعهای زیر Reference type محسوب میشوند:
مهمترین تفاوت بین این دو نوع، این است که برای دریافت مقدار یک value type فقط کافی است از stack memory کوئری بگیریم. اما برای دریافت مقادیر reference types باید ابتدا در جهت یافتن شماره ارجاع آن، از stack memory کوئری گرفته و سپس بر اساس این شماره ارجاع، اصل مقدار آنرا در heap memory پیدا کنیم.
این دو تفاوت را میتوان در شکل زیر بهتر مشاهده کرد:
استراتژی Default یا پیشفرض تشخیص تغییرات در Angular
همانطور که پیشتر نیز عنوان شد، Angular تغییرات یک شیء مدل را در جهت تشخیص تغییرات و انعکاس آنها به View برنامه، ردیابی میکند. در این حالت هر تغییری بین حالت فعلی و پیشین یک شیء مدل برای این منظور بررسی میگردد. در اینجا Angular این سؤال را مطرح میکند: آیا مقداری در این مدل تغییر یافتهاست؟
اما برای یک reference type میتوان سؤالات بهتری را مطرح کرد که بهینهتر و سریعتر باشند. اینجاست که استراتژی OnPush تشخیص تغییرات مطرح میشود.
استراتژی OnPush تشخیص تغییرات در Angular
ایده اصلی استراتژی OnPush تشخیص تغییرات در Angular در immutable فرض کردن reference types نهفتهاست. در این حالت هر تغییری در شیء مدل، سبب ایجاد یک ارجاع جدید به آن در stack memory میشود. به این ترتیب میتوان تشخیص تغییرات بسیار سریعتری را شاهد بود. چون دیگر در اینجا نیازی نیست تمام مقادیر یک شیء را مدام تحت نظر قرار داد. همینقدر که ارجاع آن در stack memory تغییر کند، یعنی مقادیر این شیء در heap memory تغییر یافتهاند.
در این حالت Angular دو سؤال را مطرح میکند: آیا ارجاع به یک reference type در stack memory تغییر یافتهاست؟ اگر بله، آیا مقادیر آن در heap memory تغییر کردهاند؟ برای مثال جهت بررسی تغییرات یک آرایهی با 30 عضو، دیگر در ابتدای کار نیازی نیست تا هر 30 عضو آن بررسی شوند (برخلاف حالت پیشفرض بررسی تغییرات). در حالت استراتژی OnPush، ابتدا مقدار ارجاع این آرایه در stack memory بررسی میشود. اگر تغییری در آن صورت گرفته بود، به معنای تغییری در اعضای آرایهاست.
استراتژی OnPush در یک کامپوننت به نحو ذیل فعال و انتخاب میشود و مقدار پیشفرض آن ChangeDetectionStrategy.Default است:
استراتژی OnPush تغییرات یک کامپوننت را در حالتهای ذیل نیز ردیابی میکند:
- اگر مقدار یک خاصیت از نوع Input@ تغییر کند.
- اگر یک event handler رخدادی را صادر کند.
- اگر سیستم ردیابی به صورت دستی فراخوانی شود.
- اگر سیستم ردیاب تغییرات child component آن، اجرا شود.
نوعهای ارجاعی Immutable
همانطور که عنوان شد، شرط کار کردن استراتژی OnPush، داشتن نوعهای ارجاعی immutable است. اما نوع ارجاعی immutable چیست؟
Immutable بودن به زبان ساده به این معنا است که ما هیچگاه جهت تغییر مقدار خاصیتی در یک شیء، آن خاصیت را مستقیما مقدار دهی نمیکنیم؛ بلکه کل شیء را مجددا مقدار دهی میکنیم.
برای نمونه در مثال زیر، خاصیت foo شیء before مستقیما مقدار دهی شدهاست:
اما اگر بخواهیم با آن به صورت Immutable «رفتار» کنیم، کل این شیء را جهت اعمال تغییرات، مقدار دهی مجدد خواهیم کرد:
البته باید دقت داشت در هر دو مثال، شیءهای ایجاد شده در اصل mutable هستند؛ اما در مثال دوم، با این شیء به صورت immutable «رفتار» شدهاست و صرفا «تظاهر» به رفتار immutable با یک شیء ارجاعی صورت گرفتهاست.
معرفی کتابخانهی Immutable.js
جهت ایجاد اشیاء واقعی immutable کتابخانهی Immutable.js توسط Facebook ایجاد شدهاست و برای کار با استراتژی تشخیص تغییرات OnPush در Angular بسیار مناسب است.
برای نصب آن دستور ذیل را صادر نمائید:
یک نمونه مثال از کاربرد ساختارهای دادهی List و Map آن برای کار با آرایهها و اشیاء:
تغییر ارجاع به یک شیء با استفاده از spread properties
در این مثال تنها خاصیت age شیء user به روز رسانی میشود. بنابراین ارجاع به این شیء تغییر نخواهد کرد و اگر از روش changeDetection: ChangeDetectionStrategy.OnPush استفاده کنیم، رابط کاربری برنامه به روز رسانی نخواهد شد و این تغییر صرفا با بررسی عمیق تک تک خواص این شیء با وضعیت قبلی آن قابل تشخیص است (یا همان حالت پیش فرض بررسی تغییرات در Angular و نه حالت OnPush).
اگر علاقمند به استفادهی از یک کتابخانهی اضافی مانند Immutable.js در کدهای خود نباشید، روش دیگری نیز برای تغییر ارجاع به یک شیء وجود دارد:
در اینجا با استفاده از spread properties یک شیء کاملا جدید ایجاد شدهاست و ارجاع به آن با ارجاع به شیء user یکی نیست.
نمونهی دیگر آن در حین کار با متد push یک آرایهاست:
متد push، بدون تغییر ارجاعی به آرایهی اصلی، عضوی را به آن آرایه اضافه میکند. بنابراین اعضای اضافه شدهی به آن نیز توسط استراتژی OnPush قابل تشخیص نیستند. اما اگر بجای متد push از spread operator استفاده کنیم:
اینبار this.food به یک شیء کاملا جدید اشاره میکند که ارجاع به آن، با ارجاع به شیء this.food قبلی یکی نیست. بنابراین استراتژی OnPush قابلیت تشخیص تغییرات آنرا دارد.
آگاه سازی دستی موتور تشخیص تغییرات Angular در حالت استفادهی از استراتژی OnPush
تا اینجا دریافتیم که استراتژی OnPush تنها به تغییرات ارجاعات به اشیاء پاسخ میدهد و به نحوی باید این ارجاع را با هر به روز رسانی تغییر داد. اما روش دیگری نیز برای وادار کردن این سیستم به تغییر وجود دارد:
در این کامپوننت از استراتژی OnPush استفاده شدهاست. در اینجا میتوان همانند قبل با اشیاء و آرایههای موجود کار کرد (بدون اینکه ارجاعات به آنها را تغییر دهیم و یا آنها را immutable کنیم) و در پایان کار، متد detectChanges سرویس ChangeDetectorRef را به صورت دستی فراخوانی کرد تا Angular کار رندر مجدد view این کامپوننت را بر اساس تغییرات آن انجام دهد (کل کامپوننت به عنوان یک کامپوننت تغییر کرده به سیستم ردیابی تغییرات معرفی میشود).
کار با Observables در حالت استفادهی از استراتژی OnPush
مطلب «صدور رخدادها از سرویسها به کامپوننتها در برنامههای Angular» را در نظر بگیرید. Observables نیز ما را از تغییرات رخداده آگاه میکنند؛ اما برخلاف immutable objects با هر تغییری که رخ میدهد، ارجاع به آنها تغییری نمیکند. آنها تنها رخدادی را به مشترکین، جهت اطلاع رسانی از تغییرات صادر میکنند.
بنابراین اگر از Observables و استراتژی OnPush استفاده کنیم، چون ارجاع به آنها تغییری نمیکند، رخدادهای صادر شدهی توسط آنها ردیابی نخواهند شد. برای رفع این مشکل، امکان علامتگذاری رخدادهای Observables به تغییر کرده پیشبینی شدهاست.
در اینجا کامپوننتی را داریم که قابلیت صدور رخدادها را از طریق یک BehaviorSubject دارد:
و کامپوننت دیگری توسط خاصیت ورودی data از نوع Observable در متد ngOnInit، مشترک آن خواهد شد:
در این حالت چون از ChangeDetectionStrategy.OnPush استفاده میشود و ارجاع به this.data این observable با هر بار صدور رخدادی توسط آن، تغییر نمیکند، سیستم ردیابی تغییرات آنرا به عنوان تغییر کرده درنظر نمیگیرد. برای رفع این مشکل تنها کافی است رخدادگردان آنرا با متد markForCheck علامتگذاری کنیم:
markForCheck به Angular اعلام میکند که این مسیر ویژه را در بررسی بعدی سیستم ردیابی تغییرات لحاظ کن.
- بروز رخدادهای DOM مانند کلیک
- صدور درخواستهای Ajax ایی
- استفاده از تایمرها (setTimer, setInterval)
ردیابهای تغییرات در Angular
تمام برنامههای Angular در حقیقت سلسله مراتبی از کامپوننتها هستند. در زمان اجرای برنامه، Angular به ازای هر کامپوننت، یک تشخیص دهندهی تغییرات را ایجاد میکند که در نهایت سلسله مراتب ردیابها را همانند سلسله مراتب کامپوننتها ایجاد خواهد کرد. هر زمانیکه ردیابی فعال میشود، Angular این درخت را پیموده و مواردی را که تغییراتی را گزارش دادهاند، بررسی میکند. این پیمایش به ازای هر تغییر رخ دادهی در مدلهای برنامه صورت میگیرد و همواره از بالای درخت شروع شده و به صورت ترتیبی تا پایین آن ادامه پیدا میکند:
این پیمایش ترتیبی از بالا به پایین، از این جهت صورت میگیرد که اطلاعات کامپوننتها از والدین آنها تامین میشوند. تشخیص دهندههای تغییرات، روشی را جهت ردیابی وضعیت پیشین و فعلی یک کامپوننت ارائه میدهد تا Angular بتواند تغییرات رخداده را منعکس کند. اگر Angular گزارش تغییری را از یک تشخیص دهندهی تغییر دریافت کند، کامپوننت مرتبط را مجددا ترسیم کرده و DOM را به روز رسانی میکند.
استراتژیهای تشخیص تغییرات در Angular
برای درک نحوهی عملکرد سیستم تشخیص تغییرات نیاز است با مفهوم value types و reference types در JavaScript آشنا شویم. در JavaScript نوعهای زیر value type هستند:
• Boolean • Null • Undefined • Number • String
• Arrays • Objects • Functions
این دو تفاوت را میتوان در شکل زیر بهتر مشاهده کرد:
استراتژی Default یا پیشفرض تشخیص تغییرات در Angular
همانطور که پیشتر نیز عنوان شد، Angular تغییرات یک شیء مدل را در جهت تشخیص تغییرات و انعکاس آنها به View برنامه، ردیابی میکند. در این حالت هر تغییری بین حالت فعلی و پیشین یک شیء مدل برای این منظور بررسی میگردد. در اینجا Angular این سؤال را مطرح میکند: آیا مقداری در این مدل تغییر یافتهاست؟
اما برای یک reference type میتوان سؤالات بهتری را مطرح کرد که بهینهتر و سریعتر باشند. اینجاست که استراتژی OnPush تشخیص تغییرات مطرح میشود.
استراتژی OnPush تشخیص تغییرات در Angular
ایده اصلی استراتژی OnPush تشخیص تغییرات در Angular در immutable فرض کردن reference types نهفتهاست. در این حالت هر تغییری در شیء مدل، سبب ایجاد یک ارجاع جدید به آن در stack memory میشود. به این ترتیب میتوان تشخیص تغییرات بسیار سریعتری را شاهد بود. چون دیگر در اینجا نیازی نیست تمام مقادیر یک شیء را مدام تحت نظر قرار داد. همینقدر که ارجاع آن در stack memory تغییر کند، یعنی مقادیر این شیء در heap memory تغییر یافتهاند.
در این حالت Angular دو سؤال را مطرح میکند: آیا ارجاع به یک reference type در stack memory تغییر یافتهاست؟ اگر بله، آیا مقادیر آن در heap memory تغییر کردهاند؟ برای مثال جهت بررسی تغییرات یک آرایهی با 30 عضو، دیگر در ابتدای کار نیازی نیست تا هر 30 عضو آن بررسی شوند (برخلاف حالت پیشفرض بررسی تغییرات). در حالت استراتژی OnPush، ابتدا مقدار ارجاع این آرایه در stack memory بررسی میشود. اگر تغییری در آن صورت گرفته بود، به معنای تغییری در اعضای آرایهاست.
استراتژی OnPush در یک کامپوننت به نحو ذیل فعال و انتخاب میشود و مقدار پیشفرض آن ChangeDetectionStrategy.Default است:
import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ // ... changeDetection: ChangeDetectionStrategy.OnPush }) export class OnPushComponent { // ... }
- اگر مقدار یک خاصیت از نوع Input@ تغییر کند.
- اگر یک event handler رخدادی را صادر کند.
- اگر سیستم ردیابی به صورت دستی فراخوانی شود.
- اگر سیستم ردیاب تغییرات child component آن، اجرا شود.
نوعهای ارجاعی Immutable
همانطور که عنوان شد، شرط کار کردن استراتژی OnPush، داشتن نوعهای ارجاعی immutable است. اما نوع ارجاعی immutable چیست؟
Immutable بودن به زبان ساده به این معنا است که ما هیچگاه جهت تغییر مقدار خاصیتی در یک شیء، آن خاصیت را مستقیما مقدار دهی نمیکنیم؛ بلکه کل شیء را مجددا مقدار دهی میکنیم.
برای نمونه در مثال زیر، خاصیت foo شیء before مستقیما مقدار دهی شدهاست:
static mutable() { var before = {foo: "bar"}; var current = before; current.foo = "hello"; console.log(before === current); // => true }
static immutable() { var before = {foo: "bar"}; var current = before; current = {foo: "hello"}; console.log(before === current); // => false }
معرفی کتابخانهی Immutable.js
جهت ایجاد اشیاء واقعی immutable کتابخانهی Immutable.js توسط Facebook ایجاد شدهاست و برای کار با استراتژی تشخیص تغییرات OnPush در Angular بسیار مناسب است.
برای نصب آن دستور ذیل را صادر نمائید:
npm install immutable --save
import {Map, List} from 'immutable'; var foobar = {foo: "bar"}; var immutableFoobar = Map(foobar); console.log(immutableFooter.get("foo")); // => bar var helloWorld = ["Hello", "World!"]; var immutableHelloWorld = List(helloWorld); console.log(immutableHelloWorld.first()); // => Hello console.log(immutableHelloWorld.last()); // => World! helloWorld.push("Hello Mars!"); console.log(immutableHelloWorld.last()); // => Hello Mars!
تغییر ارجاع به یک شیء با استفاده از spread properties
const user = { name: 'Max', age: 30 } user.age = 31
اگر علاقمند به استفادهی از یک کتابخانهی اضافی مانند Immutable.js در کدهای خود نباشید، روش دیگری نیز برای تغییر ارجاع به یک شیء وجود دارد:
const user = { name: 'Max', age: 30 } const modifiedUser = { ...user, age: 31 }
نمونهی دیگر آن در حین کار با متد push یک آرایهاست:
export class AppComponent { foods = ['Bacon', 'Lettuce', 'Tomatoes']; addFood(food) { this.foods.push(food); } }
addFood(food) { this.foods = [...this.foods, food]; }
آگاه سازی دستی موتور تشخیص تغییرات Angular در حالت استفادهی از استراتژی OnPush
تا اینجا دریافتیم که استراتژی OnPush تنها به تغییرات ارجاعات به اشیاء پاسخ میدهد و به نحوی باید این ارجاع را با هر به روز رسانی تغییر داد. اما روش دیگری نیز برای وادار کردن این سیستم به تغییر وجود دارد:
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-child', templateUrl: './child.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class ChildComponent { @Input() data: string[]; constructor(private cd: ChangeDetectorRef) {} refresh() { this.cd.detectChanges(); } }
کار با Observables در حالت استفادهی از استراتژی OnPush
مطلب «صدور رخدادها از سرویسها به کامپوننتها در برنامههای Angular» را در نظر بگیرید. Observables نیز ما را از تغییرات رخداده آگاه میکنند؛ اما برخلاف immutable objects با هر تغییری که رخ میدهد، ارجاع به آنها تغییری نمیکند. آنها تنها رخدادی را به مشترکین، جهت اطلاع رسانی از تغییرات صادر میکنند.
بنابراین اگر از Observables و استراتژی OnPush استفاده کنیم، چون ارجاع به آنها تغییری نمیکند، رخدادهای صادر شدهی توسط آنها ردیابی نخواهند شد. برای رفع این مشکل، امکان علامتگذاری رخدادهای Observables به تغییر کرده پیشبینی شدهاست.
در اینجا کامپوننتی را داریم که قابلیت صدور رخدادها را از طریق یک BehaviorSubject دارد:
import { Component } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Component({ ... }) export class AppComponent { foods = new BehaviorSubject(['Bacon', 'Letuce', 'Tomatoes']); addFood(food) { this.foods.next(food); } }
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; @Component({ selector: 'app-child', templateUrl: './child.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class ChildComponent implements OnInit { @Input() data: Observable<any>; foods: string[] = []; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { this.data.subscribe(food => { this.foods = [...this.foods, ...food]; }); }
ngOnInit() { this.data.subscribe(food => { this.foods = [...this.foods, ...food]; this.cd.markForCheck(); // marks path }); }
نظرات مطالب
EF Code First #1
- رشته اتصالی به SQL Server حالتهای مختلفی میتواند داشته باشد. اطلاعات بیشتر
Data Source آن معمولا نام کامپیوتر جاری است یا IP Server. چون در تصویر شما instance name خالی است، از همان وهلهی پیش فرض استفاده میشود. اگر مقدار داشت میشد computer_name/instance_name
Initial Catalog نام بانک اطلاعاتی مدنظر است که قرار است به آن متصل شوید (یا در اینجا به صورت خودکار ساخته شود).
Integrated Security = true به معنای استفاده از اعتبارسنجی ویندوزی است برای اتصال به SQL Server. یعنی کاربر جاری لاگین کرده به سیستم باید دسترسی لازم را برای کار با SQL Server داشته باشد.
- برای فراگیری یک فناوری جدید از برنامههای کنسول استفاده کنید و نه ASP.NET. این مباحث عمومی است بین فناوریهای مختلف استفاده کننده از آن. در یک برنامهی کنسول آغاز کار از متد Main است؛ در یک برنامهی وب از متد Application_Start فایل global.asax.cs خواهد بود.
Data Source آن معمولا نام کامپیوتر جاری است یا IP Server. چون در تصویر شما instance name خالی است، از همان وهلهی پیش فرض استفاده میشود. اگر مقدار داشت میشد computer_name/instance_name
Initial Catalog نام بانک اطلاعاتی مدنظر است که قرار است به آن متصل شوید (یا در اینجا به صورت خودکار ساخته شود).
Integrated Security = true به معنای استفاده از اعتبارسنجی ویندوزی است برای اتصال به SQL Server. یعنی کاربر جاری لاگین کرده به سیستم باید دسترسی لازم را برای کار با SQL Server داشته باشد.
- برای فراگیری یک فناوری جدید از برنامههای کنسول استفاده کنید و نه ASP.NET. این مباحث عمومی است بین فناوریهای مختلف استفاده کننده از آن. در یک برنامهی کنسول آغاز کار از متد Main است؛ در یک برنامهی وب از متد Application_Start فایل global.asax.cs خواهد بود.
- تنظیمات وابستگیهای jquery, jquery-validation, jquery-validation-unobtrusive که در این مطلب از ابتدا بحث شدند (و یا به روز شدهی آن در «یک نکتهی تکمیلی: جایگزینی bower با npm») و استفادهی از Tag helpers مرتبط که در انتهای بحث عنوان شدند، دقیقا همینکار را انجام میدهند.
- فقط دقت داشته باشید این مطالب جهت «ارتقاء به ASP.NET Core» تهیه شدهاند. این «ارتقاء» یعنی پیشتر این مطالب را مطالعه کردهاید و با آنها آشنایی دارید:
اشتراکها
مقایسهی Blazor و Angular
از لحاظ تاریخی، Blazor به همراه دو حالت اصلی است:
- Blazor Server، که در آن یک اتصال SignalR، بین مرورگر کاربر و سرور، برقرار شده و سرور حالات مختلف این جلسهی کاری را مدیریت میکند. آغاز این حالت، بسیار سریع است؛ اما وجود اتصال دائم SignalR در آن ضروری است. نیاز به وجود این اتصال دائم، با تعداد بالای کاربر میتواند کارآیی سرور را تحت تاثیر قرار دهد.
- Blazor WASM: در این حالت کل برنامهی Blazor، درون مرورگر کاربر اجرا میشود و برای اینکار الزاما نیازی به سرور ندارد؛ اما آغاز اولیهی آن به علت نیاز به بارگذاری کل برنامه درون مرورگر کاربر، اندکی کند است. اتصال این روش با سرور، از طریق روشهای متداول کار با Web API صورت میگیرد و نیازی به اتصال دائم SignalR را ندارد.
دات نت 8، دو تغییر اساسی را در اینجا ارائه میدهد:
- در اینجا حالت جدیدی به نام SSR یا Static Server Rendering ارائه شدهاست (به آن Server-side rendering هم میگویند). در این حالت نه WASM ای درون مرورگر کاربر اجرا میشود و نه اتصال دائم SignalR ای برای کار با آن نیاز است! در این حالت برنامه تقریبا همانند یک MVC Razor application سنتی کار میکند؛ یعنی سرور، کار رندر نهایی HTML قابل ارائهی به کاربر را انجام داده و آنرا به سمت مرورگر، برای نمایش ارسال میکند و همچنین سرور، هیچ حالتی را هم از برنامه ذخیره نمیکند و بهعلاوه، کلاینت نیز نیازی به دریافت کل برنامه را در ابتدای کار ندارد (هم آغاز و نمایش سریعی را دارد و هم نیاز به منابع کمتری را در سمت سرور برای اجرا دارد).
- تغییر مهم دیگری که در دات نت 8 صورت گرفته، امکان ترکیب کردن حالتهای مختلف رندر صفحات، در برنامههای Blazor است. یعنی میتوان یک صفحهی SSR را داشت که تنها قسمت کوچکی از آن بر اساس معماری Blazor Server کار کند (قسمتهای اصطلاحا interactive یا تعاملی آن). یا حتی در یک برنامه، امکان ترکیب Blazor Server و Blazor WASM نیز وجود دارد.
اینها عناوین موارد جدیدی هستند که در این سری به تفصیل بررسی خواهیم کرد.
تاریخچهی نمایش صفحات وب در مرورگرها
در ابتدای ارائهی وب، سرورها، ابتدا درخواستی را از طرف مرورگر کلاینت دریافت میکردند و در پاسخ به آن، HTML ای را تولید و بازگشت میدادند. حاصل آن، نمایش یک صفحهی استاتیک non-interactive بود (غیرتعاملی). علت تاکید بر روی واژهی interactive (تعاملی)، بکارگیری گستردهی آن در نگارش جدید Blazor است؛ تا حدی که ایجاد قالبهای جدید آغازین برنامههای آن، با تنظیم این گزینه همراه است. برای مشاهدهی آن، پس از نصب SDK جدید دات نت، دستور dotnet new blazor --help را صادر کنید.
سپس JavaScript از راه رسید و هدف آن، افزودن interactivity به صفحات سمت کاربر بود تا بتوان بر اساس تعاملات و ورودیهای کاربر، تغییراتی را بر روی محتوای صفحه اعمال کرد. در ادامه JavaScript این امکان را یافت تا بتواند درخواستهایی را به سمت سرور ارسال کند و بر اساس خروجی دریافتی، قسمتهایی از صفحهی جاری استاتیک را به صورت پویا تغییر دهد.
در ادامه با بالارفتن توانمندیهای سختافزاری و همچنین توسعهی کتابخانههای جاوااسکریپتی، به برنامههای تک صفحهای کاملا پویا و interactive رسیدیم که به آنها SPA گفته میشود (Single-page applications)؛ از این دست کتابخانهها میتوان به Backbone اشاره کرد و پس از آن به React و Angular. برنامههای Blazor نیز اخیرا به این جمع اضافه شدهاند.
اما ... اخیرا توسعه دهندهها به این نتیجه رسیدهاند که SPAها برای تمام امور، مناسب و یا حتی الزامی نیستند. گاهی از اوقات ما فقط نیاز داریم تا محتوایی را خیلی سریع و بهینه تولید و بازگشت دهیم؛ مانند نمایش لیست اخبار، به هزاران دنبال کننده، با حداقل مصرف منابع و در همین حال نیاز به interactivity در بعضی از قسمتهای خاص نیز احساس میشود. این رویهای است که در تعدادی از فریمورکهای جدید و مدرن جاوااسکریپتی مانند Astro در پیش گرفته شدهاست؛ در آنها ترکیبی از رندر سمت سرور، به همراه interactivity سمت کاربر، مشاهده میشود. برای مثال این امکان را فراهم میکنند تا محتوای قسمتی از صفحه را در سمت سرور تهیه و رندر کنید، یا قسمتی از صفحه (یک کامپوننت خاص)، به صورت interactive فعال شود. ترکیب این دو مورد، دقیقا هدف اصلی Blazor، در دات نت 8 است. برای مثال فرض کنید میخواهید برنامه و سایتی را طراحی کنید که چند صفحهی آغازین آن، بدون هیچگونه تعاملی با کاربر هستند و باید سریع و SEO friendly باشند. همچنین تعدادی از صفحات آن هم قرار است فقط یک سری محتوای ثابت را نمایش دهند، اما در قسمتهای خاصی از آن نیاز به تعامل با کاربر است.
معرفی Blazor یکپارچه در دات نت 8
مهمترین تغییر Blazor در دات نت 8، یکپارچه شدن حالتهای مختلف رندر آن در سمت سرور است. تغییرات زیاد رخ دادهاند تا امکان داشتن Server-side rendering یا SSR به همراه قابلیت فعال سازی interactivity به ازای هر کامپوننت دلخواه که به آن حالتهای رندر (Render modes) گفته میشود، میسر شوند. در اساس، این روش جدید، همان Blazor Server بهبود یافتهاست که حالت SSR، حالت پیشفرض آن است. در کنار آن قابلیتهای راهبری (navigation)، نیز بهبود یافتهاند تا برنامههای SSR همانند برنامههای SPA بهنظر برسند.
در دات نت 8، ASP.NET Core و Blazor نیز کاملا یکپارچه شدهاند. در این حالت برنامههای Blazor Server میتوانند همانند برنامههای MVC Razor Pages متداول، با کمک قابلیت SSR، صفحات غیر interactive ای را رندر کنند؛ البته به کمک کامپوننتهای Razor. مزیت آن نسبت به MVC Razor Pages این است که اکنون میتوانید هر کامپوننت مجزایی از صفحه را نیز کاملا interactive کنید.
در نگارشهای قبلی Blazor، برنامههای Blazor Server حتی برای شروع کار نیاز به یک صفحهی Razor Pages آغازین داشتند، اما دیگر نیازی به این مورد با دات نت 8 نیست؛ چون ASP.NET Core 8x میتواند کامپوننتهای Razor را نیز به صورت HTML خالص بازگشت دهد و یا Minimal API آن به همراه خروجی new RazorComponentResult نیز شدهاست. در حالت SSR، حتی سیستم مسیریابی ASP.NET Core نیز با Blazor یکی شدهاست.
البته این تغییرات، حالتهای خالص Blazor WebAssembly و یا MAUI Blazor Hybrid را تحت تاثیر قرار نمیدهند؛ اما بدیهی است تمام آنها از سایر قابلیتهای جدید اضافه شده نیز بهرهمند هستند.
معرفی حالتهای مختلف رندر Blazor در دات نت 8
یک برنامهی جدید 8x Blazor، در اساس بر روی سرور رندر میشود (همان SSR). اما همانطور که عنوان شد، این SSR ارائه شدهی توسط Blazor، یک قابلیت مهم را نسبت به MVC Razor pages دارد و آن هم امکان فعالسازی interactivity، به ازای کامپوننتها و قسمتهای کوچکی از صفحه است که واقعا نیاز است تعاملی باشند. فعالسازی آن هم بسیار ساده، یکدست و یکپارچه است:
در این حالت میتوان مشخص کرد که آیا قرار است این کامپوننت خاصی که در قسمتی از صفحهی جاری قرار است رندر شود، نیاز است به کمک فناوری وباسمبلی اجرا شود و یا قرار است بر روی سرور رندر شود؟
این تعاریف حالت رندر را توسط دایرکتیوها نیز میتوان به ازای هر کامپوننت مجزا، مشخص کرد (یکی از این دو حالت باید بکار گرفته شود):
حالت رندر مشخص شده، توسط زیرکامپوننتهای تشکیل دهندهی این کامپوننتها نیز به ارث برده میشوند؛ اما امکان ترکیب آنها با هم نیست. یعنی اگر حالت رندر را InteractiveServer انتخاب کردید، زیرکامپوننتهای تشکیل دهندهی آن نمیتوانند حالت دیگری را انتخاب کنند.
امکان اعمال این ویژگیها به مسیریاب برنامه نیز وجود دارد که در این حالت کل برنامه را interactive میکند. اما در حالت پیشفرض، برنامهای که ایجاد میشود فاقد تنظیمات تعاملی در ریشهی اصلی آن است.
معرفی حالت رندر خودکار در Blazor 8x
یکی دیگر از حالتهای رندر معرفی شدهی در Blazor 8x، حالت Auto است:
این حالت رندر، به صورت پیشفرض از WebAssembly استفاده میکند؛ اما فقط زمانیکه فایلهای مرتبط با آن کاملا دریافت شدهباشند. یعنی در ابتدای کار برای ارائهی امکانات تعاملی، از حالت سریع و سبک InteractiveServer استفاده میکند؛ اما در پشت صحنه مشغول به دریافت فایلهای مرتبط با نگارش وباسمبلی کامپوننت فوق خواهد شد. پس از بارگذاری و کش شدن این فایلها، برای بارهای بعدی رندر، فقط از حالت وباسمبلی استفاده میکند.
معرفی حالت رندر Streaming در Blazor 8x
در بار اول بارگذاری صفحات، ممکن است دریافت اطلاعات مرتبط با آن کمی کند و با وقفه باشند. در این حالت برای اینکه برنامههای SSR یک صفحهی خالی را نمایش ندهند، میتوان در ابتدا با استفاده از حالت رندر جدید StreamRendering، حداقل قالب صفحه را نمایش داد و سپس اصل اطلاعات را:
این روش، از HTTP Streaming در پشت صحنه استفاده کرده و مرحله به مرحله قسمتهای تکمیل شده را به سمت مرورگر کاربر، برای نمایش نهایی ارسال میکند.
جزئیات بیشتر نحوهی کار با این حالات را در قسمتهای بعدی بررسی خواهیم کرد.
نتیجه گیری:
روشهای جدید رندر ارائه شدهی در Blazor 8x، برای موارد زیر مفید هستند:
- زمانیکه قسمت عمدهای از برنامهی شما بر روی سرور اجرا میشود.
- زمانیکه خروجی اصلی برنامهی شما بیشتر حاوی محتواهای ثابت است؛ مانند CMSها.
- زمانیکه میخواهید صفحات شما قابل ایندکس شدن توسط موتورهای جستجو باشند و مباحث SEO برای شما مهم است.
- زمانیکه نیاز به مقدار کمی امکانات تعاملی دارید و فقط قسمتهای کوچکی از صفحه قرار است تعاملی باشند. برای مثال فقط قرار است قسمت کوچکی از یک صفحهی نمایش مقالهای از یک بلاگ، به همراه امکان رای دادن به آن مطلب (تنها قسمت «تعاملی» صفحه) باشد.
- و یا زمانیکه میخواهید MVC Razor Pages را با یک فناوری جدید که امکانات بیشتری را در اختیار شما قرار میدهد، جایگزین کنید.
- Blazor Server، که در آن یک اتصال SignalR، بین مرورگر کاربر و سرور، برقرار شده و سرور حالات مختلف این جلسهی کاری را مدیریت میکند. آغاز این حالت، بسیار سریع است؛ اما وجود اتصال دائم SignalR در آن ضروری است. نیاز به وجود این اتصال دائم، با تعداد بالای کاربر میتواند کارآیی سرور را تحت تاثیر قرار دهد.
- Blazor WASM: در این حالت کل برنامهی Blazor، درون مرورگر کاربر اجرا میشود و برای اینکار الزاما نیازی به سرور ندارد؛ اما آغاز اولیهی آن به علت نیاز به بارگذاری کل برنامه درون مرورگر کاربر، اندکی کند است. اتصال این روش با سرور، از طریق روشهای متداول کار با Web API صورت میگیرد و نیازی به اتصال دائم SignalR را ندارد.
دات نت 8، دو تغییر اساسی را در اینجا ارائه میدهد:
- در اینجا حالت جدیدی به نام SSR یا Static Server Rendering ارائه شدهاست (به آن Server-side rendering هم میگویند). در این حالت نه WASM ای درون مرورگر کاربر اجرا میشود و نه اتصال دائم SignalR ای برای کار با آن نیاز است! در این حالت برنامه تقریبا همانند یک MVC Razor application سنتی کار میکند؛ یعنی سرور، کار رندر نهایی HTML قابل ارائهی به کاربر را انجام داده و آنرا به سمت مرورگر، برای نمایش ارسال میکند و همچنین سرور، هیچ حالتی را هم از برنامه ذخیره نمیکند و بهعلاوه، کلاینت نیز نیازی به دریافت کل برنامه را در ابتدای کار ندارد (هم آغاز و نمایش سریعی را دارد و هم نیاز به منابع کمتری را در سمت سرور برای اجرا دارد).
- تغییر مهم دیگری که در دات نت 8 صورت گرفته، امکان ترکیب کردن حالتهای مختلف رندر صفحات، در برنامههای Blazor است. یعنی میتوان یک صفحهی SSR را داشت که تنها قسمت کوچکی از آن بر اساس معماری Blazor Server کار کند (قسمتهای اصطلاحا interactive یا تعاملی آن). یا حتی در یک برنامه، امکان ترکیب Blazor Server و Blazor WASM نیز وجود دارد.
اینها عناوین موارد جدیدی هستند که در این سری به تفصیل بررسی خواهیم کرد.
تاریخچهی نمایش صفحات وب در مرورگرها
در ابتدای ارائهی وب، سرورها، ابتدا درخواستی را از طرف مرورگر کلاینت دریافت میکردند و در پاسخ به آن، HTML ای را تولید و بازگشت میدادند. حاصل آن، نمایش یک صفحهی استاتیک non-interactive بود (غیرتعاملی). علت تاکید بر روی واژهی interactive (تعاملی)، بکارگیری گستردهی آن در نگارش جدید Blazor است؛ تا حدی که ایجاد قالبهای جدید آغازین برنامههای آن، با تنظیم این گزینه همراه است. برای مشاهدهی آن، پس از نصب SDK جدید دات نت، دستور dotnet new blazor --help را صادر کنید.
سپس JavaScript از راه رسید و هدف آن، افزودن interactivity به صفحات سمت کاربر بود تا بتوان بر اساس تعاملات و ورودیهای کاربر، تغییراتی را بر روی محتوای صفحه اعمال کرد. در ادامه JavaScript این امکان را یافت تا بتواند درخواستهایی را به سمت سرور ارسال کند و بر اساس خروجی دریافتی، قسمتهایی از صفحهی جاری استاتیک را به صورت پویا تغییر دهد.
در ادامه با بالارفتن توانمندیهای سختافزاری و همچنین توسعهی کتابخانههای جاوااسکریپتی، به برنامههای تک صفحهای کاملا پویا و interactive رسیدیم که به آنها SPA گفته میشود (Single-page applications)؛ از این دست کتابخانهها میتوان به Backbone اشاره کرد و پس از آن به React و Angular. برنامههای Blazor نیز اخیرا به این جمع اضافه شدهاند.
اما ... اخیرا توسعه دهندهها به این نتیجه رسیدهاند که SPAها برای تمام امور، مناسب و یا حتی الزامی نیستند. گاهی از اوقات ما فقط نیاز داریم تا محتوایی را خیلی سریع و بهینه تولید و بازگشت دهیم؛ مانند نمایش لیست اخبار، به هزاران دنبال کننده، با حداقل مصرف منابع و در همین حال نیاز به interactivity در بعضی از قسمتهای خاص نیز احساس میشود. این رویهای است که در تعدادی از فریمورکهای جدید و مدرن جاوااسکریپتی مانند Astro در پیش گرفته شدهاست؛ در آنها ترکیبی از رندر سمت سرور، به همراه interactivity سمت کاربر، مشاهده میشود. برای مثال این امکان را فراهم میکنند تا محتوای قسمتی از صفحه را در سمت سرور تهیه و رندر کنید، یا قسمتی از صفحه (یک کامپوننت خاص)، به صورت interactive فعال شود. ترکیب این دو مورد، دقیقا هدف اصلی Blazor، در دات نت 8 است. برای مثال فرض کنید میخواهید برنامه و سایتی را طراحی کنید که چند صفحهی آغازین آن، بدون هیچگونه تعاملی با کاربر هستند و باید سریع و SEO friendly باشند. همچنین تعدادی از صفحات آن هم قرار است فقط یک سری محتوای ثابت را نمایش دهند، اما در قسمتهای خاصی از آن نیاز به تعامل با کاربر است.
معرفی Blazor یکپارچه در دات نت 8
مهمترین تغییر Blazor در دات نت 8، یکپارچه شدن حالتهای مختلف رندر آن در سمت سرور است. تغییرات زیاد رخ دادهاند تا امکان داشتن Server-side rendering یا SSR به همراه قابلیت فعال سازی interactivity به ازای هر کامپوننت دلخواه که به آن حالتهای رندر (Render modes) گفته میشود، میسر شوند. در اساس، این روش جدید، همان Blazor Server بهبود یافتهاست که حالت SSR، حالت پیشفرض آن است. در کنار آن قابلیتهای راهبری (navigation)، نیز بهبود یافتهاند تا برنامههای SSR همانند برنامههای SPA بهنظر برسند.
در دات نت 8، ASP.NET Core و Blazor نیز کاملا یکپارچه شدهاند. در این حالت برنامههای Blazor Server میتوانند همانند برنامههای MVC Razor Pages متداول، با کمک قابلیت SSR، صفحات غیر interactive ای را رندر کنند؛ البته به کمک کامپوننتهای Razor. مزیت آن نسبت به MVC Razor Pages این است که اکنون میتوانید هر کامپوننت مجزایی از صفحه را نیز کاملا interactive کنید.
در نگارشهای قبلی Blazor، برنامههای Blazor Server حتی برای شروع کار نیاز به یک صفحهی Razor Pages آغازین داشتند، اما دیگر نیازی به این مورد با دات نت 8 نیست؛ چون ASP.NET Core 8x میتواند کامپوننتهای Razor را نیز به صورت HTML خالص بازگشت دهد و یا Minimal API آن به همراه خروجی new RazorComponentResult نیز شدهاست. در حالت SSR، حتی سیستم مسیریابی ASP.NET Core نیز با Blazor یکی شدهاست.
البته این تغییرات، حالتهای خالص Blazor WebAssembly و یا MAUI Blazor Hybrid را تحت تاثیر قرار نمیدهند؛ اما بدیهی است تمام آنها از سایر قابلیتهای جدید اضافه شده نیز بهرهمند هستند.
معرفی حالتهای مختلف رندر Blazor در دات نت 8
یک برنامهی جدید 8x Blazor، در اساس بر روی سرور رندر میشود (همان SSR). اما همانطور که عنوان شد، این SSR ارائه شدهی توسط Blazor، یک قابلیت مهم را نسبت به MVC Razor pages دارد و آن هم امکان فعالسازی interactivity، به ازای کامپوننتها و قسمتهای کوچکی از صفحه است که واقعا نیاز است تعاملی باشند. فعالسازی آن هم بسیار ساده، یکدست و یکپارچه است:
@* For being rendered on the server *@ <Counter @rendermode="@InteractiveServer" /> @* For running in WebAssembly *@ <Counter @rendermode="@InteractiveWebAssembly" />
این تعاریف حالت رندر را توسط دایرکتیوها نیز میتوان به ازای هر کامپوننت مجزا، مشخص کرد (یکی از این دو حالت باید بکار گرفته شود):
@rendermode InteractiveServer @rendermode InteractiveWebAssembly
امکان اعمال این ویژگیها به مسیریاب برنامه نیز وجود دارد که در این حالت کل برنامه را interactive میکند. اما در حالت پیشفرض، برنامهای که ایجاد میشود فاقد تنظیمات تعاملی در ریشهی اصلی آن است.
معرفی حالت رندر خودکار در Blazor 8x
یکی دیگر از حالتهای رندر معرفی شدهی در Blazor 8x، حالت Auto است:
<Counter @rendermode="@InteractiveAuto" />
معرفی حالت رندر Streaming در Blazor 8x
در بار اول بارگذاری صفحات، ممکن است دریافت اطلاعات مرتبط با آن کمی کند و با وقفه باشند. در این حالت برای اینکه برنامههای SSR یک صفحهی خالی را نمایش ندهند، میتوان در ابتدا با استفاده از حالت رندر جدید StreamRendering، حداقل قالب صفحه را نمایش داد و سپس اصل اطلاعات را:
@attribute [StreamRendering(prerender: true)]
جزئیات بیشتر نحوهی کار با این حالات را در قسمتهای بعدی بررسی خواهیم کرد.
نتیجه گیری:
روشهای جدید رندر ارائه شدهی در Blazor 8x، برای موارد زیر مفید هستند:
- زمانیکه قسمت عمدهای از برنامهی شما بر روی سرور اجرا میشود.
- زمانیکه خروجی اصلی برنامهی شما بیشتر حاوی محتواهای ثابت است؛ مانند CMSها.
- زمانیکه میخواهید صفحات شما قابل ایندکس شدن توسط موتورهای جستجو باشند و مباحث SEO برای شما مهم است.
- زمانیکه نیاز به مقدار کمی امکانات تعاملی دارید و فقط قسمتهای کوچکی از صفحه قرار است تعاملی باشند. برای مثال فقط قرار است قسمت کوچکی از یک صفحهی نمایش مقالهای از یک بلاگ، به همراه امکان رای دادن به آن مطلب (تنها قسمت «تعاملی» صفحه) باشد.
- و یا زمانیکه میخواهید MVC Razor Pages را با یک فناوری جدید که امکانات بیشتری را در اختیار شما قرار میدهد، جایگزین کنید.
نظرات مطالب
EF Code First #12
- نیازی به بازنویسی SaveChanges اصلی نیست؛ چون تعریف و پیاده سازی آن در کلاس پایه DbContext قرار دارد.
- در اینجا متد جدید SaveAllChanges تعریف شد تا بدانید اگر خواستید پیش از SaveChanges اصلی، مثلا کار اعتبارسنجی دستی را انجام دهید (برای نمونه در برنامههای دسکتاپ مثلا)، چگونه میتوان به آن دسترسی داشت.
- DbContext در DataLayer قرار دارد. زمانیکه با یک ORM کار میکنید، خود ORM همان DataLayer شما است. لایهای که از آن استفاده میکند (لایه سرویس)، صرفا با اینترفیس IUnitOfWork کار میکند و تعاریف موجود در آن و نه چیزی بیشتر از آن.
- زمانیکه با IUnitOfWork کار میکنید، در هیچ قسمتی از برنامه قرار نیست مستقیما new MyContext مشاهده شود. تامین این وهله صرفا از طریق تزریق وابستگیها صورت میگیرد (کل بحث جاری یا همان پیاده سازی الگوی Context Per Request بر همین مبنا است؛ یک وهله از Context در طول یک درخواست. نه اینکه هر جایی علاقمند بودیم یک new MyContext دستی نوشته شود).
- در اینجا متد جدید SaveAllChanges تعریف شد تا بدانید اگر خواستید پیش از SaveChanges اصلی، مثلا کار اعتبارسنجی دستی را انجام دهید (برای نمونه در برنامههای دسکتاپ مثلا)، چگونه میتوان به آن دسترسی داشت.
- DbContext در DataLayer قرار دارد. زمانیکه با یک ORM کار میکنید، خود ORM همان DataLayer شما است. لایهای که از آن استفاده میکند (لایه سرویس)، صرفا با اینترفیس IUnitOfWork کار میکند و تعاریف موجود در آن و نه چیزی بیشتر از آن.
- زمانیکه با IUnitOfWork کار میکنید، در هیچ قسمتی از برنامه قرار نیست مستقیما new MyContext مشاهده شود. تامین این وهله صرفا از طریق تزریق وابستگیها صورت میگیرد (کل بحث جاری یا همان پیاده سازی الگوی Context Per Request بر همین مبنا است؛ یک وهله از Context در طول یک درخواست. نه اینکه هر جایی علاقمند بودیم یک new MyContext دستی نوشته شود).