پراپرتی سفارشی در EF Database First
میخواهیم از یک لیست در گزارش خود استفاده کنیم؛ بطور مثال وقتی در LINQ از دستور ToList استفاده میکنیم و میخواهیم آنرا بصورت مستقیم به Stimul بفرستیم. فرض بر این است که شما DLLهای Stimul را به پروژه اضافه کرده اید و آماده گزارشگیری هستید.
مثلا مدلی در Entity FrameWork با نام base_CenterType
public class base_CenterType { public int ID { get; set; } public string Title { get; set; } public string Dsc { get; set; } }
و متدی بصورت ذیل:
public IList<base_CenterType> GetAll() { return _base_CenterType.ToList(); }
طراحی گزارش برای این لیست به این صورت است:
1- اضافه کردن StiWebReport به فرم به نام StiWebReport1
2- با کلیک بر روی فلش سمت راست و بالای StiWebReport1 و انتخاب Design Report، وارد قسمت طراحی میشویم:
3- با راست کلیک بر روی Business Object و انتخاب New Business Object پنجره مربوطه باز میشود:
4- بعد از زدن OK پنجره زیر باز خواهد شد که باید در کادر Name نام Business Object را انتخاب کنیم که برای خوانایی بهتر است همان نام کلاس را برای آن انتخاب کنیم. چون Category نداریم پس باید کادر آن خالی بماند.
در قسمت Columns باید ستونهای هم نام و هم نوع با خواص کلاس base_CenterType را ایجاد کنیم.
و نهایتا Business Objectی به نام base_CenterType با سه ستون ایجاد خواهد شد.
حال میتوانید ستونهای مورد نظر را در گزارش بکار ببرید.
با فرض اینکه گزارش را طراحی کرده و آنرا در ریشه درایو C ذخیره کردهاید، از قطعه کد زیر برای ارسال لیست به گزارش و نمایش آن استفاده میکنیم.
StiReport mainreport = new StiReport(); mainreport.RegBusinessObject("base_CenterType", base_CenterTypeService.GetAll()); mainreport.Load("C:\\StiWebReport2.mrt"); mainreport.Show();
تا اینجا در قسمت سوم، نحوهی قراردادن یک کامپوننت را در کامپوننتی دیگر، توسط مقدار دهی خاصیت directives مزین کنندهی Component بررسی کردیم. همینقدر که یک کامپوننت دارای selector باشد، قابلیت قرارگرفتن در یک کامپوننت دیگر را دارد. اما چگونه باید بین این کامپوننتها ارتباط برقرار کرد؟
تهیه کامپوننت نمایش ستارهای امتیازهای محصولات
مثال نمایش لیست محصولات سری جاری، دارای ستون «5Star Rating» است. در این قسمت میخواهیم بجای نمایش عددی این امتیازها، کامپوننتی را طراحی کنیم که نماش ستارهای آنها را سبب شود. این کامپوننت باید بتواند یک مقدار ورودی، یا همان عدد امتیاز محصول را از کامپوننت دربرگیرندهی آن دریافت کند. همچنین میخواهیم اگر کاربر بر روی این ستارهها کلیک کرد، کامپوننت در برگیرنده را نیز مطلع سازیم.
در این مثال در فایل product-list.component.html چنین سطری تعریف شدهاست:
<td>{{ product.starRating }}</td>
با توجه به اینکه کامپوننت نمایش ستارهای امتیازها، قابلیت استفادهی مجدد را دارد و الزامی ندارد که حتما در لیست محصولات، بکار گرفته شود، بهتر است محل تعریف آنرا به خارج از پوشهی products فعلی منتقل کنیم. برای مثال میتوان پوشهی app\shared را برای آن و تمامی کامپوننتهای با قابلیت استفادهی مجدد ایجاد کرد.
برای شروع، فایل جدید App\shared\star.component.ts را اضافه کنید؛ با کدهای کامل ذیل:
import { Component, OnChanges, Input, Output, EventEmitter } from 'angular2/core'; @Component({ selector: 'ai-star', templateUrl: 'app/shared/star.component.html', styleUrls: ['app/shared/star.component.css'] }) export class StarComponent implements OnChanges { @Input() rating: number; starWidth: number; @Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>(); ngOnChanges(): void { this.starWidth = this.rating * 86 / 5; } onClick() { this.ratingClicked.emit(`The rating ${this.rating} was clicked!`); } }
سپس مسیر template و مسیر فایل css ویژهی آن، در تزئین کنندهی Component مشخص شدهاند. محتوای کامل این دو فایل را در ذیل مشاهده میکنید:
الف) محتوای فایل App\shared\star.component.html
<div class="crop" [style.width.px]="starWidth" [title]="rating" (click)='onClick()'> <div style="width: 86px"> <span class="glyphicon glyphicon-star"></span> <span class="glyphicon glyphicon-star"></span> <span class="glyphicon glyphicon-star"></span> <span class="glyphicon glyphicon-star"></span> <span class="glyphicon glyphicon-star"></span> </div> </div>
.crop { overflow: hidden; } div { cursor: pointer; }
معرفی مقدماتی life cycle hooks در قسمت قبل صورت گرفت. در اینجا چون نیاز است به ازای هر بار رندر شدن این کامپوننت، عرض آن متفاوت باشد، بنابراین نیاز است راهی را پیدا کنیم تا بتوان مقدار خاصیت starWidth را متغیر کرد. به همین منظور از hook مخصوص این تغییرات یا همان OnChanges استفاده میشود. بنابراین باید کلاس این کامپوننت، اینترفیس OnChanges را پیاده سازی کند. پس از آن، importهای لازم جهت تعریف OnChanges به ابتدای فایل اضافه شده و همچنین متد ngOnChanges نیز جهت تکمیل کار پیاده سازی اینترفیس OnChanges، به کلاس جاری اضافه میشود.
کار متد ngOnChanges، تبدیل عدد امتیاز یک محصول، به عرض div نمایش ستارهها است.
مکانیزم کار رخداد ngOnChanges و دریافت اطلاعات از والد
متد ngOnChanges، تنها به خواص ویژهای به نام «input properties» واکنش نشان میدهد. اگر یک کامپوننت تو در توی قرار گرفتهی در یک کامپوننت دیگر، بخواهد اطلاعاتی را از والد خود دریافت کند، باید خاصیتی را در معرض دید آن دربرگیرنده قرار دهد. این کار توسط decorator ویژهای به نام ()Input@ انجام میشود.
به همین جهت است که پیش از خاصیت rating در کلاس StarComponent، شاهد درج مزین کنندهی ویژهی ()Input@ هستیم:
export class StarComponent implements OnChanges { @Input() rating: number;
پس از آن، کامپوننت دربرگیرنده یا والد، این خاصیت ورودی ویژه را از طریق روش property binding متداول، مقدار دهی میکند:
[rating]='product.starRating'
بدیهی است در اینجا چون خاصیت starWidth از نوع ورودی تعریف نشدهاست، قابلیت property binging فوق را در کامپوننت والد، ندارد.
اکنون به ازای هر بار نمایش این کامپوننت فرزند، خاصیت rating ورودی آن مقدار دهی شده و مقدار آن در رخداد ngOnChanges قابل دسترسی و استفاده خواهد بود. اینجا است که میتوان از این مقدار تغییر یافته، جهت ترجمهی آن به عرض div نمایش ستارهها، استفاده کرد.
ارسال دادهها از کامپوننت فرزند به کامپوننت والد
تا اینجا با استفاده از «خواص ورودی» امکان دسترسی به مقادیر ارسالی از طرف والد را در کامپوننت فرزند، پیدا کردیم. عکس آن نیز امکان پذیر است؛ اما توسط رخدادها.
کامپوننت فرزند، با استفاده از decorator ویژهی دیگری به نام ()Output@ امکان ارسال رخدادها را به کامپوننت والد پیدا میکند:
export class StarComponent implements OnChanges { @Input() rating: number; starWidth: number; @Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>();
در مثال جاری اگر کاربر بر روی div ستارههای نمایش داده شده کلیک کند، اتصال به آن از طریق event binging متداول انجام میشود (متد جدید onClick به رخداد click متصل شدهاست):
<div class="crop" [style.width.px]="starWidth" [title]="rating" (click)='onClick()'>
onClick() { this.ratingClicked.emit(`The rating ${this.rating} was clicked!`); }
تا اینجا مرحلهی تنظیمات رخدادها در کامپوننت فرزند صورت گرفت. ابتدا خاصیتی از نوع Output تعریف شد. سپس در کدهای قالب این کامپوننت جدید، متد onClick به رخداد click متصل گردید و سپس در کدهای مدیریت کنندهی این متد، متد ratingClicked.emit جهت ارسال اطلاعات نهایی به والد، فراخوانی گردید.
اکنون در کامپوننت والد، باید این مراحل برای دریافت اطلاعات از کامپوننت فرزند خود، طی شوند:
الف) ابتدا نام خاصیت مزین شدهی با Output، به عنوان مقصد event binding مشخص میشود و سپس متدی در کلاس کامپوننت والد، به آن متصل میگردد:
(ratingClicked)='onRatingClicked($event)'
ب) در ادامه، تعریف این متد جدید متصل شده را به کلاس ProductListComponent اضافه میکنیم:
onRatingClicked(message: string): void { this.pageTitle = 'Product List: ' + message; }
به این ترتیب با کلیک بر روی div هر کامپوننت نمایش ستارهای امتیازها، خاصیت pageTitle درج شدهی در صفحه تغییر میکند.
استفاده از کامپوننت نمایش ستارهای امتیازها
نکات کلی افزودن این کامپوننت جدید، تفاوتی با مطالب عنوان شدهی در قسمت سوم، در حین بررسی مراحل افزودن دایرکتیو نمایش لیست محصولات، به کامپوننت ریشهی سایت ندارد و یکی هستند.
برای افزودن و استفاده از این کامپوننت جدید، ابتدا قالب product-list.component.html را گشوده و سپس سطر نمایش عددی امتیاز یک محصول را به نحو ذیل تغییر میدهیم:
<td> <ai-star [rating]='product.starRating' (ratingClicked)='onRatingClicked($event)'> </ai-star> </td>
سپس باید به کلاس کامپوننت لیست محصولات (کامپوننت در برگیرنده) اعلام کرد که این کامپوننت جدید را باید از کجا پیدا کند. برای این منظور فایل product-list.component.ts را گشوده و خاصیت directives این کامپوننت را مقدار دهی میکنیم:
import { Component, OnInit } from 'angular2/core'; import { IProduct } from './product'; import { ProductFilterPipe } from './product-filter.pipe'; import { StarComponent } from '../shared/star.component'; @Component({ selector: 'pm-products', templateUrl: 'app/products/product-list.component.html', styleUrls: ['app/products/product-list.component.css'], pipes: [ProductFilterPipe], directives: [StarComponent] })
نمونهای از اجرای برنامه را در تصویر ذیل مشاهده میکنید:
در اینجا ستون امتیازهای محصولات با کامپوننت نمایش ستارهای این امتیازها جایگزین شدهاست و همچنین با کلیک بر روی یکی از آنها، عنوان panel جاری تغییر کردهاست.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part6.zip
خلاصهی بحث
در اینجا نحوهی طراحی API عمومی یک کامپوننت را بررسی کردیم. تا زمانیکه خواص کلاس یک کامپوننت به نحو متداولی تعریف میشوند، میدان دید آنها محدود است به قالب تعریف شدهی متناظر با آنها. اگر نیاز است خاصیتی خارج از این قالب و به صورت عمومی در کامپوننت دربرگیرندهی دیگری در دسترس قرار گیرد، آنرا با مزین کنندهی ()Input@ مشخص میکنیم و اگر قرار است این کامپوننت فرزند، اطلاعاتی را به کامپوننت والد ارسال کند، اینکار را توسط رخدادها و با تعریف ویژگی ()Output@ و EventEmitter انجام میدهد. نوع آرگومان جنریک EventEmitter، تعیین کنندهی نوع اطلاعاتی است که قرار است به کامپوننت دربرگیرنده ارسال شوند.
پس از تعریف کامپوننت فرزند، برای تعریف آن در کامپوننت والد، از نام selector آن به عنوان یک المان جدید HTML استفاده میشود و سپس با استفاده از property binding، اطلاعات لازم، به خاصیت از نوع ()Input@ کامپوننت فرزند ارسال میگردد. از event binding برای دریافت رخدادها از کامپوننت فرزند استفاده میشود. در اینجا هر رخدادی که توسط مزین کنندهی ()Output@ تعریف شده باشد، میتواند به عنوان مقصد event binding تعریف شود و اگر نیاز است به رخدادهای property binding از والد به فرزند، گوش فرا داد، میتوان اینترفیس OnChanges را در کلاس کامپوننت فرزند پیاده سازی کرد.
تعدادی از فناوریهایی که توسط این کتابخانه پشتیبانی میشوند در زیر آمده است:
Web Service: این فناوری اجازهی ارسال و دریافت پیامهای تحت شبکه را به خصوص بر روی اینترنت، فراهم میکند و باعث ارتباط جامعتر بین برنامهها و فناوریهای مختلف میگردد. در انواع جدیدتر WCF و Web Api نیز به بازار ارائه شدهاند.
webform و MVC : فناوریهای تحت وب که باعث سهولت در ساخت وب سایتها میشوند که وب فرم رفته رفته به سمت منسوخ شدن پیش میرود و در صورتی که قصد دارید طراحی وب را آغاز کنید توصیه میکنم از همان اول به سمت MVC بروید.
Rich Windows GUI Application : برای سهولت در ایجاد برنامههای تحت وب حالا چه با فناوری WPF یا فناوری قدیمی و البته منسوخ شده Windows Form.
Windows Console Application: برای ایجاد برنامههای ساده و بدون رابط گرافیکی.
Windows Services: شما میتوانید یک یا چند سرویس تحت ویندوز را که توسط Service Control Manager یا به اختصار SCM کنترل میشوند، تولید کنید.
Database stored Procedure: نوشتن stored procedure بر روی دیتابیسهایی چون sql server و اوراکل و ... توسط فریم ورک دات نت مهیاست.
Component Libraray: ساخت اسمبلیهای واحدی که میتوانند با انواع مختلفی از موارد بالا ارتباط برقرار کنند.
Portable Class Libary : این نوع پروژهها شما را قادر میسازد تا کلاسهایی با قابلیت انتقال پذیری برای استفاده در سیلور لایت، ویندوز فون و ایکس باکس و فروشگاه ویندوز و ... تولید کنید.
ازآنجا که یک کتابخانه شامل زیادی نوع میگردد سعی شده است گروه بندیهای مختلفی از آن در قالبی به اسم فضای نام namespace تقسیم بندی گردند که شما آشنایی با آنها دارید. به همین جهت فقط تصویر زیر را که نمایشی از فضای نامهای اساسی و مشترک و پرکاربرد هستند، قرار میدهم.
در CLR مفهومی به نام Common Type System یا CTS وجود دارد که توضیح میدهد نوعها باید چگونه تعریف شوند و چگونه باید رفتار کنند که این قوانین از آنجایی که در ریشهی CLR نهفته است، بین تمامی زبانهای دات نت مشترک میباشد. تعدادی از مشخصات این CTS در زیر آورده شده است ولی در آینده بررسی بیشتری روی آنان خواهیم داشت:
- فیلد
- متد
- پراپرتی
- رویدادها
CTS همچنین شامل قوانین زیادی در مورد وضعیت کپسوله سازی برای اعضای یک نوع دارد:
- private
- public
- Family یا در زبانهایی مثل سی ++ و سی شارپ با نام protected شناخته میشود.
- family and assembly: این هم مثل بالایی است ولی کلاس مشتق شده باید در همان اسمبلی باشد. در زبانهایی چون سی شارپ و ویژوال بیسیک، چنین امکانی پیاده سازی نشدهاست و دسترسی به آن ممکن نیست ولی در IL Assembly چنین قابلیتی وجود دارد.
- Assembly یا در بعضی زبانها به نام internal شناخته میشود.
- Family Or Assembly: که در سی شارپ با نوع Protected internal شناخته میشود. در این وضعیت هر عضوی در هر اسمبلی قابل ارث بری است و یک عضو فقط میتواند در همان اسمبلی مورد استفاده قرار بگیرد.
موارد دیگری که تحت قوانین CTS هستند مفاهیم ارث بری، متدهای مجازی، عمر اشیاء و .. است.
یکی دیگر از ویژگیهای CTS این است که همهی نوعها از نوع شیء Object که در فضای نام system قرار دارد ارث بری کردهاند. به همین دلیل همهی نوعها حداقل قابلیتهایی را که یک نوع object ارئه میدهد، دارند که به شرح زیر هستند:
- مقایسهی دو شیء از لحاظ برابری.
- به دست آوردن هش کد برای هر نمونه از یک شیء
- ارائهای از وضعیت شیء به صورت رشته ای
- دریافت نوع شیء جاری
وجود COMها به دلیل ایجاد اشیاء در یک زبان متفاوت بود تا با زبان دیگر ارتباط برقرار کنند. در طرف دیگر CLR هم بین زبانهای برنامه نویسی یکپارچگی ایجاد کرده است. یکپارچگی زبانهای برنامه نویسی علل زیادی دارند. اول اینکه رسیدن به هدف یا یک الگوریتم خاص در زبان دیگر راحتتر از زبان پایه پروژه است. دوم در یک کار تیمی که افراد مختلف با دانش متفاوتی حضور دارند و ممکن است زیان هر یک متفاوت باشند.
برای ایجاد این یکپارچگی، مایکروسافت سیستم CLS یا Common Language Specification را راه اندازی کرد. این سیستم برای تولیدکنندگان کامپایلرها جزئیاتی را تعریف میکند که کامپایلر آنها را باید با حداقل ویژگیهای تعریف شدهی CLR، پشتیبانی کند.
CLR/CTS مجموعهای از ویژگیها را شامل میشود و گفتیم که هر زبانی بسیاری از این ویژگیها را پشتیبانی میکند ولی نه کامل. به عنوان مثال برنامه نویسی که قصد کرده از IL Assembly استفاده کند، قادر است از تمامی این ویژگیهایی که CLR/CTS ارائه میدهند، استفاده کند ولی تعدادی دیگر از زبانها مثل سی شارپ و فورترن و ویژوال بیسیک تنها بخشی از آن را استفاده میکنند و CLS حداقل ویژگی که بین همه این زبانها مشترک است را ارائه میکند.
شکل زیر را نگاه کنید:
یعنی
اگر شما دارید نوع جدیدی را در یک زبان ایجاد میکنید که قصد دارید در یک
زبان دیگر استفاده شود، نباید از امتیازات ویژهای که آن زبان در اختیار شما میگذارد و به بیان بهتر CLS آنها را پشتیبانی نمیکند، استفاده کنید؛ چرا
که کد شما ممکن است در زبان دیگر مورد استفاده قرار نگیرد.
using System; // Tell compiler to check for CLS compliance [assembly: CLSCompliant(true)] namespace SomeLibrary { // Warnings appear because the class is public public sealed class SomeLibraryType { // Warning: Return type of 'SomeLibrary.SomeLibraryType.Abc()' // is not CLScompliant public UInt32 Abc() { return 0; } // Warning: Identifier 'SomeLibrary.SomeLibraryType.abc()' // differing only in case is not CLScompliant public void abc() { } // No warning: this method is private private UInt32 ABC() { return 0; } } }
دومین اخطار اینکه دو متد یکسان وجود دارند که در حروف بزرگ و کوچک تفاوت دارند. ولی زبان هایی چون ویژوال بیسیک نمیتوانند تفاوتی بین دو متد abc و ABC بیابند.
نکتهی جالب اینکه اگر شما کلمه public را از جلوی نام کلاس بردارید تمامی این اخطارها لغو میشود. به این خاطر که اینها اشیای داخلی آن اسمبلی شناخته شده و قرار نیست از بیرون به آن دسترسی صورت بگیرد. عضو خصوصی کد بالا را ببینید؛ کامنت بالای آن میگوید که چون خصوصی است هشداری نمیگیرد، چون قرار نیست در زبان مقصد از آن به طور مستقیم استفاده کند.
برای دیدن قوانین CLS به این صفحه مراجعه فرمایید.
در بالا در مورد یکپارچگی و سازگاری کدهای مدیریت شده توسط CLS صحبت کردیم ولی در مورد ارتباط با کدهای مدیریت نشده چطور؟
مایکروسافت موقعیکه CLR را ارئه کرد، متوجه این قضیه بود که بسیاری از شرکتها توانایی اینکه کدهای خودشون را مجددا طراحی و پیاده سازی کنند، ندارند و خوب، سورسهای مدیریت نشدهی زیادی هم موجود هست که توسعه دهندگان علاقه زیادی به استفاده از آنها دارند. در نتیجه مایکروسافت طرحی را ریخت که CLR هر دو قسمت کدهای مدیریت شده و نشده را پشتیبانی کند. دو نمونه از این پشتیبانی را در زیر بیان میکنیم:
یک. کدهای مدیریت شده میتوانند توابع مدیریت شده را در قالب یک dll صدا زده و از آنها استفاده کنند.
دو. کدهای مدیریت شده میتوانند از کامپوننتهای COM استفاده کنند: بسیاری از شرکتها از قبل بسیاری از کامپوننتهای COM را ایجاد کرده بودند که کدهای مدیریت شده با راحتی با آنها ارتباط برقرار میکنند. ولی اگر دوست دارید روی آنها کنترل بیشتری داشته باشید و آن کدها را به معادل CLR تبدیل کنید؛ میتوانید از ابزار کمکی که مایکروسافت همراه فریم ورک دات نت ارائه کرده است استفاده کنید. نام این ابزار TLBIMP.exe میباشد که از Type Library Importer گرفته شده است.
سه. اگر کدهای مدیریت نشدهی زیادتری دارید شاید راحتتر باشد که برعکس کار کنید و کدهای مدیریت شده را در در یک برنامهی مدیریت نشده اجرا کنید. این کدها میتوانند برای مثال به یک Activex یا shell Extension تبدیل شده و مورد استفاده قرار گیرند. ابزارهای TLBEXP .exe و RegAsm .exe برای این منظور به همراه فریم ورک دات نت عرضه شده اند.
سورس کد Type Library Importer را میتوانید در کدپلکس بیابید.
در ویندوز 8 به بعد مایکروسافت API جدید را تحت عنوان WinsowsRuntime یا winRT ارائه کرده است . این api یک سیستم داخلی را از طریق کامپوننتهای com ایجاد کرده و به جای استفاده از فایلهای کتابخانهای، کامپوننتها api هایشان را از طریق متادیتاهایی بر اساس استاندارد ECMA که توسط تیم دات نت طراحی شده است معرفی میکنند.
زیبایی این روش اینست که کد نوشته شده در زبانهای دات نت میتواند به طور مداوم با apiهای winrt ارتباط برقرار کند. یعنی همهی کارها توسط CLR انجام میگیرد بدون اینکه لازم باشد از ابزار اضافی استفاده کنید. در آینده در مورد winRT بیشتر صحبت میکنیم.
سخن پایانی: ممنون از دوستان عزیز بابت پیگیری مطالب تا بدینجا. تا این قسمت فصل اول کتاب با عنوان اصول اولیه CLR بخش اول مدل اجرای CLR به پایان رسید.
ادامهی مطالب بعد از تکمیل هر بخش در دسترس دوستان قرار خواهد گرفت.
متد Scan
فرض کنید قصد دارید تعدادی عدد را با هم جمع بزنید. برای اینکار عموما عدد اول با عدد دوم جمع زده شده و سپس حاصل آن با عدد سوم جمع زده خواهد شد و به همین ترتیب تا آخر توالی. کار متد Scan نیز دقیقا به همین نحو است. هربار که قرار است توالی پردازش شود، حاصل عملیات مرحلهی قبل را در اختیار مصرف کننده قرار میدهد.
در مثال ذیل، قصد داریم حاصل جمع اعداد موجود در آرایهای را بدست بیاوریم:
var sequence = new[] { 12, 3, -4, 7 }.ToObservable(); var runningSum = sequence.Scan((accumulator, value) => { Console.WriteLine("accumulator {0}", accumulator); Console.WriteLine("value {0}", value); return accumulator + value; }); runningSum.Subscribe(result => Console.WriteLine("result {0}\n", result));
result 12 accumulator 12 value 3 result 15 accumulator 15 value -4 result 11 accumulator 11 value 7 result 18
در دفعات بعدی، مقدار این accumulator با عدد جاری جمع زده شده و حاصل این عملیات در تکرار آتی، مجددا توسط accumulator قابل دسترسی خواهد بود.
یک نکته: اگر علاقمند باشیم که مقدار اولیهی accumulator، اولین عنصر توالی نباشد، میتوان آنرا توسط پارامتر seed متد Scan مقدار دهی کرد:
var runningSum = sequence.Scan(seed: 10, accumulator: (accumulator, value) =>
متد Buffer
متد بافر، کار تقسیم یک توالی را به توالیهای کوچکتر، بر اساس زمان، یا تعداد عنصر مشخص شده، انجام میدهد. برای مثال در برنامههای دسکتاپ شاید نیازی نباشد تا به ازای هر عنصر توالی، یکبار رابط کاربری را به روز کرد. عموما بهتر است تا تعداد مشخصی از عناصر یکجا پردازش شده و نتیجهی این پردازش به تدریج نمایش داده شود.
var sequence = Enumerable.Range(1, 200) .ToObservable() .Buffer(count: 10); sequence.Subscribe(onNext: numbers => { Console.WriteLine(numbers.Sum()); });
به این ترتیب میتوان فشار حجم اطلاعات ورودی با فرکانس بالا را کنترل کرد و در نتیجه از منابع موجود بهتر استفاده نمود. برای مثال اگر میخواهید عملیات bulk insert را انجام دهید، میتوان بر اساس یک batch size مشخص، گروه گروه اطلاعات را به بانک اطلاعاتی اضافه کرد تا فشار کار کاهش یابد.
همینکار را بر اساس زمان نیز میتوان انجام داد:
var sequence = Enumerable.Range(1, 200) .ToObservable() .Buffer(timeSpan: TimeSpan.FromSeconds(2));
متد Window
متد Window نیز دقیقا همان پارامترهای متد بافر را قبول میکند. با این تفاوت که هربار، یک توالی obsevable را به متد onNext ارسال میکند.
نوع numbers پارامتر onNext، در حین بکارگیری متد بافر در مثالهای فوق، IList of int است. اما اگر متدهای Buffer را تبدیل به متد Window کنیم، اینبار نوع numbers، معادل IObservable of int خواهد شد.
var sequence = Enumerable.Range(1, 200) .ToObservable() .Window(timeSpan: TimeSpan.FromSeconds(2)); sequence.Subscribe(onNext: numbers => { numbers.Subscribe(onNext: number => Console.WriteLine(number)); });
چه زمانی باید از Buffer استفاده کرد و چه زمانی از Window؟
در متد بافر، به ازای هر توالی که به پارامتر onNext ارسال میشود، یکبار وهلهی جدیدی از توالی مدنظر در حافظه ایجاد و ارسال خواهد شد. در متد Window صرفا اشارهگرهایی به این توالی را در اختیار داریم؛ بنابراین مصرف حافظهی کمتری را شاهد خواهیم بود. متد Window بسیار مناسب است برای اعمال aggregation. مثلا اگر نیاز است جمع، میانگین، حداقل و حداکثر عناصر دریافتی محاسبه شوند، بهتر است از متد Window استفاده شود که نهایتا قابلیت استفاده از متدهای الحاقی Sum و Max و Min را به همراه دارد. با این تفاوت که حاصل اینها نیز یک IObservable است که باید Subscribe آنرا برای دریافت نتیجه فراخوانی کرد:
var sequence = Enumerable.Range(1, 200) .ToObservable() .Window(10); sequence.Subscribe(onNext: numbers => { numbers.Sum().Subscribe(onNext: number => Console.WriteLine(number)); });
کاربردهای دنیای واقعی
در اینجا دو مثال از بکارگیری متد Buffer را جهت پردازش مجموعههای عظیمی از اطلاعات و نمایش همزمان آنها در رابط کاربری ملاحظه میکنید.
مثال اول: فرض کنید قصد دارید تمام فایلهای درایو C خود را توسط یک TreeView نمایش دهید. در این حالت نباید رابط کاربری برنامه در حالت هنگ به نظر برسد. همچنین به علت زیاد بودن تعداد فایلها و نمایش همزمان آنها در UI، نباید CPU Usage برنامه تا حدی باشد که در کار سایر برنامهها اخلال ایجاد کند. در این مثالها با استفاده از Rx و متد بافر آن، هربار مثلا 1000 آیتم را بافر کرده و سپس یکجا در TreeView نمایش میدهند. به این ترتیب دو شرط یاد شده محقق میشوند.
The Rx Framework By Example
مثال دوم: خواندن تعداد زیادی رکورد از بانک اطلاعاتی به همراه نمایش همزمان آنها در UI بدون اخلالی در کار سیستم و همچنین هنگ کردن برنامه.
Using Reactive Extensions for Streaming Data from Database
- طراحی دیتابیس یا بانک اطلاعاتی بر پایه چند زبانه بودن و بررسی سناریوهای مختلف.
- نکاتی که باید در ساخت سایتهای چند زبانه به آنها دقت کرد.
- شیوهی تشخیص و تغییر زبان سایت
- معرفی چند کامپوننت وب، برای مباحث چند زبانه
طراحی مدل دیتابیس
اولین کار برای داشتن یک سایت چند زبانه، این است که یک مدل صحیح و مناسب را برای دیتابیس خود انتخاب کنید. یکی از اولین روشهایی که به ذهن هر فردی میرسد این است که برای هر ستون متنی که قرار است چند زبانه باشد، به تعداد زبانها برایش یک ستون در نظر بگیریم. یعنی برای جدول مقالات که قرار است در سه زبان فارسی و انگلیسی و عربی باشد، سه ستون برای عنوان مقاله و سه ستون نیز برای متن آن داشته باشیم. تصویر زیر نمونهای از این مدل را نشان میدهد.
مزایا:
- پیاده سازی آسان
معایب:
- در این روش با زیاد شدن هر زبان، تعداد ستونها افزایش مییابد که باعث میشود طراحی مناسبی نداشته باشد.
- در ضمن این مورد باید توسط برنامه نویس مرتبا اضافه گردد یا اینکه برنامه نویس این امکان را در سیستم قرار دهد که مدیر سایت بتواند در پشت صحنه کوئری افزودن ستون را ایجاد کند که باید جدول مرتبا مورد alter گرفتن قرار بگیرد.
- ممکن است همیشه برای هر زبانی مطلبی قرار نگیرد و این مورد باعث میشود بی جهت فضایی برای آن در نظر گرفته شود.
پی نوشت: با اینکه امروزه بحث فیلدهای sparse Column وجود دارد ولی این فیلدها در هر شرایطی مورد استفاده قرار نمیگیرند وبیشتر متعلق به زمانی است که میدانیم آن فیلد به شدت کم مورد استفاده قرار میگیرد.
پی نوشت دوم : در صورتی که فیلد شما مانند متن مقاله که عموما از نوع داده (varchar(max است استفاده میکنید و در صورتی که زبان مورد استفاده قرار نگیرد در خیلی از اوقات بی جهت فیلدهای Blob ساخته اید که بهینه سازی آن را نیز باید در نظر بگیرید.
ID | کد |
Language | زبان |
ISO | کد دو رقمی آن زبان |
Flag | پرچم آن کشور |
مزایا:
- پیاده سازی آسان
معایب:
- ایجاد رکوردهای تکراری، هر مقاله برای بعضی از اطلاعاتش که چند زبانه نیستند دادههای تکراری خواهد داشت.
- هر مقاله یک مقالهی جدا شناخته میشود و ارتباطی میان آنان نخواهد بود. بدین ترتیب توانایی ایجاد گزارشهایی چون هر گروه از مقاله و دسته بندی آنها از بین خواهد رفت. در ضمن مدیر عموما در یک سیستم مدیریتی میخواهد تنها یک لینک را به یک مقاله بدهد و سایت بنا به تشخیص در زبان مزبور، یکی از این مقالات را به کاربر نمایش دهد؛ نه اینکه مرتبا مدیر برای هر زبان، لینکی را مهیا کند و در این حالت چنین چیزی ممکن نخواهد بود.
- در یک سیستم فروشگاهی همانند تصویر بالا کار هم سختتر میشود و هر رکورد، یک محصول جدا شناخته میشود و ویرایشها هم برای هر کدام باید جداگانه صورت بگیرد که در عمل این طرح را رد میکند.
سومین راه حل این است که سه جدول ایجاد کنیم:
یک. جدول زبانها (که بالاتر ایجاد شده بود)
دو . جدول نام مقاله به همراه اطلاعات پایه و فیلدها بی نیاز به چند زبانه بودن
سه : یک جدول که هر دو ستون آن کدهای کلید دو جدول بالا را دارند و فیلدهای چند زبانه در آن وجود دارند.
جدول پایه
ID | کد |
Name | نام مقاله |
CreationDate | تاریخ ایجاد |
Writer | نویسنده |
Visibilty | وضعیت نمایش |
LanguageCode | کد زبان |
ArticleID | کد مقاله |
CreationDate | تاریخ ایجاد |
Visibility | وضعیت نمایش مقاله |
Title | عنوان مقاله |
ContentText | متن مقاله |
در جدول پایه یک مقاله ایجاد میشود که اطلاعات عمومی همه مقالات را دارد و حتی خصوصیت وضعیت نمایش آن، روی همهی مقالات با هر زبانی تاثیر میگذارد. در جدول دو، هر مقاله یک رکورد دارد که کد زبان و کد مقاله برای آن یک کلید ترکیبی به حساب میآیند. پس، از هر مقاله یک یا چند زبان خواهیم داشت. همچنین دارای فیلدهایی با وضعیت مخصوص به خود هم هستند؛ مثل فیلد وضعیت نمایش مقاله که فقط برای این مقاله با این زبان کاربرد دارد.
مزایا:
- گزارش گیری آسان برای هر دسته مقاله با زبانهای مختلف و ارتباط و یکپارچگی
- آسان در افزودن زبان.
معایب:
- ایجاد کوئریهای پیچیدهتر و جوین دار که به نسبت روشهای قبلی کوئریها پیچیدهتر شده اند.
- کدنویسی زیادتر.
استفاده از ساختارهای XML یا JSON برای ذخیره سازی اطلاعات چند زبانه مانند ساختارهای زیر:
XML<Articles> <Article> this is english text </Article> <Article> این یک متن فارسی است </Article> </Articles> یا <Articles> <en-us> this is english text </en-us> <fa-ir> این یک متن فارسی است </fa-ir> </Articles>
"Articles":["en-us':{"title":"this is english text","content":" english content"},"fa-ir":{"title":"متن فارسی","content":"محتوای فارسی"}]
از مزایای این روش ذخیرهی همه دادهها در یک ستون و یک جدول است و نیازی به ستونهای اضافه یا جداول اضافه نیست ولی معایب این روش استفاده از کوئریهای پیچیدهتر جهت ارتباط و خواندن است.
استفاده از بانکهای اطلاعاتی NO SQL
در این بانکها دیگر درگیر تعداد ستونها و جنس آنها نیستیم و میتوانیم برای هر مقاله یا محصول، هر تعداد زبان و یا فیلد را که میخواهیم، در نظر بگیریم و اضافه کنیم. برای آشنایی بیشتر با این نوع بانکها و انواع آن، مقالات مربوط به nosql را در سایت دنبال کنید.
نکاتی که در یک سایت چند زبانه باید به آنها توجه کرد.
یک . زبان آن صفحه را معرفی کنید: این کار هم به موتورهای جست و جو برای ثبت سایت شما کمک میکند و هم برای معلولین که از ابزارهای صفحه خوان استفاده میکنند، کمک بزرگی است. در این روش، صفحه خوانها و دستگاههای خط بریل که زبان صفحه را تشخیص نمیدهند با خواندن کد زبان میتوانند زبان صفحه را تشخیص دهند. با استفاده از خط زیر میتوانید زبان اصلی صفحهی خود را تنظیم نمایید:
<html lang="en">
اگر از XHTML استفاده میکنید خاصیت زیر را فراموش نکنید. دریافت W3C Validation بدون آن امکان پذیر نخواهد بود.
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<blockquote lang=”fr”> <p>Le plus grand faible des hommes, c'est l'amour qu'ils ont de la vie.</p> </blockquote>
سه. لینک ها : اگر دارید در صفحهای لینک به جایی میدهید که متفاوت از زبان شماست، حتما باید زبان صفحه یا سایت مقصد را مشخص کنید. مثلا لینک زیر برای صفحهای است که از یک زبان غیر فرانسوی به یک صفحهی با زبان فرانسوی هدایت میشود:
<a href="" hreflang="fr">French</a>
همچنین اگر متن لینک شما هم به زبان فرانسوی باشد خیلی خوب میشود که آن را هم بیان کنید و از خاصیت lang و هم hreflang همزمان استفاده کنید:
<a href="" hreflang="fr">Francais</a>
پنج. انکودینگ صفحه را مشخص کنید: برای اینکه نحوهی رمزگذاری و رمزگشایی حروف و نمادها مشخص گردد، باید انکودینگ تنظیم شود و حتی برای بعضی از موتورهای جست و جو که ممکن است با وب سایت شما به مشکل بر بخورند. امروزه بیشتر از صفحات یونیکد استفاده میشود که سطح وسیعی از کاراکترها را پشتیبانی میکند.
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
HTML5
<meta charset="UTF-8">
شش. اندازهی فونت: موقعی که یک سایت چند زبانه را طراحی میکنید این نکته خیلی مهم هست که بدانید اندازه فونتهای زبان پیش فرض، برای باقی زبانها مناسب نیستند. به عنوان مثال ممکن است اندازه فونتی برای زبانهای انگلیسی، فرانسوی و آلمانی مناسب باشد ولی برای زبانهای فارسی و عربی و چینی و ... مناسب نباشد و خواندن آن سخت شود. به همین جهت یکی از راههای حل این مشکل استفاده از قالب css است که وابسته به خصوصیت lang ای است که شما برای صفحه و هر المان یا تگی که از این خصوصیت استفاده میکند، تعیین کردهاید.
:lang(en) { font-size: 85%; font-family: arial, verdana, sans-serif; } :lang(zh) { font-size: 125%; font-family: helvetica, verdana, sans-serif; }
خط زیر تعیین میکند که از استایل اول استفاده شود:
<html lang="en">
<html lang="zh">
<body class="english"> or <body class="chinese">
و استایل:
.english { font-size: 85%; font-family: arial, verdana, sans-serif; } .chinese { font-size: 125%; font-family: helvetica, verdana, sans-serif; }
هشت : زمان را نیز تغییر دهید: یکی از مواردی که در کمتر سایت چند زبانهای به چشم میخورد و به نظر بنده میتواند بسیار مهم باشد این است که time zone منطقهی هر زبان را بدانید. به عنوان مثال برای مقالهی خود، تاریخ ایجاد را به صورت UTC ذخیره کنید و سپس نمایش را بر اساس زبان یا حتی بهتر و دقیقتر از طریق IP کشور مربوطه به دست آورید. برای کاربران ثبت نام شده این تاریخ میتواند دقیقتر باشد همانند انجمنهای وی بولتین.
شیوههای تشخیص زبان سایت
یکی از راههای تشخیص زبان این است که موقعی که برای اولین بار کاربری به سایت مراجعه میکند، زبان مورد نظرش را سوال کنید و این اطلاعات را در یک کوکی بدون تاریخ انقضاء ذخیره کنید تا در دفعات بعدی آن را بررسی نمایید.
دومین راه، استفاده از IP کاربر مراجعه کننده است تا بر اساس آن زبان مورد نظر را انتخاب کنید.
در سومین شیوه که اغلب استفاده میشود، زبان سایت به طور پیش فرض بر روی یک زبان خاص که بهتر است انگلیسی باشد تنظیم شده است و سپس کاربر از طریق یک منو یا ابزارهای موجود در سایت، زبان سایت را تغییر دهد.
پی نوشت: فراموش نگردد که امکان تغییر زبان همیشه برای کاربر مهیا باشد و طوری نباشد که کاربر در آینده نتواند زبان سایت را تغییر دهد؛ حتی اگر تشخیص خودکار سایت برای زبان فعال باشد.
پلاگینها و ابزارهای مدیریت زبانپی نوشت: در روشهای بالا بهتر است همان مرتبهی اول اطلاعات را در یک کوکی ذخیره کنید تا مراحل پیگیری راحتتر و آسانتر شود.
دومین ابزار که بیشتر برای انتخاب کشور میباشد و من خودم در بخش مدیریتی سیستمها از آن استفاده میکنم، ابزار CountrySelector است. این پلاگین قابلیت جست و جو زبان را همزمان با تایپ کاربر نیر داراست. اسامی کشورها به صورت انگلیسی شروع شده و به زبان آن کشور در داخل پرانتز خاتمه مییابند و پرچم هر کشور نیز در کنار آن قرار دارد. کار کردن با آن بسیار راحت بوده و مستنداتش به طور کامل کار با آن را توضیح میدهد.
پلاگین بعدی International Telephone Input است که پیاده سازی پلاگین بالا میباشد. برای مواردی مفید است که شما نیاز دارید کد تلفنی کشوری را انتخاب کنید.
در مقالههای زیر که در سایت جاری است در مورد Globalization و به خصوص استفاده از ریسورسها مطالب خوبی بیان شده است:
قسمت بیست و دوم آموزش MVC که مبحث Globalization را دنبال میکند.
قسمت اول از شش قسمت مباحث Globalization که دنبالهی آن را میتوانید در مقالهی خودش دنبال کنید.
....click(function(e) { e.preventDefault() // stop the automatic form submission
برپایی پروژههای مورد نیاز
ابتدا یک پوشهی جدید را مانند DownloadFilesSample، ایجاد کرده و در داخل آن دستور زیر را اجرا میکنیم:
> dotnet new react
سپس در این پوشه، پوشهی ClientApp پیشفرض آنرا حذف میکنیم؛ چون کمی قدیمی است. همچنین فایلهای کنترلر و سرویس آب و هوای پیشفرض آنرا به همراه پوشهی صفحات Razor آن، حذف میکنیم.
به علاوه بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
spa.UseReactDevelopmentServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
اکنون در ریشهی پروژهی ASP.NET Core ایجاد شده، دستور زیر را صادر میکنیم تا پروژهی کلاینت React را با فرمت جدید آن ایجاد کند:
> create-react-app clientapp
> cd clientapp > npm install --save bootstrap axios
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود که برای افزودن فایل bootstrap.css آن به پروژهی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
- برای دریافت فایلها از سمت سرور، از کتابخانهی معروف axios استفاده خواهیم کرد.
کدهای سمت سرور دریافت فایلهای پویا
در اینجا کدهای سمت سرور برنامه، یک فایل PDF ساده را بازگشت میدهند. این محتوای باینری میتواند حاصل اجرای یک گزارش اکسل، PDF و یا کلا هر نوع فایلی باشد:
using Microsoft.AspNetCore.Mvc; namespace DownloadFilesSample.Controllers { [Route("api/[controller]")] public class ReportsController : Controller { [HttpGet("[action]")] public IActionResult GetPdfReport() { return File(virtualPath: "~/app_data/sample.pdf", contentType: "application/pdf", fileDownloadName: "sample.pdf"); } } }
روش دریافت محتوای باینری در برنامههای React
برای دریافت یک محتوای باینری از سرور توسط axios مانند تصاویر، فایلهای PDF و اکسل و غیره، مهمترین نکته، تنظیم ویژگی responseType آن به blob است:
const getResults = async () => { const { headers, data } = await axios.get(apiUrl, { responseType: "blob" }); }
ساخت URL برای دسترسی به اطلاعات باینری
تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی میکنند. این متد، شیء URL را از شیء window جاری دریافت میکند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید میکند. حاصل آن، یک URL ویژهاست مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
در ادامه کدهای تبدیل blob دریافت شدهی از سرور را به این URL ویژه، مشاهده میکنید:
import axios from "axios"; import React, { useEffect, useState } from "react"; export default function DisplayPdf() { const apiUrl = "https://localhost:5001/api/Reports/GetPdfReport"; const [blobInfo, setBlobInfo] = useState({ blobUrl: "", fileName: "" }); useEffect(() => { getResults(); }, []); const getResults = async () => { try { const { headers, data } = await axios.get(apiUrl, { responseType: "blob" }); console.log("headers", headers); const pdfBlobUrl = window.URL.createObjectURL(data); console.log("pdfBlobUrl", pdfBlobUrl); const fileName = headers["content-disposition"] .split(";") .find(n => n.includes("filename=")) .replace("filename=", "") .trim(); console.log("filename", fileName); setBlobInfo({ blobUrl: pdfBlobUrl, fileName: fileName }); } catch (error) { console.log(error); } };
- توسط useEffect Hook و بدون ذکر وابستگی خاصی در آن، سبب شبیه سازی رویداد componentDidUpdate شدهایم. به این معنا که متد getResults فراخوانی شدهی در آن، پس از رندر کامپوننت در DOM فراخوانی میشود و بهترین محلی است که از آن میتوان برای ارسال درخواستهای Ajaxای به سمت سرور و دریافت اطلاعات از backend، استفاده کرد و سپس setState را با اطلاعات جدید فراخوانی نمود. معادل setState در اینجا نیز، همان شیء حالتی است که توسط useState Hook و متد setBlobInfo آن تعریف کردهایم.
- پس از دریافت headers و data از سرور، با استفاده از متد createObjectURL، آنرا تبدیل به یک blob URL کردهایم.
- همچنین در سمت سرور، پارامتر fileDownloadName را نیز تنظیم کردهایم. این نام در سمت کلاینت، توسط هدری با کلید content-disposition ظاهر میشود:
ontent-disposition: "attachment; filename=sample.pdf; filename*=UTF-8''sample.pdf"
- اکنون که نام فایل و URL دسترسی به دادهی فایل باینری دریافتی از سرور را استخراج و ایجاد کردهایم. با فراخوانی متد setBlobInfo، سبب تنظیم متغیر حالت blobInfo خواهیم شد. این مورد، رندر مجدد UI را سبب شده و توسط آن میتوان برای مثال فایل PDF دریافتی را نمایش داد.
نمایش فایل PDF دریافتی از سرور، به همراه دکمههای دریافت، چاپ و بازکردن آن در برگهای جدید
در ادامه کدهای کامل قسمت رندر این کامپوننت را مشاهده میکنید:
import axios from "axios"; import React, { useEffect, useState } from "react"; export default function DisplayPdf() { // ... const { blobUrl } = blobInfo; return ( <> <h1>Display PDF Files</h1> <button className="btn btn-info" onClick={handlePrintPdf}> Print PDF </button> <button className="btn btn-primary ml-2" onClick={handleShowPdfInNewTab}> Show PDF in a new tab </button> <button className="btn btn-success ml-2" onClick={handleDownloadPdf}> Download PDF </button> <section className="card mb-5 mt-3"> <div className="card-header"> <h4>using iframe</h4> </div> <div className="card-body"> <iframe title="PDF Report" width="100%" height="600" src={blobUrl} type="application/pdf" ></iframe> </div> </section> <section className="card mb-5"> <div className="card-header"> <h4>using object</h4> </div> <div className="card-body"> <object data={blobUrl} aria-label="PDF Report" type="application/pdf" width="100%" height="100%" ></object> </div> </section> <section className="card mb-5"> <div className="card-header"> <h4>using embed</h4> </div> <div className="card-body"> <embed aria-label="PDF Report" src={blobUrl} type="application/pdf" width="100%" height="100%" ></embed> </div> </section> </> ); }
در اینجا با انتساب مستقیم blob URL ایجاد شده، به خواص src و یا data اشیائی مانند iframe ،object و یا embed، میتوان سبب نمایش فایل pdf دریافتی از سرور شد. این نمایش نیز توسط قابلیتهای توکار مرورگر صورت میگیرد و نیاز به نصب افزونهی خاصی را ندارد.
در ادامه کدهای مرتبط با سه دکمهی چاپ، دریافت و بازکردن فایل دریافتی از سرور را مشاهده میکنید.
مدیریت دکمهی چاپ PDF
پس از اینکه به blobUrl دسترسی یافتیم، اکنون میتوان یک iframe مخفی را ایجاد کرد، سپس src آنرا به این آدرس ویژه تنظیم نمود و در آخر متد print آنرا فراخوانی کرد که سبب نمایش خودکار دیالوگ چاپ مرورگر میشود:
const handlePrintPdf = () => { const { blobUrl } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } const iframe = document.createElement("iframe"); iframe.style.display = "none"; iframe.src = blobUrl; document.body.appendChild(iframe); if (iframe.contentWindow) { iframe.contentWindow.print(); } };
مدیریت دکمهی نمایش فایل PDF در یک برگهی جدید
اگر علاقمند بودید تا این فایل PDF را به صورت تمام صفحه و در برگهای جدید نمایش دهید، میتوان از متد window.open استفاده کرد:
const handleShowPdfInNewTab = () => { const { blobUrl } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } window.open(blobUrl); };
مدیریت دکمهی دریافت فایل PDF
بجای نمایش فایل PDF میتوان دکمهای را بر روی صفحه قرار داد که با کلیک بر روی آن، این فایل توسط مرورگر به صورت متداولی جهت دریافت به کاربر ارائه شود:
const handleDownloadPdf = () => { const { blobUrl, fileName } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } const anchor = document.createElement("a"); anchor.style.display = "none"; anchor.href = blobUrl; anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); };
اگر خواستید عملیات axios.get و دریافت فایل، با هم یکی شوند، میتوان متد handleDownloadPdf را پس از پایان کار await axios.get، فراخوانی کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: DownloadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگیهای سمت کلاینت نیز میشود، ابتدا به پوشهی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار میکند. سپس به پوشهی اصلی برنامهی ASP.NET Core بازگشته و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائهی برنامهی React بر روی پورت 5001 میشود.