من این تابع رو فراخوانی میکنم ولی کاربر همچنان لاگین هست. البته از کش توزیع شده(ذخیره در دیتابیس) که در همین مطلب توضیح داده شد استفاده کردم. بخاطر همین هست یا دلیل دیگه ای داره؟
نظرات مطالب
Url Routing در ASP.Net WebForms
با سلام؛ من از این روش از تکنیک ایجکس استفاده میکنم، ولی نمیدونم الان که Urlها رو با تکنیک شما بازنویسی کردم چگونه توابع رو فراخوانی کنم؟ به وب سرویس دسترسی دارم ولی تابع داخلش اجرا نمیشه
پاسخ به بازخوردهای پروژهها
نمایش متن در زیر جدول مربوط به هر گروه
سپاس.
از روش شما استفاده کردم و درست شد . فقط برای گروه آخر ایجاد نمیشد که در رویداد MainTableAdded ، دوباره تابع ایجاد توضیحات را فراخوانی کردم و برای گروه آخر هم ایجاد شد.
اشتراکها
دریافت کتابخانه های جاوا
در این قسمت قصد داریم اطلاعات یادداشتهای کاربران را توسط کامپوننت mat-table نمایش دهیم که به همراه قابلیتهایی مانند صفحه بندی، مرتب سازی و فیلتر کردن دادهها است.
کامپوننت mat-table
کار کامپوننت mat-table نمایش اطلاعات در ردیفها و ستونها است. به همراه آن mat-paginator برای نمایش UI صفحه بندی اطلاعات، دایرکتیو matSort و mat-sort-header برای افزودن رابط کاربری مرتب سازی اطلاعات و امکان تغییر منبع داده آن برای فیلتر کردن دادهها، نیز وجود دارند.
افزودن کامپوننت جدید notes برای نمایش یادداشتهای کاربران
برای نمایش لیست یادداشتهای هر شخص، کامپوننت جدید Notes را به صورت زیر در پوشهی components ایجاد میکنیم:
علت اینجا است که نمیخواهیم کامپوننت نمایش جزئیات شخص را بیش از اندازه شلوغ کنیم. بنابراین به قالب کامپوننت main-content (فایل main-content.component.html) مراجعه کرده و selector این کامپوننت را در آنجا درج میکنیم:
همانطور که ملاحظه میکنید app-notes در برگهی دوم کامپوننت mat-tab-group درج شدهاست. همچنین قصد داریم لیست userNotes جاری را به خاصیت notes آن نیز ارسال کنیم. به همین جهت به کامپوننت notes مراجعه کرده و این ورودی را ایجاد میکنیم:
فعلا جهت بررسی صحت عملکرد آن به قالب این کامپوننت (فایل notes.component.html) مراجعه کرده و آنرا به صورت json نمایش میدهیم:
تکمیل کامپوننت Notes توسط یک data table
در ادامه قصد داریم این اطلاعات خام را توسط یک data table نمایش دهیم. به همین جهت ابتدا به مستندات mat-table مراجعه کرده و همانند قبل، مثالی را پیدا میکنیم که به منظور ما نزدیکتر باشد. سپس کدهای آنرا به برنامه اضافه کرده و سفارشی سازی میکنیم. در ابتدا مثال basic آنرا دقیقا به همان نحوی که هست کپی کرده و سپس آنرا تغییر میدهیم:
محتوای فایل notes.component.ts
در اینجا برای نمایش یک mat-table، نیاز به یک منبع داده وجود دارد که روش تعریف آنرا توسط MatTableDataSource از نوع UserNote مشاهده میکنید.
سپس این منبع داده در قسمت ngOnInit بر اساس ورودی آرایهی notes که از کامپوننت main-content مقدار دهی میشود، تامین خواهد شد.
displayedColumns نیز لیست ستونها را مشخص میکند.
محتوای فایل notes.component.html
در اینجا ترتیب ردیفها بر اساس mat-row انتهای جدول مشخص میشود. بنابراین مهم نیست که ng-container matColumnDefها چه ترتیبی دارند.
سپس به ازای هر ستون، یک ng-container اضافه شدهاست. matColumnDef معادل نامهای displayedColumns خواهد بود. matCellDef نیز بر اساس متغیر حلقهای که بر روی منبع داده تشکیل میشود، تعریف خواهد شد. این تعریف امکان دسترسی به مقدار آنرا در ادامه میسر میکند.
در این حالت اگر برنامه را اجرا کنیم، خروجی زیر قابل مشاهده خواهد بود:
افزودن صفحه بندی به mat-table یادداشتهای یک کاربر
اگر مجددا به مستندات mat-table مراجعه کنیم، مثالی در مورد mat-paginator نیز دارد که جهت نمایش رابط کاربری صفحه بندی مورد استفاده قرار میگیرد. بنابراین از مثال آن جهت تکمیل این قسمت ایده میگیریم:
پس از بسته شدن تگ mat-table، کامپوننت mat-paginator به صفحه اضافه میشود که pageSize آن تعداد ردیفهای در هر صفحه را مشخص میکند و pageSizeOptions سبب نمایش یک دراپ داون برای انتخاب تعداد ردیفهای هر صفحه توسط کاربر خواهد شد.
در ادامه به کدهای کامپوننت مراجعه کرده و توسط ViewChild به template reference variable ایی به نام paginator دسترسی پیدا میکنیم:
سپس مطابق مستندات آن، این کامپوننت باید به خاصیت paginator منبع دادهی data table در رخداد ngAfterViewInit، متصل شود.
اکنون اگر برنامه را اجرا کنیم، صفحه بندی فعال شدهاست:
افزودن جستجو و فیلتر کردن اطلاعات به mat-table یادداشتهای یک کاربر
مستندات mat-table به همراه مثال filtering نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
ابتدا فیلد ورود اطلاعات جستجو، پیش از Mat-table به قالب کامپوننت اضافه میشود:
سپس متد applyFilter که به ازای هر keyup فعال میشود، در کدهای کامپوننت به نحو زیر تکمیل خواهد شد:
همین اندازه تنظیم سبب فعالسازی جستجو بر روی جدول میشود:
افزودن مرتب سازی اطلاعات به mat-table یادداشتهای یک کاربر
مستندات mat-table به همراه مثال sorting نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
برای فعالسازی مرتب سازی اطلاعات، در قالب کامپوننت، به mat-table، دایرکتیو matSort و به هر ستونی که نیاز است مرتب سازی شود، دایرکتیو mat-sort-header را به mat-headerها اضافه میکنیم:
در کدهای کامپوننت نیز ابتدا توسط ViewChild به matSort دسترسی پیدا میکنیم و سپس آنرا به خاصیت sort منبع داده در رخداد ngAfterViewInit، متصل خواهیم کرد:
نتیجهی این تغییرات را در تصویر زیر با فعالسازی مرتب سازی بر روی ستون Title مشاهده میکنید:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-04.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
کامپوننت mat-table
کار کامپوننت mat-table نمایش اطلاعات در ردیفها و ستونها است. به همراه آن mat-paginator برای نمایش UI صفحه بندی اطلاعات، دایرکتیو matSort و mat-sort-header برای افزودن رابط کاربری مرتب سازی اطلاعات و امکان تغییر منبع داده آن برای فیلتر کردن دادهها، نیز وجود دارند.
افزودن کامپوننت جدید notes برای نمایش یادداشتهای کاربران
برای نمایش لیست یادداشتهای هر شخص، کامپوننت جدید Notes را به صورت زیر در پوشهی components ایجاد میکنیم:
ng g c contact-manager/components/notes --no-spec
<mat-tab-group> <mat-tab label="Bio"> <p> {{user.bio}} </p> </mat-tab> <mat-tab label="Notes"> <app-notes [notes]="user.userNotes"></app-notes> </mat-tab> </mat-tab-group>
import { Component, Input, OnInit } from "@angular/core"; import { UserNote } from "../../models/user-note"; @Component({ selector: "app-notes", templateUrl: "./notes.component.html", styleUrls: ["./notes.component.css"] }) export class NotesComponent implements OnInit { @Input() notes: UserNote[];
<p> {{notes | json}} </p>
تکمیل کامپوننت Notes توسط یک data table
در ادامه قصد داریم این اطلاعات خام را توسط یک data table نمایش دهیم. به همین جهت ابتدا به مستندات mat-table مراجعه کرده و همانند قبل، مثالی را پیدا میکنیم که به منظور ما نزدیکتر باشد. سپس کدهای آنرا به برنامه اضافه کرده و سفارشی سازی میکنیم. در ابتدا مثال basic آنرا دقیقا به همان نحوی که هست کپی کرده و سپس آنرا تغییر میدهیم:
محتوای فایل notes.component.ts
import { Component, Input, OnInit } from "@angular/core"; import { MatTableDataSource } from "@angular/material"; import { UserNote } from "../../models/user-note"; @Component({ selector: "app-notes", templateUrl: "./notes.component.html", styleUrls: ["./notes.component.css"] }) export class NotesComponent implements OnInit { @Input() notes: UserNote[]; displayedColumns = ["position", "title", "date"]; dataSource: MatTableDataSource<UserNote>; constructor() { } ngOnInit() { this.dataSource = new MatTableDataSource<UserNote>(this.notes); } }
سپس این منبع داده در قسمت ngOnInit بر اساس ورودی آرایهی notes که از کامپوننت main-content مقدار دهی میشود، تامین خواهد شد.
displayedColumns نیز لیست ستونها را مشخص میکند.
محتوای فایل notes.component.html
<div class="example-container mat-elevation-z8" fxLayout="column"> <mat-table #table [dataSource]="dataSource"> <ng-container matColumnDef="position"> <mat-header-cell *matHeaderCellDef> No. </mat-header-cell> <mat-cell *matCellDef="let note"> {{note.id}} </mat-cell> </ng-container> <ng-container matColumnDef="title"> <mat-header-cell *matHeaderCellDef> Title </mat-header-cell> <mat-cell *matCellDef="let note"> {{note.title}} </mat-cell> </ng-container> <ng-container matColumnDef="date"> <mat-header-cell *matHeaderCellDef> Date </mat-header-cell> <mat-cell *matCellDef="let note"> {{note.date | date:'yyyy-MM-dd'}} </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </div>
سپس به ازای هر ستون، یک ng-container اضافه شدهاست. matColumnDef معادل نامهای displayedColumns خواهد بود. matCellDef نیز بر اساس متغیر حلقهای که بر روی منبع داده تشکیل میشود، تعریف خواهد شد. این تعریف امکان دسترسی به مقدار آنرا در ادامه میسر میکند.
در این حالت اگر برنامه را اجرا کنیم، خروجی زیر قابل مشاهده خواهد بود:
افزودن صفحه بندی به mat-table یادداشتهای یک کاربر
اگر مجددا به مستندات mat-table مراجعه کنیم، مثالی در مورد mat-paginator نیز دارد که جهت نمایش رابط کاربری صفحه بندی مورد استفاده قرار میگیرد. بنابراین از مثال آن جهت تکمیل این قسمت ایده میگیریم:
</mat-table> <mat-paginator #paginator [pageSize]="2" [pageSizeOptions]="[2, 4, 6]"> </mat-paginator> </div>
در ادامه به کدهای کامپوننت مراجعه کرده و توسط ViewChild به template reference variable ایی به نام paginator دسترسی پیدا میکنیم:
export class NotesComponent implements OnInit, AfterViewInit { dataSource: MatTableDataSource<UserNote>; @ViewChild(MatPaginator) paginator: MatPaginator; ngAfterViewInit() { this.dataSource.paginator = this.paginator; } }
اکنون اگر برنامه را اجرا کنیم، صفحه بندی فعال شدهاست:
افزودن جستجو و فیلتر کردن اطلاعات به mat-table یادداشتهای یک کاربر
مستندات mat-table به همراه مثال filtering نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
ابتدا فیلد ورود اطلاعات جستجو، پیش از Mat-table به قالب کامپوننت اضافه میشود:
<div class="example-container mat-elevation-z8" fxLayout="column"> <div class="example-header"> <mat-form-field> <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter"> </mat-form-field> </div>
applyFilter(filterValue: string) { this.dataSource.filter = filterValue.trim().toLowerCase(); // MatTableDataSource defaults to lowercase matches }
افزودن مرتب سازی اطلاعات به mat-table یادداشتهای یک کاربر
مستندات mat-table به همراه مثال sorting نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
برای فعالسازی مرتب سازی اطلاعات، در قالب کامپوننت، به mat-table، دایرکتیو matSort و به هر ستونی که نیاز است مرتب سازی شود، دایرکتیو mat-sort-header را به mat-headerها اضافه میکنیم:
<mat-table #table [dataSource]="dataSource" matSort> <ng-container matColumnDef="position"> <mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell>
export class NotesComponent implements OnInit, AfterViewInit { dataSource: MatTableDataSource<UserNote>; @ViewChild(MatSort) sort: MatSort; ngAfterViewInit() { this.dataSource.sort = this.sort; } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-04.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
در نگاه اول این ترکیب ممکن است کمی گیجکننده باشد ولی بعد از مدتی کار با React به قدرت آن پی خواهید برد. در واقع درج کدهای JSX درون کامپوننتها یک مزیت محسوب میشود زیرا پیمایش بین کدها را راحتتر و همچنین نوشتن و دیباگ کامپوننتها را سادهتر خواهد کرد. از آنجائیکه تمام قابلیتها درون یک فایل تعریف میشوند دیگر نیاز نخواهد بود زمان زیادی را برای سوئیچ کردن بین فایلهای جدا (تمپلیت و...) هدر دهید. همچنین جداسازی فایلهای JSX از کامپوننت به معنای پیروی از اصل Separation Of Concern نیست چون مجموع فایلها در نهایت مختص به یک کامپوننت هستند (به عنوان مثال نمیتوان تمپلیت را برای یک کامپوننت دیگر استفاده کرد و...)
ضمناً برای اینکه حجم کدهایتان بعد از مدتی زیاد نشود بهتر است کامپوننتهایی که جزئیات زیادی دارند را به Child Componentهای کوچکتری تقسیم کنید (مانند مثال طراحی گرید این سری).
پس از بررسی روش تعیین نوعهای خواص props در قسمتهای قبل، اکنون نوبت به بررسی روش تعیین نوعهای انواع React Hooks است. در این قسمت دو هوک پرکاربرد useState و useRef را بررسی میکنیم.
روش تعیین نوع useState Hook
برای این منظور در ابتدا فایل جدید src\components\Input.tsx را ایجاد کرده و به صورت زیر تکمیل میکنیم:
همچنین تعریف المان آنرا نیز به فایل src\App.tsx جهت نمایش </ Input> با ذکر "import { Input } from "./components/Input، انجام میدهیم.
پس از این تعاریف ... برنامه بدون مشکل کار میکند و کامپایل میشود. اکنون سؤال اینجا است که آیا تایپاسکریپت در ایجا اصلا کاری را هم انجام میدهد؟ برای درک این موضوع، سطر useState را به صورت زیر تغییر میدهیم:
بلافاصله خطای زیر ظاهر میشود:
مفهوم Type Inference را در تصاویر زیر بهتر میتوان مشاهده کرد. اشارهگر ماوس را به تعریف useState نزدیک کنید. در توضیحاتی که ظاهر میشود، بر اساس نوع مقدار پیشفرض آن، نوع آرگومان جنریک متد useState نیز به صورت خودکار تغییر میکند:
و نکتهی مهم اینجا است که نیازی به ذکر صریح این نوع جنریک، مانند مثال زیر نیست:
و سطر فوق با سطر زیر که بیانگر Type Inference است، دقیقا یکی است:
سؤال: اگر مقدار اولیهی useState را null تعیین کردیم و یا اصلا تعریف نکردیم و undefined بود، چطور؟
در یک چنین حالتی که نوع دقیق state، از طریق مقدار اولیهی آن قابل استنتاج نیست، نیاز هست همانند تصاویر فوق، تعریف جنریک useState را به نحو صریحی ذکر کرده و آنرا با union types تکمیل کنیم:
به این ترتیب عنوان کردهایم که نوع name در اینجا میتواند رشتهای و یا نال باشد.
روش تعیین نوع useRef Hook
در ادامه میخواهیم نحوهی تعیین نوع DOM Elements را در React بررسی کنیم. با استفاده از useRef میتوان به ارجاعی از یک DOM Element دسترسی یافت.
برای اینکار ابتدا useRef را با یک مقدار اولیهی null، توسط ویژگی ref، به یک DOM Element خاص متصل میکنیم. تا اینجا برنامه بدون مشکل کار میکند؛ اما زمانیکه خواستیم برای مثال به inputRef.current.value دسترسی پیدا کنیم، دیگر تعریف سادهی useRef(null) پاسخگو نبوده و خطای زیر گزارش میشود:
عنوان میکند نوعی که inputRef.current دارد، نال است و نال به همراه خاصیت value نیست. برای اینکه نوع inputRef را بهتر بتوانیم بررسی کنیم، دقیقا بر روی آن کلیک راست کرده و گزینهی Go to Type Definition را انتخاب کنید. بلافاصله به تعریف زیر هدایت خواهیم شد:
inputRef، از نوع MutableRefObject جنریک است که تنها دارای یک خاصیت current است. نوع T آن هم در اینجا با توجه به مقدار اولیهی آن، null درنظر گرفته شدهاست. به همین جهت هرچند میدانیم inputRef.current به المان input صفحه اشاره میکند، اما نمیتوانیم به خواص و متدهای آن دسترسی پیدا کنیم.
برای رفع این مشکل فقط کافی است نوع المان مدنظر را صریحا به عنوان آرگومان جنریک useRef معرفی کنیم:
نحوهی تشخیص این نوع هم سادهاست. فقط کافی است اشارهگر ماوس را بر روی المانی خاص قرار دهید. بلافاصله نوع آن مشخص میشود:
فعال بودن Strict Null Checking در پروژههای TypeScript ای React
نکات مطلب «نوعهای نال نپذیر در TypeScript» به صورت پیشفرض در پروژههای تایپ اسکریپتی React هم فعال هستند؛ چون پرچم strict واقع در فایل tsconfig.json پروژه، به صورت پیشفرض به true تنظیم شدهاست و این پرچم، Strict Null Checking را نیز شامل میشود. برای آزمایش فعال بودن آن، کدهای فوق را به صورت زیر تغییر دهید تا صرفا if آن حذف شود:
بلافاصله خطای امکان نال بودن inputRef.current در اولین بار رندر کامپوننت جاری را دریافت خواهید کرد:
روش بررسی if (inputRef && inputRef.current) معادل سادهتری را نیز در TypeScript 3.7 به بعد دارد که به Optional Chaining معروف است؛ به صورت زیر:
در این حالت دیگر نیازی به ذکر if یاد شده نیست و وجود .? به معنای ادامهی این زنجیره، در صورت نال نبودن قطعهی قبلی است.
روش تعیین نوع useState Hook
برای این منظور در ابتدا فایل جدید src\components\Input.tsx را ایجاد کرده و به صورت زیر تکمیل میکنیم:
import React, { useState } from "react"; export const Input = () => { const [name, setName] = useState(""); return <input value={name} onChange={(e) => setName(e.target.value)} />; };
پس از این تعاریف ... برنامه بدون مشکل کار میکند و کامپایل میشود. اکنون سؤال اینجا است که آیا تایپاسکریپت در ایجا اصلا کاری را هم انجام میدهد؟ برای درک این موضوع، سطر useState را به صورت زیر تغییر میدهیم:
const [name, setName] = useState(0);
عنوان میکند که مقدار رشتهای e.target.value، به مقدار عددی name قابل انتساب نیست. به عبارتی TypeScript از قابلیت Type Inference خود در اینجا استفاده میکند. درست است که به ظاهر نوعی را برای useState و خروجی منتسب به آن تعیین نکردهایم، اما بر اساس نوع مقدار پیشفرض آن، نوع name و setName به صورت خودکار مشخص میشوند و نیازی به ذکر صریح این نوع، نیست. برای مثال در حالت اول چون مقدار پیشفرض useState را یک رشتهی خالی معرفی کرده بودیم، نوع name نیز string درنظر گرفته شده بود. در حالت دوم بر اساس مقدار پیشفرض عددی useState، اینبار نوع name نیز یک number خواهد بود و دیگر نمیتوان یک مقدار رشتهای را مانند e.target.value به آن انتساب داد. مزیت کار کردن با TypeScript در اینجا، مشاهدهی آنی خطای یک چنین استفادهها و انتسابهای نادرستی است.
مفهوم Type Inference را در تصاویر زیر بهتر میتوان مشاهده کرد. اشارهگر ماوس را به تعریف useState نزدیک کنید. در توضیحاتی که ظاهر میشود، بر اساس نوع مقدار پیشفرض آن، نوع آرگومان جنریک متد useState نیز به صورت خودکار تغییر میکند:
و نکتهی مهم اینجا است که نیازی به ذکر صریح این نوع جنریک، مانند مثال زیر نیست:
const [name, setName] = useState<string>("");
const [name, setName] = useState("");
سؤال: اگر مقدار اولیهی useState را null تعیین کردیم و یا اصلا تعریف نکردیم و undefined بود، چطور؟
در یک چنین حالتی که نوع دقیق state، از طریق مقدار اولیهی آن قابل استنتاج نیست، نیاز هست همانند تصاویر فوق، تعریف جنریک useState را به نحو صریحی ذکر کرده و آنرا با union types تکمیل کنیم:
const [name, setName] = useState<string | null>(null);
روش تعیین نوع useRef Hook
در ادامه میخواهیم نحوهی تعیین نوع DOM Elements را در React بررسی کنیم. با استفاده از useRef میتوان به ارجاعی از یک DOM Element دسترسی یافت.
import React, { useRef, useState } from "react"; export const Input = () => { const [name, setName] = useState(""); const inputRef = useRef(null); if (inputRef && inputRef.current) { console.log("ref", inputRef.current.value); } return ( <input ref={inputRef} value={name} onChange={(e) => setName(e.target.value)} /> ); };
عنوان میکند نوعی که inputRef.current دارد، نال است و نال به همراه خاصیت value نیست. برای اینکه نوع inputRef را بهتر بتوانیم بررسی کنیم، دقیقا بر روی آن کلیک راست کرده و گزینهی Go to Type Definition را انتخاب کنید. بلافاصله به تعریف زیر هدایت خواهیم شد:
interface MutableRefObject<T> { current: T; }
برای رفع این مشکل فقط کافی است نوع المان مدنظر را صریحا به عنوان آرگومان جنریک useRef معرفی کنیم:
const inputRef = useRef<HTMLInputElement>(null);
فعال بودن Strict Null Checking در پروژههای TypeScript ای React
نکات مطلب «نوعهای نال نپذیر در TypeScript» به صورت پیشفرض در پروژههای تایپ اسکریپتی React هم فعال هستند؛ چون پرچم strict واقع در فایل tsconfig.json پروژه، به صورت پیشفرض به true تنظیم شدهاست و این پرچم، Strict Null Checking را نیز شامل میشود. برای آزمایش فعال بودن آن، کدهای فوق را به صورت زیر تغییر دهید تا صرفا if آن حذف شود:
//if (inputRef && inputRef.current) { console.log("ref", inputRef.current.value); //}
که برای رفع آن، همانند قبل باید ذکر if بررسی نال بودن inputRef و خاصیت current آنرا اضافه کرد؛ تا دیگر در زمان اجرای برنامه، با شانس نال بودن یکی از ایندو مواجه نشویم و به کیفیت بالاتری در برنامهی خود برسیم.
روش بررسی if (inputRef && inputRef.current) معادل سادهتری را نیز در TypeScript 3.7 به بعد دارد که به Optional Chaining معروف است؛ به صورت زیر:
console.log("ref", inputRef?.current?.value);
نحوه معرفی متغیرهای محلی در Reflection.Emit
ابتدا مثال کامل ذیل را درنظر بگیرید:
در این مثال سعی کردهایم معادل متد Calculate را که در ابتدای برنامه ملاحظه میکنید، با کدهای IL تولید کنیم. روش کار مانند قسمت قبل است. ابتدا وهلهی جدیدی را از کلاس DynamicMethod جهت معرفی امضای متد پویای خود ایجاد میکنیم. در اینجا نوع خروجی را int و نوع سه پارامتر آنرا به نحوی که مشخص شده است توسط آرایهای از typeهای int معرفی خواهیم کرد. سپس محل قرارگیری کد تولیدی پویا مشخص میشود.
در ادامه توسط ILGenerator، آرگومانهای دریافتی بارگذاری شده، در هم ضرب میشوند. سپس نتیجه در یک متغیر محلی ذخیره شده و سپس از آرگومان سوم کسر میگردد. در آخر هم این نتیجه بازگشت داده خواهد شد.
در اینجا روش سومی را برای کار با متدهای پویا مشاهده میکنید. بجای تعریف یک delegate به صورت صریح همانند قسمت قبل، از یک Func یا حتی Action نیز بنابر امضای متد مد نظر، میتوان استفاده کرد. در اینجا از یک Func که سه پارامتر int را قبول کرده و خروجی int نیز دارد، استفاده شده است.
اگر برنامه را اجرا کنید ... کرش خواهد کرد! با استثنای ذیل:
علت اینجا است که در حین کار با System.Reflection.Emit، نیاز است نوع متغیر محلی مورد استفاده را نیز مشخص نمائیم. اینکار را توسط فراخوانی متد DeclareLocal که باید پس از فراخوانی GetILGenerator، درج گردد، میتوان انجام داد:
با این تغییر، برنامه بدون مشکل اجرا خواهد شد.
نحوه تعریف برچسبها در Reflection.Emit
در ادامه قصد داریم یک مثال پیشرفتهتر را بررسی کنیم.
در اینجا میخواهیم کدهای معادل متد محاسباتی فوق را توسط امکانات System.Reflection.Emit و کدهای IL تولید کنیم.
کد کامل معادل را به همراه کامنت گذاری سطر به سطر آن، ملاحظه میکنید. در اینجا نکتههای جدید، نحوه تعریف برچسبها و انتقال کنترل برنامه به آنها هستند؛ جهت شبیه سازی حلقه و همچنین خاتمه آن و انتقال کنترل به انتهای متد.
فراخوانی متدها توسط کدهای پویای Reflection.Emit
در ادامه کدهای کامل یک مثال متد پویا را که متد print را فراخوانی میکند، ملاحظه میکنید:
در اینجا از OpCode مخصوص فراخوانی متدها به نام Call که در قسمتهای قبل در مورد آن بحث شد، استفاده گردیده است. برای اینکه امضای دقیقی را در اختیار آن قرار دهیم، میتوان از Reflection استفاده کرد که نمونهای از آنرا در اینجا ملاحظه میکنید.
به علاوه چون خروجی امضای متد ما از نوع void است، اینبار delegate تعریف شده را از نوع Action تعریف کردهایم و نه از نوع Func.
فراخوانی متدهای پویای Reflection.Emit توسط سایر متدهای پویای Reflection.Emit
فراخوانی یک متد پویای مشخص از طریق متدهای پویای دیگر نیز همانند مثال قبل است:
در مثال فوق ابتدا یک متد پویای ضرب را تعریف کردهایم که عددی صحیح را دریافت و آنرا در 42 ضرب میکند و نتیجه را بازگشت میدهد.
سپس متد پویای دومی تعریف شده است که دو عدد صحیح را دریافت و این دو را در هم ضرب کرده و سپس نتیجه را به عنوان پارامتر به متد پویای اول ارسال میکند.
هنگام فراخوانی OpCodes.Call، پارامتر دوم باید از نوع MethodInfo باشد. نوع یک DynamicMethod نیز همان MethodInfo است. بنابراین برای فراخوانی آن، کار خاصی نباید انجام شود و صرفا ذکر نام متغیر مرتبط با مد پویای مدنظر کفایت میکند.
ابتدا مثال کامل ذیل را درنظر بگیرید:
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static int Calculate(int a, int b, int c) { var result = a * b; return result - c; } static void Main(string[] args) { //روش متداول Console.WriteLine(Calculate(10, 2, 3)); //تعریف امضای متد var myMethod = new DynamicMethod( name: "CalculateMethod", returnType: typeof(int), parameterTypes: new[] { typeof(int), typeof(int), typeof(int) }, m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); il.Emit(opcode: OpCodes.Ldarg_0); // بارگذاری اولین آرگومان بر روی پشته ارزیابی il.Emit(opcode: OpCodes.Ldarg_1); // بارگذاری دومین آرگومان بر روی پشته ارزیابی il.Emit(opcode: OpCodes.Mul); // انجام عملیات ضرب il.Emit(opcode: OpCodes.Stloc_0); // ذخیره سازی نتیجه عملیات ضرب در یک متغیر محلی il.Emit(opcode: OpCodes.Ldloc_0); // متغیر محلی را بر روی پشته ارزیابی قرار میدهد تا در عملیات بعدی قابل استفاده باشد il.Emit(opcode: OpCodes.Ldarg_2); // آرگومان سوم را بر روی پشته ارزیابی قرار میدهد il.Emit(opcode: OpCodes.Sub); // انجام عملیات تفریق il.Emit(opcode: OpCodes.Ret); // بازگشت نتیجه //فراخوانی متد پویا var method = (Func<int, int, int, int>)myMethod.CreateDelegate(typeof(Func<int, int, int, int>)); Console.WriteLine(method(10, 2, 3)); } } }
در ادامه توسط ILGenerator، آرگومانهای دریافتی بارگذاری شده، در هم ضرب میشوند. سپس نتیجه در یک متغیر محلی ذخیره شده و سپس از آرگومان سوم کسر میگردد. در آخر هم این نتیجه بازگشت داده خواهد شد.
در اینجا روش سومی را برای کار با متدهای پویا مشاهده میکنید. بجای تعریف یک delegate به صورت صریح همانند قسمت قبل، از یک Func یا حتی Action نیز بنابر امضای متد مد نظر، میتوان استفاده کرد. در اینجا از یک Func که سه پارامتر int را قبول کرده و خروجی int نیز دارد، استفاده شده است.
اگر برنامه را اجرا کنید ... کرش خواهد کرد! با استثنای ذیل:
System.InvalidProgramException was unhandled Message=Common Language Runtime detected an invalid program.
il.DeclareLocal(typeof(int));
نحوه تعریف برچسبها در Reflection.Emit
در ادامه قصد داریم یک مثال پیشرفتهتر را بررسی کنیم.
static int Calculate(int x) { int result = 0; for (int i = 0; i < 10; i++) { result += i * x; } return result; }
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static int Calculate(int x) { int result = 0; for (int i = 0; i < 10; i++) { result += i * x; } return result; } static void Main(string[] args) { //روش متداول Console.WriteLine(Calculate(10)); //تعریف امضای متد var myMethod = new DynamicMethod( name: "CalculateMethod", returnType: typeof(int), // خروجی متد عدد صحیح است parameterTypes: new[] { typeof(int) }, // یک پارامتر عدد صحیح دارد m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); // از برچسبها برای انتقال کنترل استفاده میشود // در اینجا به دو برچسب برای تعریف ابتدای حلقه // و همچنین برای پرش به جایی که متد خاتمه مییابد نیاز داریم var loopStart = il.DefineLabel(); var methodEnd = il.DefineLabel(); // variable 0; result = 0 il.DeclareLocal(typeof(int)); // برای تعریف متغیر محلی نتیجه عملیات il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار میدهد il.Emit(OpCodes.Stloc_0); // و نهایتا این عدد ثابت به متغیر محلی انتساب داده خواهد شد // variable 1; i = 0 il.DeclareLocal(typeof(int)); // در اینجا کار تعریف و مقدار دهی متغیر حلقه انجام میشود il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار میدهد il.Emit(OpCodes.Stloc_1); // و نهایتا این عدد ثابت به متغیر حلقه در ایندکس یک انتساب داده خواهد شد // در اینجا کار تعریف بدنه حلقه شروع میشود il.MarkLabel(loopStart); // شروع حلقه را علامتگذاری میکنیم تا بعدا بتوانیم به این نقطه پرش نمائیم il.Emit(OpCodes.Ldloc_1); // در ادامه میخواهیم بررسی کنیم که آیا مقدار متغیر حلقه از عدد 10 کوچکتر است یا خیر il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت ده را بر روی پشته ارزیابی قرار میدهد // برای انجام بررسیهای تساوی یا کوچکتر یا بزرگتر نیاز است ابتدا دو متغیر مدنظر بر روی پشته قرار گیرند il.Emit(OpCodes.Bge, methodEnd); // اگر اینطور نیست و مقدار متغیر از 10 کمتر نیست، کنترل برنامه را به انتهای متد هدایت خواهیم کرد // i * x il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه را بر روی پشته قرار میدهد il.Emit(OpCodes.Ldarg_0); // مقدار اولین آرگومان متد را بر روی پشته قرار میدهد il.Emit(OpCodes.Mul); // انجام عملیات ضرب // نتیجه این عملیات اکنون بر روی پشته قرار گرفته است // result += il.Emit(OpCodes.Ldloc_0); // متغیر نتیجه را بر روی پشته قرار میدهد il.Emit(OpCodes.Add); // اکنون عملیات جمع بر روی نتیجه ضرب قسمت قبل که بر روی پشته قرار دارد و همچنین متغیر نتیجه انجام میشود il.Emit(OpCodes.Stloc_0); // ذخیره سازی نتیجه در متغیر محلی // i++ // در اینجا کار افزایش متغیر حلقه انجام میشود il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه بر روی پشته قرار میگیرد il.Emit(OpCodes.Ldc_I4_1); // عدد ثابت یک بر روی پشته قرار میگیرد il.Emit(OpCodes.Add); // سپس این دو عدد بارگذاری شده با هم جمع خواهند شد il.Emit(OpCodes.Stloc_1); // نتیجه در متغیر حلقه ذخیره خواهد شد // مرحله بعد شبیه سازی حلقه با پرش به ابتدای برچسب آن است il.Emit(OpCodes.Br, loopStart); //در اینجا انتهای متد علامتگذاری شده است il.MarkLabel(methodEnd); il.Emit(OpCodes.Ldloc_0); // مقدار نتیجه بر روی پشته قرار داده شده il.Emit(OpCodes.Ret); // و بازگشت داده میشود //فراخوانی متد پویا var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>)); Console.WriteLine(method(10)); } } }
فراخوانی متدها توسط کدهای پویای Reflection.Emit
در ادامه کدهای کامل یک مثال متد پویا را که متد print را فراخوانی میکند، ملاحظه میکنید:
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { public static void print(int i) { Console.WriteLine("i: {0}", i); } static void Main(string[] args) { //روش متداول print(10); //تعریف امضای متد var myMethod = new DynamicMethod( name: "myMethod", returnType: typeof(void), parameterTypes: null, // پارامتری ندارد m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت 10 را بر روی پشته قرار میدهد // اکنون این مقدار بر روی پشته است و از آن میتوان برای فراخوانی متد پرینت استفاده کرد il.Emit(OpCodes.Call, typeof(Program).GetMethod("print")); il.Emit(OpCodes.Ret); //فراخوانی متد پویا var method = (Action)myMethod.CreateDelegate(typeof(Action)); method(); } } }
به علاوه چون خروجی امضای متد ما از نوع void است، اینبار delegate تعریف شده را از نوع Action تعریف کردهایم و نه از نوع Func.
فراخوانی متدهای پویای Reflection.Emit توسط سایر متدهای پویای Reflection.Emit
فراخوانی یک متد پویای مشخص از طریق متدهای پویای دیگر نیز همانند مثال قبل است:
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static void Main(string[] args) { //تعریف امضای متد var myMethod = new DynamicMethod( name: "mulMethod", returnType: typeof(int), parameterTypes: new[] { typeof(int) }, m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // اولین آرگومان متد را بر روی پشته قرار میدهد il.Emit(OpCodes.Ldc_I4, 42); // عدد ثابت 42 را بر روی پشته قرار میدهد il.Emit(OpCodes.Mul); // ضرب این دو در هم il.Emit(OpCodes.Ret); // بازگشت نتیجه //فراخوانی متد پویا var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>)); Console.WriteLine(method(10)); // فراخوانی متد پویای فوق در یک متد پویای دیگر var callerMethod = new DynamicMethod( name: "callerMethod", returnType: typeof(int), parameterTypes: new[] { typeof(int), typeof(int) }, m: typeof(Program).Module); //تعریف بدنه متد var callerMethodIL = callerMethod.GetILGenerator(); callerMethodIL.Emit(OpCodes.Ldarg_0); // پارامتر اول متد را بر روی پشته قرار میدهد callerMethodIL.Emit(OpCodes.Ldarg_1); // پارامتر دوم متد را بر روی پشته قرار میدهد callerMethodIL.Emit(OpCodes.Mul); // ضرب این دو در هم //حاصل ضرب اکنون بر روی پشته است که در فراخوانی بعدی استفاده میشود callerMethodIL.Emit(OpCodes.Call, myMethod); // فراخوانی یک متد پویای دیگر callerMethodIL.Emit(OpCodes.Ret); //فراخوانی متد پویای جدید var method2 = (Func<int, int, int>)callerMethod.CreateDelegate(typeof(Func<int, int, int>)); Console.WriteLine(method2(10, 2)); } } }
سپس متد پویای دومی تعریف شده است که دو عدد صحیح را دریافت و این دو را در هم ضرب کرده و سپس نتیجه را به عنوان پارامتر به متد پویای اول ارسال میکند.
هنگام فراخوانی OpCodes.Call، پارامتر دوم باید از نوع MethodInfo باشد. نوع یک DynamicMethod نیز همان MethodInfo است. بنابراین برای فراخوانی آن، کار خاصی نباید انجام شود و صرفا ذکر نام متغیر مرتبط با مد پویای مدنظر کفایت میکند.
در سایت جاری از ویرایشگر آنلاین RedActor استفاده شده و کار کردن با آن هم بسیار ساده است:
یک TextArea ساده را به صفحه اضافه کرده و این افزونه جیکوئری را بر روی آن اجرا میکنید. به این ترتیب TextArea به صورت خودکار تبدیل به یک ویرایشگر مطلوب خواهد شد. برای مثال:
اما فعال سازی قسمت ارسال فایل و تصویر همراه با آن چطور؟
یک سری مثال نوشته شده با PHP به همراه این ویرایشگر آنلاین هستند که برای ایده گرفتن بد نیستند (البته به این معنا نیست که این ویرایشگر نیازی به PHP دارد. تنها قسمت سمت سرور مثالهای آن با PHP است). برای مثال اگر در PHP از دستور echo برای ارائه یک نتیجه نهایی به ویرایشگر RedActor استفاده شده، معادل آن در ASP.NET MVC مساوی return Content است.
برای فعال سازی قسمت آپلود این ادیتور نیاز است پارامترهای imageUpload، fileUpload و linkFileUpload مقدار دهی شوند.
همانطور که ملاحظه میکنید از T4MVC برای مشخص سازی مسیرها استفاده شده. برای مثال MVC.RedactorUpload.ImageUpload به این معنا است که در کنترلری به نام RedactorUpload، اکشن متدی به نام ImageUpload پذیرای ارسال فایل ادیتور خواهد بود و به همین ترتیب در مورد سایر پارامترها.
RedactorUploadController هم ساختار بسیار سادهای دارد. برای مثال هر کدام از متدهای آپلود یاد شده یک چنین امضایی دارند:
البته در مورد مسایل امنیتی آپلود هم پیشتر در سایت بحث شده است. برای مثال در اینجا استفاده از فیلتر زیر را فراموش نکنید:
در هر کدام از متدهای آپلود (به سه متد برای سه پارامتر یاد شده نیاز است)، ابتدا HttpPostedFileBase را در پوشهایی که مدنظر دارید ذخیره کنید. سپس باید محتوایی را به RedActor بازگشت دهید و اصل کار یکپارچگی با ASP.NET MVC نیز در همینجا است:
در حالت imageUpload، محتوایی به شکل زیر باید بازگشت داده شود:
در حالت fileUpload، پس از ذخیره سازی فایل در سرور، مسیر آن باید به نحو زیر بازگشت داده شود:
و در حالت linkFileUpload فقط باید مسیر نهایی فایل ذخیره شده بر روی سرور را بازگشت دهید:
همچنین باید دقت داشت که کار ارسال فایل به سرور توسط خود افزونه RedActor انجام میشود و نیازی به کدنویسی ندارد. فقط باید سمت سرور آنرا به نحوی که عنوان شد مدیریت کنید. ابتدا فایل را در سرور ذخیره کنید. سپس باید یک محتوای رشتهای را به نحو یاد شده، ساخت و توسط return Content بازگشت داد.
پ.ن.
قسمتی از مطالب متن فوق در نگارش جدید این ویرایشگر به نحو زیر تغییر کرده است.
یک TextArea ساده را به صفحه اضافه کرده و این افزونه جیکوئری را بر روی آن اجرا میکنید. به این ترتیب TextArea به صورت خودکار تبدیل به یک ویرایشگر مطلوب خواهد شد. برای مثال:
@Html.TextAreaFor(model => model.ArticleBody, htmlAttributes: new { style = "width:98%; height:500px" }) <script type="text/javascript"> $('#ArticleBody').redactor({ autoformat: false, convertDivs: false }); </script>
یک سری مثال نوشته شده با PHP به همراه این ویرایشگر آنلاین هستند که برای ایده گرفتن بد نیستند (البته به این معنا نیست که این ویرایشگر نیازی به PHP دارد. تنها قسمت سمت سرور مثالهای آن با PHP است). برای مثال اگر در PHP از دستور echo برای ارائه یک نتیجه نهایی به ویرایشگر RedActor استفاده شده، معادل آن در ASP.NET MVC مساوی return Content است.
<script type="text/javascript"> $('#ArticleBody').redactor({ imageUpload: "@Url.Action(result: MVC.RedactorUpload.ImageUpload())", fileUpload: "@Url.Action(result: MVC.RedactorUpload.FileUpload())", linkFileUpload: "@Url.Action(result: MVC.RedactorUpload.FileLinkUpload())" , autoformat: false , convertDivs: false }); </script>
همانطور که ملاحظه میکنید از T4MVC برای مشخص سازی مسیرها استفاده شده. برای مثال MVC.RedactorUpload.ImageUpload به این معنا است که در کنترلری به نام RedactorUpload، اکشن متدی به نام ImageUpload پذیرای ارسال فایل ادیتور خواهد بود و به همین ترتیب در مورد سایر پارامترها.
RedactorUploadController هم ساختار بسیار سادهای دارد. برای مثال هر کدام از متدهای آپلود یاد شده یک چنین امضایی دارند:
[HttpPost] public virtual ActionResult ImageUpload(HttpPostedFileBase file) { }
[AllowUploadSpecialFilesOnly(".jpg,.gif,.png")]
در حالت imageUpload، محتوایی به شکل زیر باید بازگشت داده شود:
return Content("<img src='" + path + "' />");
return Content("<a href=" + path + ">" + someName + "</a>");
return Content(path);
همچنین باید دقت داشت که کار ارسال فایل به سرور توسط خود افزونه RedActor انجام میشود و نیازی به کدنویسی ندارد. فقط باید سمت سرور آنرا به نحوی که عنوان شد مدیریت کنید. ابتدا فایل را در سرور ذخیره کنید. سپس باید یک محتوای رشتهای را به نحو یاد شده، ساخت و توسط return Content بازگشت داد.
پ.ن.
قسمتی از مطالب متن فوق در نگارش جدید این ویرایشگر به نحو زیر تغییر کرده است.
بازخوردهای دوره
مدیریت تغییرات گریدی از اطلاعات به کمک استفاده از الگوی واحد کار مشترک بین ViewModel و لایه سرویس
در این فریمورک جهت نمایش پیغام به کاربر کلاس SendMsg تدارک دیده شده است. نحوه استفاده از آن به شکل زیر است:
ابتدا در کلاس AddNewUserViewModel یک فیلد خصوصی از نوع کلاس SendMsg ایجاد کنید
سپس در متد حذف، تابع ShowMsg آن را فراخوانی کنید
ابتدا در کلاس AddNewUserViewModel یک فیلد خصوصی از نوع کلاس SendMsg ایجاد کنید
private SendMsg _sendMsg = new SendMsg();
private void doDelete() { _sendMsg.ShowMsg(new AlertConfirmBoxModel { Errors = new List<string> { "آیا کاربر انتخاب شده حذف شود؟" }, ShowConfirm = Visibility.Visible, ShowCancel = Visibility.Visible }, confirmed: input => delete(input)); } private void delete(AlertConfirmBoxModel input) { UsersList.Remove(SelectedItem); }