React 16x
- قسمت 1 : معرفی و شروع به کار
- قسمت 2 : بررسی پیشنیازهای جاوا اسکریپتی - بخش 1
- قسمت 3 : بررسی پیشنیازهای جاوا اسکریپتی - بخش 2
- قسمت 4 : کامپوننتها - بخش 1 - کار با عبارات JSX
- قسمت 5 : کامپوننتها - بخش 2 - نمایش لیستها و مدیریت رویدادها و حالات
- قسمت 6 : کامپوننتها - بخش 3 - یک تمرین
- قسمت 7 : ترکیب کامپوننتها - بخش 1 - ارسال دادهها، مدیریت رخدادها
- قسمت 8 : ترکیب کامپوننتها - بخش 2 - مدیریت state
- قسمت 9 : ترکیب کامپوننتها - بخش 3 - Lifecycle Hooks
- قسمت 10 : ترکیب کامپوننتها - بخش 4 - یک تمرین
- قسمت 11 : طراحی یک گرید - بخش 1 - کامپوننت صفحه بندی
- قسمت 12 : طراحی یک گرید - بخش 2 - فیلتر کردن اطلاعات
- قسمت 13 : طراحی یک گرید - بخش 3 - مرتب سازی اطلاعات
- قسمت 14: طراحی یک گرید - بخش 4 - پویاسازی تعاریف ستونها
- قسمت 15 :مسیریابی - بخش 1 - تعریف و تنظیم مسیریابیها
- قسمت 16 : مسیریابی - بخش 2 - پارامترهای مسیریابی
- قسمت 17 : مسیریابی - بخش 3 - یک تمرین
- قسمت 18 : کار با فرمها - بخش 1 - دریافت ورودیها از کاربر
- قسمت 19 : کار با فرمها - بخش 2 - اعتبارسنجی ورودیهای کاربران
- قسمت 20 : کار با فرمها - بخش 3 - بهبود کیفیت کدهای فرم لاگین
- قسمت 21 : کار با فرمها - بخش 4 - چند تمرین
- قسمت 22 : ارتباط با سرور - بخش 1 - برپایی تنظیمات
- قسمت 23 : ارتباط با سرور - بخش 2 - شروع به کار با Axios
- قسمت 24 : ارتباط با سرور - بخش 3 - نکات تکمیلی کار با Axios
- قسمت 25 : ارتباط با سرور - بخش 4 - یک تمرین
- قسمت 26 : احراز هویت و اعتبارسنجی کاربران - بخش 1 - ثبت نام و ورود به سیستم
- قسمت 27 : احراز هویت و اعتبارسنجی کاربران - بخش 2 - استخراج و نمایش اطلاعات JWT و خروج از سیستم
- قسمت 28 : احراز هویت و اعتبارسنجی کاربران - بخش 3 - فراخوانی منابع محافظت شده و مخفی کردن عناصر صفحه
- قسمت 29 : احراز هویت و اعتبارسنجی کاربران - بخش 4 - محافظت از مسیرها
- قسمت 30 : React Hooks - بخش 1 - معرفی useState و useEffect
- قسمت 31 : React Hooks - بخش 2 - مقایسه حالتهای مختلف مدیریت حالت با useState Hook
- قسمت 32 : React Hooks - بخش 3 - نکات ویژهی برقراری ارتباط با سرور
- قسمت 33 : React Hooks - بخش 4 - useContext Hook
Redux
- قسمت اول : Redux چیست؟
- قسمت دوم : بررسی توابع Redux
- قسمت سوم : روش اتصال Redux به برنامههای React
- قسمت چهارم : انجام اعمال async با Redux
- قسمت پنجم : Redux Hooks
- قسمت ششم : MobX چیست؟
- قسمت هفتم : بررسی مفاهیم Mobx
- قسمت هشتم : تنظیمات پروژههای React برای کار با Mobx decorators
- قسمت نهم : مثالی از کتابخانهی mobx-react
- قسمت دهم : MobX Hooks و اعمال Async در Mobx
- ساخت یک مثال Todo با MobX و React
- روش یکی کردن پروژههای React و ASP.NET Core
- آپلود فایلها توسط برنامههای React به یک سرور ASP.NET Core به همراه نمایش درصد پیشرفت
- روش کار با فایلهای ایستا در برنامههای React
- روش کار با فایلهای پویای ارائه شدهی توسط یک برنامهی ASP.NET Core در برنامههای React
- استفاده از قالب مخصوص Redux Toolkit جهت ایجاد پروژههای React/Redux
- تبدیل یک قالب HTML معمولی به قالب React
- React component lifecycle
- React reconciliation
حالتهای مختلف ذخیره سازی اطلاعات در مرورگر کاربر
Web Storage و یا Client-side storage در دو حالت کلی session storage و local storage قابل دسترسی است:
الف) session storage
در این حالت اطلاعات ذخیره شدهی در session storage، پس از بسته شدن مرورگر، به صورت خودکار حذف خواهند شد.
ب) local storage
اطلاعات ذخیره شدهی در local storage پس از بسته شدن مرورگر نیز باقی مانده و قابل دسترسی و بازیابی مجدد هستند. تاریخ انقضای آنها صرفا بر اساس خالی شدن دستی کش مرورگر توسط کاربر و یا حذف دستی اطلاعات آن توسط کدهای برنامه تعیین میشود.
هر دو حالت فوق به صورت ایزوله ارائه میشوند؛ با محدودیت حجم 10 مگابایت (جمع حجم نهایی هر دو حالت با هم، محدود به 10 مگابایت است). به این معنا که برنامههای هر دومین، تنها به محل ذخیره سازی خاص همان دومین دسترسی خواهند داشت.
همچنین API دسترسی به آنها synchronous است و کار کردن با آنها سادهاست.
البته Client-side storage به دو مورد فوق خلاصه نمیشود و شامل File Storage ،WebSQL ،IndexedDB و کوکیهای مرورگر نیز هست.
- File Storage هنوز مراحل آزمایشی خودش را طی میکند و مناسب برنامههای دنیای واقعی نیست.
- WebSQL قرار بود بر اساس بانک اطلاعاتی معروف SQLite ارائه شود؛ اما W3C در سال 2010 این استاندارد را منسوخ شده اعلام کرد و با IndexedDB جایگزین شد. دسترسی به آن async است و میتواند موضوع بحثی مجزا باشد.
- کوکیهای مرورگرها نیز یکی دیگر از روشهای ذخیره سازی اطلاعات در مرورگرها هستند و تنها به ذخیره سازی حداکثر 4096 بایت اطلاعات محدود هستند. کوکیها نیز همانند local storage پس از بسته شدن مرورگر باقی میمانند؛ اما برخلاف آن، دارای تاریخ انقضاء و همچنین قابلیت ارسال بین دومینها را نیز دارا میباشند. اگر تاریخ انقضای یک کوکی تعیین نشود، همانند session storage، در پایان کار مرورگر و بسته شدن آن، حذف خواهد شد.
تهیه یک سرویس Angular برای کار با Web Storage
جهت کپسوله سازی نحوهی کار با session storage و local storage میتوان سرویسی را برای اینکار تهیه کرد:
import { Injectable } from "@angular/core"; @Injectable() export class BrowserStorageService { getSession(key: string): any { const data = window.sessionStorage.getItem(key); return JSON.parse(data); } setSession(key: string, value: any): void { const data = value === undefined ? null : JSON.stringify(value); window.sessionStorage.setItem(key, data); } removeSession(key: string): void { window.sessionStorage.removeItem(key); } removeAllSessions(): void { for (const key in window.sessionStorage) { if (window.sessionStorage.hasOwnProperty(key)) { this.removeSession(key); } } } getLocal(key: string): any { const data = window.localStorage.getItem(key); return JSON.parse(data); } setLocal(key: string, value: any): void { const data = value === undefined ? null : JSON.stringify(value); window.localStorage.setItem(key, data); } removeLocal(key: string): void { window.localStorage.removeItem(key); } removeAllLocals(): void { for (const key in window.localStorage) { if (window.localStorage.hasOwnProperty(key)) { this.removeLocal(key); } } } }
در حالت setItem اطلاعاتی را که مرورگرها ذخیره میکنند باید رشتهای باشد. به همین جهت توسط متد JSON.stringify میتوان یک شیء را تبدیل به رشته کرد و ذخیره نمود و در حالت getItem توسط متد JSON.parse، میتوان این رشته را مجددا به همان شیء پیشین خود تبدیل کرد و بازگشت داد.
محل صحیح تعریف BrowserStorageService
همانطور که در مطلب «سازماندهی برنامههای Angular توسط ماژولها» بررسی شد، محل صحیح تعریف این سرویس سراسری مشترک در بین کامپوننتها و ماژولهای برنامه، در CoreModule و پوشهی src\app\core\browser-storage.service.ts است:
import { BrowserStorageService } from "./browser-storage.service"; import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; import { RouterModule } from "@angular/router"; @NgModule({ imports: [CommonModule, RouterModule], exports: [], // components that are used in app.component.ts will be listed here. declarations: [], // components that are used in app.component.ts will be listed here. providers: [BrowserStorageService] // singleton services of the whole app will be listed here. }) export class CoreModule { };
و CoreModule نیز به AppModule اضافه میشود:
import { CoreModule } from "./core/core.module"; @NgModule({ imports: [ //... CoreModule, //... RouterModule.forRoot(appRoutes) ], //... }) export class AppModule { }
بنابراین یکی دیگر از روشهای به اشتراک گذاری اطلاعات در بین قسمتهای مختلف برنامه، ذخیره سازی آنها در session/local storage و سپس بازیابی آنها بر اساس کلیدهای مشخص آنها است.
مثالی از نحوهی کاربرد BrowserStorageService
برای آزمایش سرویس تهیه شده، از کامپوننت و قالب ذیل استفاده خواهیم کرد. در اینجا سرویس BrowserStorageService به سازندهی کلاس تزریق شدهاست و سپس دو حالت session storage و local storage مورد بررسی قرار گرفتهاند:
import { BrowserStorageService } from "./../../core/browser-storage.service"; import { Component, OnInit } from "@angular/core"; @Component({ selector: "app-browser-storage-sample-test", templateUrl: "./browser-storage-sample-test.component.html", styleUrls: ["./browser-storage-sample-test.component.css"] }) export class BrowserStorageSampleTestComponent implements OnInit { fromSessionStorage = ""; fromLocalStorage = "" sessionStorageKey = "sessionStorageKey1"; localStorageKey = "localStorageKey1" constructor(private browserStorage: BrowserStorageService) { } ngOnInit() { } sessionStorageSetItem() { this.browserStorage.setSession(this.sessionStorageKey, "Val1"); } sessionStorageGetItem() { this.fromSessionStorage = this.browserStorage.getSession(this.sessionStorageKey); } localStorageSetItem() { this.browserStorage.setLocal(this.localStorageKey, { key1: "val1", key2: 2 }); } localStorageGetItem() { this.fromLocalStorage = JSON.stringify(this.browserStorage.getLocal(this.localStorageKey)); } }
<h1>Browser storage sample</h1> <div class="panel"> <button class="btn btn-primary" (click)="sessionStorageSetItem()" type="button">sessionStorage -> Set Item</button> <button class="btn btn-success" (click)="sessionStorageGetItem()" type="button">sessionStorage -> Get Item</button> <div class="alert alert-info" *ngIf="fromSessionStorage"> {{fromSessionStorage}} </div> </div> <div class="panel"> <button class="btn btn-warning" (click)="localStorageSetItem()" type="button">localStorage -> Set Item</button> <button class="btn btn-success" (click)="localStorageGetItem()" type="button">localStorage -> Get Item</button> <div class="alert alert-info" *ngIf="fromLocalStorage"> {{fromLocalStorage}} </div> </div>
در این حالت اگر برنامه را اجرا کنیم، یک چنین خروجی قابل مشاهده خواهد بود:
و اگر به برگهی Application کنسول ابزارهای توسعه دهندههای مرورگرها نیز مراجعه کنیم، این مقادیر ثبت شده را در دو حالت استفادهی از session storage و local storage، میتوان مشاهده کرد:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
نحوهی ارتقاء برنامههای SignalR 1.x به SignalR 2.x
2) حذف وابستگیهای قدیمی
Uninstall-Package Microsoft.AspNet.SignalR -RemoveDependencies
3) نصب فایلهای جدید SignalR
Install-Package Microsoft.AspNet.SignalR
4) به روز رسانی ارجاعات اسکریپتی
<script src="Scripts/jquery.signalR-2.0.0.min.js"></script>
5) حذف نحوهی تعریف مسیریابی هابهای SignalR از فایل global.asax برنامه.
protected void Application_Start(object sender, EventArgs e) { //RouteTable.Routes.MapHubs(); }
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(SignalRChat.Startup))] namespace SignalRChat { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } }
اگر از آخرین نگارش VS.NET استفاده میکنید، این کلاس را توسط گزینه Add -> New Item -> Owin Startup Class نیز میتوانید اضافه نمائید.
@Input('readOnly') FormIsReadOnly: boolean;
ردیابی تغییرات اعمال شده بر روی خصوصیت
- ردیابی تغییرات صورت گرفته از طریق تنظیم setter به متغیر تعریف شده با متادیتای Input@
- پیاده سازی onChanges توسط کامپوننت فرزند جهت ردیابی تغییرات کامپوننت
ردیابی تغییرات از طریق تنظیم setter
<app-customer-info FormIsReadOnly="true"></app-customer-info>
@Component({ selector: 'app-customer-info', templateUrl: './customer-info.component.html', styleUrls: ['./customer-info.component.css'] }) export class CustomerInfoComponent implements OnInit { private _formIsReadOnly: boolean; @Input() set FormIsReadOnly(value: boolean) { if (typeof (value) != 'boolean') throw new Error(`${value} type is not boolean.`); this._formIsReadOnly = value; } get FormIsReadOnly(): boolean { return this._formIsReadOnly; } constructor() { } ngOnInit() { } }
پیاده سازی onChanges توسط کامپوننت فرزند جهت ردیابی تغییرات کامپوننت
import { Component, OnInit, Input, OnChanges, SimpleChange } from '@angular/core'; import { ICustomerInfo } from '../../core/model/ICustomerInfo'; @Component({ selector: 'app-customer-info', template: '<ul>< li *ngFor="let change of changeLog">{{change }}</li></ul>', styleUrls: ['./customer-info.component.css'] }) export class CustomerInfoComponent implements OnChanges { changeLog: string[] = []; @Input() FormIsReadOnly: boolean; ngOnChanges(changes: { [propKey: string]: SimpleChange }) { let log: string[] = []; for (let propName in changes) { let changedProp = changes[propName]; let to = JSON.stringify(changedProp.currentValue); if (changedProp.isFirstChange()) { log.push(`Initial value of ${propName} set to ${to}`); } else { let from = JSON.stringify(changedProp.previousValue); log.push(`${propName} changed from ${from} to ${to}`); } } this.changeLog.push(log.join(', ')); } constructor() { } }
• Initial value of FormIsReadOnly set to true • FormIsReadOnly changed from true to "trued" • FormIsReadOnly changed from "trued" to "true" • FormIsReadOnly changed from "true" to "truef" • FormIsReadOnly changed from "truef" to "true" • FormIsReadOnly changed from "true" to "tru" • FormIsReadOnly changed from "tru" to "tr" • FormIsReadOnly changed from "tr" to "t" • FormIsReadOnly changed from "t" to "" • FormIsReadOnly changed from "" to "t" • FormIsReadOnly changed from "t" to "tr" • FormIsReadOnly changed from "tr" to "tru" • FormIsReadOnly changed from "tru" to "true"
در ادامه عنوان «بهجریان انداختن رخدادها از کامپوننت فرزند و گرفتن آنها را از طریق کامپوننت پدر» را مورد برسی قرار خواهیم داد.
ادامه دارد/
بایدها و نبایدهای استفاده از IoC Containers
protected readonly IUnitOfWork UnitOfWork; protected BaseOperation():this(ObjectFactory.GetInstance<IUnitOfWork>()) { } protected BaseOperation(IUnitOfWork uow) { UnitOfWork = uow; }
JSON Hijacking
Cookie - قسمت اول
- Singleton
- Scoped
- Transient
Singleton (یگانه)
فقط و فقط یک شیء از سرویس ثبت شده با این طول عمر، در اولین درخواست ایجاد میشود و سپس در کل طول حیات برنامه، از همین شیء ایجاد شده، استفاده میگردد. به همین دلیل به آن «یگانه» یا Singleton میگویند. هر زمانیکه این سرویس در خواست داده میشود، DI Container، همان یک شیء را در اختیار درخواست دهنده قرار میدهد و این شیء، هیچگاه از بین نمیرود. به بیان دیگر، DI Container هیچگاه این شیء را از بین نمیبرد. شیء ساخته شده از سرویس ثبت شدهی با حالت Singleton، بین تمامی استفاده کنندگان، به صورت اشتراکی استفاده میشود. این طول عمر تقریبا مشابهی اشیاء ساخته شده توسط Singleton Pattern عمل میکند.
با توجه به مطالب گفته شده، ویژگیهای سرویسهای Singleton به شرح زیر هستند:
- در اولین درخواست به سرویس، یک نمونه از آن ساخته میشود و تا پایان برنامه در حافظه نگه داشته میشود.
- در سایر درخواستها، همان یک نمونهی ساخته شدهی از سرویس، ارائه داده میشود.
- به علت موجود بودن در حافظه، معمولا دسترسی به آنها و عملکرد آنها سریعتر است.
- بار کاری بر روی Garbage Collector فریمورک را کاهش میدهند.
بنابراین در هنگام تعریف کردن یک سرویس به صورت Singleton باید نکات زیر را مد نظر قرار بدهید:
- باید سرویس مورد نظر Thread Safe باشد .
- نباید استفاده کنندهی از این سرویس، امکان تغییر State آن را داشته باشد.
- اگر ساخت شیءای از یک سرویس، هزینهی زیادی را داشته باشد ، احتمالا Singleton کردن آن میتواند ایدهی خوبی باشد.
- شیء ساخته شدهی از این سرویس، تا زمان اجرای برنامه، بخشی از حافظهی برنامه را اشغال میکند. پس باید حجم اشغالی در حافظه را نیز مد نظر قرار داد.
- تعداد دفعات استفاده را در برابر مصرف حافظه در نظر بگیرید.
services.AddSingleton(services => new GuidProvider());
public HomeController(ILogger<HomeController> logger, IMessageServiceA messageService, LiteDbConfig liteDbConfig, GuidProvider guidHelper) { _logger = logger; _messageService = messageService; _messageService = new MessageServiceAA(); _guidHelper = guidHelper; }
حالا اگر برنامه
را اجرا کنیم، میبینید که با تازه
سازی صفحهی Home/Index ، همچنان Id، برابر با یک رشتهی یکسان
است. حتی اگر تب دیگری را در مرورگر باز کنیم و دوباره به این صفحه برویم، میبینیم
که Id برابر همان
رشتهی قبلی است و دلیل این موضوع، ثبت
سرویس Guid Service به صورت Singleton است.
Scoped ( محدود شده )
به ازای هر درخواست (در اینجا معمولا درخواستهای Http مد نظر است) یک نمونه از این سرویس ساخته میشود و در طول حیات این درخواست، DI Container به هر کلاسی که به این سرویس نیاز دارد، همان یک نمونه را برگشت میدهد و این نمونه در کل طول اجرای این درخواست، بین تمامی سرویس گیرندگان، یکسان است. هر زمانی، درخواست به پایان برسد، نمونهی ساخته شده از سرویس، Disposed میگردد و GC میتواند آن را از بین ببرد.
معمولا سرویسهای اتصال به پایگاه دادهها و کار بر روی آنها که شامل خواندن، نوشتن، ویرایش، حذف میشوند را با طول حیات Scoped ، درون DI Container ثبت میکنند . EF Core به صورت پیش فرض ، Db Context را به صورت Scoped ثبت میکند.
سرویسهای Scoped در محدودهی درخواست، مانند Singleton عمل میکنند و شیء ساخته شده و وضعیت آن در بین تمامی سرویسهایی که به آن نیاز دارند، مشترک است. بنابراین باید به این نکته در هنگام تعریف سرویس به صورت Scoped ، توجه داشته باشید.
تمام Middleware های ASP.NET Core هم فقط همان نمونهی ایجاد شده از سرویس Scoped را در طی اجرای یک درخواست خاص، میگیرند.
هر سرویسی که به سرویسهای Scoped نیاز دارد، یا باید به صورت Transient و یا باید به صورت Scoped ثبت شود، تا مانع از این شویم که شیء ساخته شده، فراتر از طول حیات موردنظرمان، در حافظه بماند و از آن استفاده شود .
برای ثبت یک سرویس
به صورت Scoped میتوانیم از متدهای توسعهای با نام AddScoped() با سربارهای
مختلف بر روی IServiceCollection استفاده کنیم. در اینجا از نسخهای که دو
پارامتر جنریک را میگیرد، برای ثبت یک سرویس به صورت Scoped استفاده میکنیم:
services.AddScoped<IMessageServiceB, MessageServiceBA>();
می توانیم سرویس GuidProvider را به جای Signleton ، به صورت Scoped ثبت کنیم:
services.AddScoped(services => new GuidProvider());
Transient (گذرا)
به ازای هر درخواست دهندهی جدید، یک نمونهی جدید از سرویس، توسط DI Container ساخته میشود و در اختیار آن قرار میگیرد.
سرویسهایی را به این صورت ثبت کنید که:
- نیاز به Thread Safe بودن داشته باشند.
- نمی توانید طول عمر سرویس را حدس بزنید.
سرویسهای Transient ، کارآیی پائینتری دارند و سربار عملکردی زیادی بر روی Garbage Collector می گذارند؛ ولی به علت اینکه به ازای هر واکشی، یک نمونهی جدید از آنها ساخته میشود و State بین این اشیاء به اشتراک گذاشته نمیشود، امنیت بیشتری دارند و درک و خطایابی آنها سادهتر است.
برای ثبت سرویسهای Transient از متد توسعهای AddTransient() استفاده میکنیم. سربارهای این متد مانند سربارهای متدهای AddSingleton() و AddScoped() است:
services.AddTransient<IMessageServiceC, MessageServiceCA>();
وابستگیهای محصور شده
یک سرویس نباید وابستهی به سرویسی باشد که طول حیاتی کمتر از طول حیات خودش را دارد.
برای مثال اگر درون سرویسی با طول حیات Singleton، از یک سرویس با طول حیات Transient استفاده کنیم، اینکار باعث میشود که یک نمونه از سرویس Transient در طول حیات برنامه، همیشه درون حافظه بماند و این ممکن است باعث خطاهای عجیبی در هنگام اجرا شود که معمولا خطایابی و رفع آنها مشکل است.
اثرات جانبی وابستگیهای محصور شده:
- به اشتراک گذاری تصادفی وضعیت یک شیء بین Thread ها درون سرویسهایی که Thread Safe نیستند.
- اشیاء، بیش از زمان پیش بینی شدهی برایشان، درون حافظه باقی میمانند.
سرویسهای Transient میتوانند به سرویسهایی با طول حیات زیر وابستگی داشته باشند:
- Transient
- Scoped
- Singleton
سرویسهای Scoped میتوانند به سرویسهایی با طول حیات زیر وابستگی داشته باشند:
- Transient
- Scoped
سرویسهای Singleton میتوانند به سرویس هایی با طول حیات زیر وابستگی داشته باشند:
Singleton
میتوانید از جدول زیر به عنوان راهنمای خلاصه شدهی برای استفادهی امن از سرویسها درون یکدیگر بهره ببرید:
Scope Validation
این قابلیت که به صورت پیش
فرض در حالت توسعهی برنامههای ASP.NET Core فعال است، در زمان شروع برنامه و در Startup ، بررسی میکند که سرویسها، وابستگی
به سرویسهایی با طول حیاتی مناسب، داشته باشند.