آموزش بلیزور برای مبتدیان
ترجمهٔ صفحهٔ 1 از ۱۲۱بازگشت به فهرست
بلیزر: راهنمای مبتدیان
راهنمای شروع سریع برای بهرهوری با بلیزر (Blazor)
اِد شاربنو (Ed Charbeneau)
کتاب الکترونیکی
بازنمایی درونخطی صفحهٔ 1 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 2 از ۱۲۱بازگشت به فهرست
بلیزر: راهنمای مبتدیان
راهنمای شروع سریع برای بهرهوری با بلیزر
اِد شاربنو
بازنمایی درونخطی صفحهٔ 2 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 3 از ۱۲۱بازگشت به فهرست
تقدیمنامه
این کتاب به جامعهٔ .NET تقدیم میشود. بدون پشتیبانی شما، چیزی به نام «حمایت از توسعهدهندگان» (Developer Advocacy) وجود نداشت؛ عنوانی که سخت تلاش میکنم شایستهٔ آن باشم و اطمینان دهم محصولاتی که با آنها سروکار داریم، تا جای ممکن بهترین باشند.
بازنمایی درونخطی صفحهٔ 3 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 4 از ۱۲۱بازگشت به فهرست
حقنشر
کتاب «بلیزر: راهنمای مبتدیان» © ۲۰۲۰، اثر اِد شاربنو. تمامی حقوق محفوظ است.
تمامی حقوق محفوظ است. هیچ بخشی از این کتاب، بدون اجازهٔ کتبی نویسنده، نباید به هیچ شکل یا با هیچ وسیلهٔ الکترونیکی یا مکانیکی - از جمله سامانههای ذخیرهسازی و بازیابی اطلاعات - تکثیر شود. تنها استثنا، منتقدی است که میتواند در یک نقد، بخشهای کوتاهی از اثر را نقل کند.
طراحی جلد: Progress Software
تمام نامهای تجاری و نامهای محصولاتِ آمده در این کتاب، علامت تجاری، علامت تجاری ثبتشده یا نام تجاریِ صاحبان مربوطهاند. ما با هیچیک از محصولات یا فروشندگان مطرحشده در این کتاب وابستگی نداریم.
اِد شاربنو
وبسایت نویسنده: www.EdCharbeneau.com
تولیدشده توسط Progress Software
رابط کاربری Telerik برای بلیزر: www.telerik.com/blazor-ui
ویرایش نخست: مارس ۲۰۲۰
اِد شاربنو
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
بازنمایی درونخطی صفحهٔ 4 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 5 از ۱۲۱بازگشت به فهرست
دربارهٔ این کتاب
کتاب «بلیزر: راهنمای مبتدیان» برای توسعهدهندگانی نوشته شده است که مقداری تجربهٔ .NET دارند. اگر پیشینهٔ توسعهٔ شما خارج از .NET است، ممکن است در طول مسیر برخی مبانی .NET را نیز بیاموزید؛ بااینحال، مطالعهٔ منابع تکمیلی دربارهٔ C# و .NET کمک زیادی به شما خواهد کرد.
کتاب با دیدگاه شخصی نویسنده دربارهٔ وباسمبلی (WebAssembly)، اهمیت برخورداری از حق انتخاب در توسعهٔ وب و نگاهی به علت شکلگیری بلیزر آغاز میشود. سپس وارد یک مرور کلی میشوید تا با چارچوب و پشتهٔ فناوری آشنا شوید. با پیشرفت کتاب، نمونههای عملی را خواهید دید که مفاهیم اصلی را در سطحی گسترده آموزش میدهند. در بخشهای بعدی، موضوعاتی که به دانش عمیقتری نیاز دارند دوباره بررسی میشوند تا جزئیات ظریف آنها را درک کنید و بتوانید دربارهٔ شیوهٔ ساخت برنامهها و مؤلفههایتان تصمیمهای آگاهانه بگیرید.
هدف از چیدمان کتاب این است که شما را هرچه سریعتر به بهرهوری برساند. هنگام مطالعهٔ فصلها باید بتوانید بهراحتی در پروژههای بلیزر حرکت کنید و پروژههای جدید بسازید؛ نه اینکه برای آغاز یک پروژه ناچار باشید ابتدا کل کتاب را به پایان برسانید.
بازنمایی درونخطی صفحهٔ 5 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 6 از ۱۲۱بازگشت به فهرست
پیشگفتار
در اجلاس Microsoft MVP (Most Valuable Professional) سال ۲۰۱۷، استیو سندرسون را دیدم که چارچوب آزمایشی خود را در اتاقی پر از توسعهدهندگان .NET معرفی کرد. دیدم که هیجان سراسر اتاق را فراگرفت و حاضران چند بار ایستاده تشویق کردند. این هیجان، نظر شخصی مرا تأیید کرد: این همان چیزی بود که توسعهدهندگان .NET مانند من همیشه برای وب میخواستند. این کتاب را نوشتم تا به جامعهٔ .NET کمک کنم آنچه بلیزر ارائه میدهد سریعتر درک کند و در هیجانی که همهٔ ما در اجلاس MVP احساس کردیم سهیم شود.
از آن زمان، نوشتههای وبلاگی زیادی دربارهٔ این موضوع منتشر کردهام که با هر انتشار ماهانهٔ بلیزر قدیمی میشدند. بهترینِ آن مطالب را انتخاب کردم، بازبینی و بهروزرسانی کردم و بخشهایی از آنها را بازنویسی نمودم؛ سپس آنها را همراه با مقدار زیادی محتوای کاملاً جدید در این کتاب قرار دادم. با گردآوری بهترین نوشتههایم در یک اثر واحد و در قالب کتاب الکترونیکی، میتوان این منبع یکپارچهٔ دانش را همزمان با انتشار نسخههای جدید بهروز نگه داشت.
بازنمایی درونخطی صفحهٔ 6 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 7 از ۱۲۱بازگشت به فهرست
وباسمبلی
شما اینجا هستید
توسعهٔ وب همواره مترادف با توسعهٔ جاوااسکریپت بوده است؛ دستکم تا امروز.
توسعهٔ وب همیشه با توسعهٔ جاوااسکریپت هممعنا بوده است. اما اکنون گونهٔ تازهای از توسعهٔ وب در حال شکلگیری است که نوید جایگزینی برای جاوااسکریپت را میدهد: وباسمبلی (WebAssembly). بهعنوان توسعهدهندهٔ نرمافزاری با سالها تجربه در توسعهٔ وب، این مسیر جدید توجه مرا به خود جلب کرده است.
وباسمبلی (wasm) یک قالب دستورالعمل دودویی برای مرورگرهای وب است که طراحی شده تا مقصد کامپایل زبانهای سطحبالا، مانند C++، باشد. در سال ۲۰۱۷، مایکروسافت آزمایش با وباسمبلی را آغاز کرد تا با استفاده از زماناجرای مونو (Mono runtime)، .NET را به مرورگر بیاورد.
بازنمایی درونخطی صفحهٔ 7 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 8 از ۱۲۱بازگشت به فهرست
مونو زیرساخت لازم را فراهم میکند تا کتابخانههای .NET با پسوند .dll روی وباسمبلی اجرا شوند. بلیزر روی مونو اجرا میشود؛ بلیزر یک چارچوب برنامهٔ وب تکصفحهای (Single-Page Application یا SPA) است که بر پایهٔ .NET ساخته شده است. پشتهٔ WebAssembly-Mono-Blazor این ظرفیت را دارد که بستری تمامپشته برای برنامههای .NET در اختیار توسعهدهندگان وب قرار دهد؛ بستری که نیازی به نوشتن جاوااسکریپت ندارد و علاوه بر آن، به نصب افزونهای در مرورگر کاربر وابسته نیست.
معرفی مفهوم «وبِ بدون جاوااسکریپت» بلافاصله پرسشهایی را مطرح میکند؛ و کاملاً هم بجاست.
وباسمبلی چه چیزی ارائه میدهد که جاوااسکریپت یا تایپاسکریپت ارائه نمیکنند؟
پاسخ من سرشار از جانبداری و نظر شخصی است و به گمانم باید هم چنین باشد؛ زیرا همهٔ توسعهدهندگان، پروژهها و ابزارها یکسان نیستند. برای من پاسخ روشن و کوتاه است: «انتخاب». گسترش توسعهٔ وب به فراتر از جاوااسکریپت یعنی حق انتخاب و آزادی برای انتخاب نهفقط جاوااسکریپت یا .NET، بلکه مجموعهای بسیار گستردهتر از گزینهها. دقیقتر و شخصیتر بگویم، من میتوانم یک برنامهٔ وب را با ابزارها و زبانهایی توسعه دهم که در بخشهای دیگر نیز از آنها استفاده میکنم.
جایگزینهایی برای npm و Webpack
بازنمایی درونخطی صفحهٔ 8 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 9 از ۱۲۱بازگشت به فهرست
یکی از مزایای گشودن وب به روی .NET این است که اکنون برای npm و Webpack جایگزین داریم. بهعنوان توسعهدهندهای قدیمی در .NET، با اشتیاق از مدیر بستهٔ NuGet و MSBuild استقبال میکنم. برای من این فناوریها دردسر کمتری دارند، آشناترند و بهرهوری بسیار بیشتری ایجاد میکنند. هیچ ابزاری بینقص نیست، اما تجربهٔ من با NuGet و MSBuild عمدتاً مثبت بوده است.
در نگاه اول شاید چنین برداشت شود که npm و Webpack بهنوعی ابزارهای بدی هستند و من توصیه میکنم آنها را کنار بگذاریم؛ اما واقعیت دقیقاً برعکس است. npm و Webpack ابزارهای بسیار خوبیاند و احتمالاً مدت زیادی باقی خواهند ماند. اگر ابزارهای جاوااسکریپتی شما برای خودتان و برنامههایی که میسازید خوب کار میکنند، این موضوع بسیار عالی است. بهدلیل سابقهٔ طولانیام در وب، میدانم چرا npm و Webpack به وجود آمدهاند و برای دستاوردهای گذشته و آیندهٔ آنها ارزش قائلم.
کاهش منحنی یادگیری
یکی از چیزهایی که دربارهٔ بلیزر مرا شگفتزده کرد، سادگی واقعی کار با آن است. بلیزر سهولت نحو نشانهگذاری Razor را با مفاهیم دیگر .NET ترکیب میکند. این چارچوب بهترین الگوها را از چارچوبهای محبوب جاوااسکریپت مانند Angular و React وام گرفته است، درعینحال از قالبهای Razor بهره میبرد و با قراردادهای دیگر .NET همترازی دارد. این ترکیب ویژگیها امکان استفادهٔ دوباره از مهارتها را به شکلی فراهم میکند که پیش از این در دسترس نبود. همین سخن دربارهٔ توسعهدهندگان Node نیز صدق میکند؛ کسانی که در برنامههای تمامپشتهٔ جاوااسکریپت از یک زبان و مفاهیم آشنا استفاده میکنند.
قابلیت تعامل
استفاده از وباسمبلی به این معنا نیست که میتوان جاوااسکریپت را بهطور کامل کنار گذاشت. در حال حاضر، وباسمبلی باید بهوسیلهٔ جاوااسکریپت بارگذاری شود. (بله، صدای خشخش ناگهانی صفحهٔ گرامافون را میشنوم!)
بازنمایی درونخطی صفحهٔ 9 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 10 از ۱۲۱بازگشت به فهرست
ضرورت جاوااسکریپت به همینجا ختم نمیشود. وباسمبلی به هیچیک از APIهای پلتفرم دسترسی ندارد و برای دسترسی به APIهای پلتفرم، جاوااسکریپت لازم است.
در حال حاضر، وباسمبلی باید بهوسیلهٔ جاوااسکریپت بارگذاری شود. (بله، صدای خشخش ناگهانی صفحهٔ گرامافون را میشنوم!)
برنامههای وباسمبلی میتوانند توابع جاوااسکریپت را فراخوانی کنند و به این ترتیب، برای APIهایی که خارج از دسترس وباسمبلی خالص هستند مسیر مهاجرتی فراهم میشود. این قابلیت در چارچوب بلیزر نیز به کار میرود. چون بلیزر جدید است، قابلیت تعامل بلیزر (Blazor interop) به توسعهدهندگان اجازه میدهد هرجا خود وباسمبلی محدودیت دارد یا چارچوب بلیزر دسترسی لازم را فراهم نمیکند، به جاوااسکریپت بازگردند. نمودار بلوکی پشتهٔ برنامه در شکل ۱ نمایش داده شده است.
لایهٔ تعامل، یک لایهٔ انتزاعی است که بسیاری از توسعهدهندگان در C# با آن کار میکنند و لازم نیست نگران باشند که فناوری زیرین همچنان کد جاوااسکریپت اجرا میکند. با بلوغ وباسمبلی، نیاز به این انتزاعها بهمرور کاهش خواهد یافت. جزئیات استفاده از قابلیت تعامل با جاوااسکریپت، همراه با نمونههای کامل، در بخشهای بعدی کتاب بررسی میشود.
بازنمایی درونخطی صفحهٔ 10 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 11 از ۱۲۱بازگشت به فهرست
شکل ۱: نمودار بلوکی چارچوب بلیزر و مرورگر وب.
ادامهٔ مسیر
اگر توسعهٔ وب با استفاده از جایگزینهای جاوااسکریپت برایتان جذاب است، صرف زمان برای وباسمبلی و چارچوبهایی مانند بلیزر ارزشمند خواهد بود. هنوز در روزهای آغازین وباسمبلی و فناوریهای مبتنی بر آن قرار داریم، اما نوید شکلگیری زیستبومی گستردهتر توجه مرا جلب کرده است. بهعنوان یکی از علاقهمندان جدی توسعهٔ وب، دوست دارم این حوزه رو به جلو حرکت کند و دیدگاه ما دربارهٔ شیوهٔ نوشتن برنامهها برای این پلتفرم را گسترش دهد.
چشمانداز استفاده از سالها تجربهٔ .NET برای ساخت برنامهها به روشی که بهرهوری مرا افزایش دهد، دستکم بسیار هیجانانگیز است. در کنار آن، پایهٔ محکمی از مهارتهای جاوااسکریپت نیز ساختهام و هر روز آن را توسعه میدهم. این تنوع مهارتها، دیدگاههای گوناگون و روشهای منحصربهفردی برای حل مسائل مهندسی در اختیارم میگذارد.
بازنمایی درونخطی صفحهٔ 11 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 12 از ۱۲۱بازگشت به فهرست
آشنایی با بلیزر
در فصل پیش با دیدگاه نویسنده دربارهٔ اهمیت بلیزر برای زیستبوم توسعهٔ وب آشنا شدیم. در این فصل تمرکز خود را به یادگیری مفاهیم کلی چارچوب بلیزر و انتخابهایی که در اختیار ما قرار میدهد معطوف میکنیم.
بلیزر، چارچوب وب جدیدی است که وضعیت موجود را به چالش میکشد و درعین قدرتمندبودن، بهرهوری بالایی نیز دارد. بلیزر از معماری .NET Core استفاده میکند تا الگوها و شیوههای رایج را در سراسر پشته به کار گیرد. این چارچوب با پشتیبانی از مدل میزبانی سمت کاربر و سمت سرور، از قابلیت .NET Core برای اجرا در هر محیطی بهره میبرد.
این دوگانگی در بلیزر، حق انتخاب ایجاد میکند و درعینحال آنقدر انعطافپذیر است که بتوان بهآسانی بین حالتهای میزبانی جابهجا شد. بلیزر سادگی Razor را با مفاهیم دیگر .NET Core مانند تزریق وابستگی (Dependency Injection)، پیکربندی و مسیریابی ترکیب میکند. این چارچوب بهترین الگوها را از چارچوبهای محبوب جاوااسکریپت مانند Angular و React وام گرفته، از قالبهای Razor بهره برده و با قراردادها و ابزارهای دیگر .NET همترازی ایجاد کرده است.
بازنمایی درونخطی صفحهٔ 12 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 13 از ۱۲۱بازگشت به فهرست
مدل مؤلفهٔ Razor
کار با مدل مؤلفهٔ Razor (Razor Component) برای کسانی که با ASP.NET MVC یا Razor Pages کار کردهاند بسیار آشنا خواهد بود. مؤلفههای Razor، نحو Razor را با روشی جدید برای کپسولهکردن نشانهگذاری در مؤلفههای قابلاستفادهٔ مجدد ترکیب میکنند. با مؤلفههای Razor میتوانید بهسرعت بلوکهای سازندهٔ رابط کاربری (User Interface یا UI) برنامهتان را ایجاد کنید.
مؤلفهها کلاسهای .NET هستند که ویژگیهای آنها بهعنوان پارامترهای مؤلفه عمل میکنند؛ همین سامانهٔ ساده، نوشتن مؤلفههای Razor را آسان میسازد. با تقسیم مدل مؤلفه به سه مفهومِ «دستورها»، «نشانهگذاری» و «کد»، میتوانیم نحوهٔ ساختهشدن آنها را درک کنیم.
گرچه عبارتهای Razor Component و Blazor Component بهطور گسترده بهجای یکدیگر استفاده میشوند، اصطلاح دقیقتر Razor Component است. این نکته هنگام جستوجو در اینترنت اهمیت دارد، زیرا هر دو عبارت بسیار رایجاند. بااینحال، در سراسر این کتاب از مؤلفهها با عنوان «مؤلفههای Razor» یاد میکنیم.
شکل ۲: تجزیهٔ ساختار یک مؤلفهٔ Razor؛ بنیادیترین بلوک سازنده در یک برنامهٔ بلیزر.
در شکل ۲، مؤلفهها از دستورها (Directives) برای افزودن قابلیتهای ویژهای مانند مسیریابی یا تزریق وابستگی استفاده میکنند. نحو دستورها شبیه چیزی است که در ASP.NET MVC یا Razor Pages به کار میرود. نشانهگذاری مؤلفه عمدتاً HTML است که با نحو Razor غنیتر میشود. نحو Razor اجازه میدهد C# بهصورت درونخطی در کنار نشانهگذاری استفاده شود و مقادیر را در رابط کاربری نمایش دهد.
منطق مؤلفه درون یک بلوک @code نوشته میشود. پارامترهای مؤلفه و مقادیر متصل به داده در همین بخش تعریف میشوند. در روشی جایگزین، میتوان کد را با رویکرد کدِ پشتصحنه (Code-Behind)، شبیه ASP.NET WebForms، ارجاع داد. این رویکرد را در فصلی بعد بررسی میکنیم.
بازنمایی درونخطی صفحهٔ 13 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 14 از ۱۲۱بازگشت به فهرست
فعلاً کافی است بدانیم چنین گزینهای وجود دارد. مدل مؤلفهٔ Razor به ما اجازه میدهد رابط کاربری را به بخشهای قابلکنترل تقسیم کنیم. سپس میتوان مؤلفهها را کنار هم قرار داد و رابطهای کاربری بزرگتر، مؤلفههای پیچیدهتر و صفحهها را ساخت.
داستان دو بلیزر
بلیزر در آغاز بهعنوان یک چارچوب برنامهٔ تکصفحهای (SPA) سمت کاربر شکل گرفت.
هدف بلیزر این است که برنامههای .NET با استفاده از وباسمبلی و نحو Razor در مرورگر اجرا شوند. نام Blazor نیز از ترکیب browser + Razor به وجود آمده است. در طول توسعه، تیم ASP.NET یک مدل میزبانی سمت سرور نیز به بلیزر افزود. هرچند هر مدل میزبانی نقاط قوت بنیادی متفاوتی دارد، هر دو بر معماری زیربنایی یکسانی تکیه میکنند.
این رویکرد به توسعهدهندگان امکان میدهد بیشتر کد خود را مستقل از مدل میزبانی بنویسند. در حالت ایدهآل، تنها جایی که کد بهروشنی سمت سرور یا سمت کاربر محسوب میشود، هنگام دریافت داده است. برای درک بهتر کاربردپذیری این دو مدل، آنها را دقیقتر بررسی میکنیم.
سمت کاربر: Blazor WebAssembly
در سمت کاربر، اجرای بلیزر با کمک وباسمبلی ممکن میشود و به آن «برنامهٔ Blazor WebAssembly» میگویند. وباسمبلی (wasm) یک قالب دستورالعمل دودویی برای مرورگرهای وب است که بهعنوان مقصد کامپایل زبانهای سطحبالا مانند C++ طراحی شده است. بلیزر از طریق زماناجرای Mono - که به یک ماژول وباسمبلی کامپایل شده - از این فناوری بهره میبرد. همانطور که در شکل ۳ میبینیم، آوردن زماناجرای .NET به مرورگر باعث میشود کتابخانههای .NET یا همان فایلهای DLL مستقیماً روی کاربر اجرا شوند.
بازنمایی درونخطی صفحهٔ 14 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 15 از ۱۲۱بازگشت به فهرست
شکل ۳: این نمودار، زماناجرای .NET را درون مرورگر و با استفاده از وباسمبلی نشان میدهد. بلیزر از این زماناجرا برای کار مستقیم با کتابخانههای استاندارد .NET استفاده میکند.
این رویکرد از نظر کارایی و اندازهٔ بسته، مصالحههایی دارد؛ اما نقطهٔ قوت اصلی آن در نهایت قابلیت حمل است. چون برنامههای بلیزر از کتابخانههای .NET استفاده میکنند، لازم نیست کد موجود دوباره کامپایل شود یا از کامپایلر ویژهای با مقصد خاص استفاده کنیم. کد برنامه را میتوان میان پروژههای .NET به اشتراک گذاشت و در نتیجه کد کمتری نوشت.
در یک برنامهٔ معمولِ فرانتاند جاوااسکریپت، کد اعتبارسنجی و انتقال داده اغلب تکرار میشود، زیرا هم در لایهٔ .NET و هم در لایهٔ جاوااسکریپت لازم است. این تکرار در برنامهٔ بلیزر ضروری نیست، چون کد سمت کاربر و سمت سرور میتوانند به یک کتابخانهٔ مشترک ارجاع دهند.
مانند هر فناوری سمت کاربر، برنامههای Blazor WebAssembly برای داده به وبسرویسهای RESTful وابستهاند. دریافت داده با HttpClient انجام میشود که استاندارد زیستبوم .NET است. هنگامی که در بلیزر یک فراخوانی HTTP انجام میدهیم، داده بهصورت خودکار به شیء کلاس مشخصشده سریالسازی میشود. برای درک سادگی این فرایند، فراخوانی متد GetJsonAsync<T> از HttpClient را بررسی کنیم.
در یک برنامهٔ Web API، کنترلری به نام WeatherForecast داریم که یک نقطهٔ پایانی با خروجی IEnumerable<WeatherForecast> ارائه میکند. وقتی متد Get فراخوانی شود، ASP.NET پاسخ را بهطور خودکار به یک رشتهٔ JSON سریالسازی میکند.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
بازنمایی درونخطی صفحهٔ 15 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 16 از ۱۲۱بازگشت به فهرست
{
[HttpGet]
public IEnumerable<WeatherForecast> Get() {…}
}
هنگام دریافت داده در یک برنامهٔ بلیزر، یک فیلد برای نگهداری آرایهای از WeatherForecast ایجاد میکنیم. در این نمونه از متد چرخهٔ عمر OnInitializedAsync برای مقداردهی فیلد forecasts استفاده میکنیم. با GetJsonAsync یک درخواست GET به کنترلر WeatherForecast میفرستیم. چون GetJsonAsync با نوع مشخص WeatherForecast[] فراخوانی شده است، نتیجهٔ سریالشدهای از همین نوع بازمیگرداند.
WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http
.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
}
هم Web API و هم برنامهٔ سمت کاربر میتوانند از کلاس یکسان WeatherForecast استفاده کنند؛ نتیجه، کد کمتر، آزمون کمتر و پیچیدگی پایینتر است.
میتوانیم با اجرای بلیزر در سمت سرور، پیچیدگی برنامه را باز هم کاهش دهیم. در حالت سمت سرور، بلیزر میتواند مستقیماً از Entity Framework استفاده کند و نیاز به یک برنامهٔ Web API را بهطور کامل از میان بردارد. پس از آشنایی بیشتر با مفاهیم اصلی، این گزینهها را در فصلهای بعد بررسی خواهیم کرد.
Blazor Server با SignalR
یک برنامهٔ Blazor Server برای ساخت برنامهٔ وب .NET رویکردی متفاوت از Blazor WebAssembly دارد. در Blazor Server، بهجای وباسمبلی، مرورگر بهعنوان یک کاربر سبک (Thin Client) در نظر گرفته میشود. تمام کد برنامه با زماناجرای .NET Core روی سرور اجرا میشود.
برای ممکنشدن این عملیات کاربر سبک، یک کتابخانهٔ کمحجم SignalR در سمت کاربر راهاندازی میشود تا کد سرور بتواند از طریق WebSocket بهروزرسانیهای ناهمگام ارسال کند. پیامهای ردوبدلشده میان سرور و کاربر فقط شامل رویدادها و بهروزرسانیها هستند. همانطور که در شکل ۴ میبینیم، بلیزر روی سرور اجرا میشود و از طریق SignalR با مرورگر ارتباط دارد؛ در همین حال، کاربر سبک بهروزرسانیهای DOM (Document Object Model یا مدل شیء سند) را اعمال میکند.
بازنمایی درونخطی صفحهٔ 16 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 17 از ۱۲۱بازگشت به فهرست
شکل ۴: این نمودار بلیزر را در حال اجرا در سمت سرور و تعامل با مرورگر از طریق اتصال SignalR نشان میدهد.
چون کد برنامه کاملاً در سمت سرور اجرا میشود، لایهٔ داده میتواند بدون نیاز به لایهٔ انتزاعی جداگانه، اتصال نزدیکی با برنامه داشته باشد. این رویکرد برای برنامههای سازمانی (Line-of-Business) که اتصال پایدار در دسترس دارند ایدهآل است.
وقتی داده روی همان سرور قرار دارد، برنامههای Blazor Server برای ارتباط با پایگاه داده یا سرویسها به یک Web API نیاز ندارند. بهجای استفاده از HttpClient مانند Blazor WebAssembly، میتوان سرویسها را مستقیماً به برنامه تزریق و استفاده کرد. در نهایت، برنامههای Blazor Server به انتزاعهای کمتری نیاز دارند و میتوان آنها را در یک پروژهٔ واحد ساخت. اندازه و دامنهٔ پروژه نیز ممکن است بر سطح انتزاع اثر بگذارد، اما داشتن امکان انتخابِ راهحل سادهتر مزیت مهمی است.
دریافت داده در Blazor Server، هنگام استفادهٔ مستقیم از Entity Framework Core، کار سادهای است. ابتدا فیلدی میسازیم که دادهها را نگه دارد. در این نمونه از متد چرخهٔ عمر OnInitializedAsync برای مقداردهی فیلد blogs استفاده میکنیم. با dbContext متد ToArrayAsync را فراخوانی میکنیم. چون برنامه کاملاً در سمت سرور اجرا میشود، نه نقطهٔ پایانی Web API وجود دارد و نه نگرانی دربارهٔ سریالسازی و بازسریالسازی.
Blog[] blogs;
protected override async Task OnInitializedAsync()
{
blogs = await dbContext.Blogs.ToArrayAsync();
}
بازنمایی درونخطی صفحهٔ 17 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 18 از ۱۲۱بازگشت به فهرست
استفاده از بلیزر این انتخاب را در اختیارتان میگذارد که یا یک برنامهٔ RESTful سمت کاربر را با فناوریهای .NET بهجای جاوااسکریپت بسازید، یا یک برنامهٔ وب .NET سمت سرور با پیچیدگی کمتر ایجاد کنید.
مدلهای میزبانی بلیزر: مقایسهٔ نقاط قوت و مصالحهها
| سمت کاربر |
سمت سرور |
| + سربار کم یا صفر برای سرور |
+ اندازهٔ بسیار کوچک دادهٔ ارسالی |
| + RESTful |
+ انتزاع کمتر |
| + قابلیت کار آفلاین و PWA |
+ پیشرندر بهصورت پیشفرض |
| - اندازهٔ بزرگتر دادهٔ ارسالی |
- سربار سرور |
| - محیط جداشده از سرور |
- نیازمند اتصال |
بازنمایی درونخطی صفحهٔ 18 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 19 از ۱۲۱بازگشت به فهرست
انواع پروژه در بلیزر
در فصل پیش، دوگانگی بلیزر و مفاهیم کلی هر مدل میزبانی را بررسی کردیم. در این فصل، بیشتر بر انواع پروژهای تمرکز میکنیم که از انتخابهای ارائهشده توسط چارچوب پدید میآیند.
برای آنکه همه از نقطهای یکسان شروع کنیم، ابتدا پیشنیازهای بلیزر را مرور میکنیم. برخی از ابزارهایی که با آنها کار خواهیم کرد، بهصورت پیشفرض همراه با .NET SDK یا محیط توسعهٔ یکپارچهٔ Visual Studio نصب میشوند. بااینحال، بخشهایی از بلیزر در زمان نگارش هنوز در وضعیت پیشنمایش هستند و برای آنکه قالبها و کد بهدرستی کار کنند به مراحل نصب اضافی نیاز دارند.
برای آغاز توسعه با بلیزر، موارد زیر باید روی رایانهٔ شما نصب باشند:
- جدیدترین نسخهٔ .NET Core 3.x SDK.
- قالب Blazor WebAssembly.
- جدیدترین نسخهٔ Preview از Visual Studio 2019، همراه با بارکاری ASP.NET and web development.
برای دستورالعمل دقیق نصب، بهتر است مراحل موجود در مستندات رسمی مایکروسافت در Blazor.net را دنبال کنید؛ زیرا این مستندات میتوانند شمارهٔ جدیدترین نسخهٔ هر نرمافزار را همواره بهروز نگه دارند.
بازنمایی درونخطی صفحهٔ 19 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 20 از ۱۲۱بازگشت به فهرست
پس از نصب نرمافزارهای لازم، آمادهایم قالبهای پروژهٔ جدید بلیزر را بررسی کنیم. برای ایجاد یک برنامهٔ جدید بلیزر در Visual Studio، مسیر File > New Project را انتخاب کنید و در پنجرهٔ Create a new project گزینهٔ Blazor App را برگزینید.
شکل ۵: پنجرهٔ ایجاد برنامهٔ جدید بلیزر سه قالب برای انتخاب ارائه میکند.
انواع پروژهٔ موجود در پنجرهٔ Create a new Blazor app در شکل ۵ عبارتاند از:
Blazor Server App - سمت سرور
Blazor WebAssembly - سمت کاربر
Blazor WebAssembly - سمت کاربر، همراه با میزبان ASP.NET و Web API (معروف به Full-Stack)
اکنون که میدانیم هر قالب پروژه را کجا پیدا کنیم، میتوانیم جزئیات هر انتخاب را بررسی کنیم.
Blazor WebAssembly
قالب سمت کاربر بلیزر را میتوان با یک برنامهٔ HTML، CSS و JavaScript در دیگر چارچوبهای SPA مقایسه کرد. بااینحال، در بلیزر، جاوااسکریپت از طریق وباسمبلی با .NET و C# تکمیل میشود. برخلاف پروژههای ASP.NET MVC که ادامهٔ توضیح آن در صفحهٔ بعد آمده است، رندر اصلی این مدل در سمت کاربر انجام میشود.
بازنمایی درونخطی صفحهٔ 20 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 21 از ۱۲۱بازگشت به فهرست
در پروژههای سمت کاربر بلیزر، هرچند از Razor استفاده میشود، خروجی روی سرور رندر نمیشود؛ در عوض، چارچوب بلیزر در سمت کاربر HTML را در مرورگر رندر میکند. مرورگر همهچیز را پردازش میکند و محتوای این پروژه بهصورت منابع ایستا در نظر گرفته میشود.
چون پروژه را میتوان مجموعهای از منابع ایستا دانست، امکانات جالبی پدید میآید که پیش از این در زیستبوم ASP.NET دیده نشده بود. پروژههای ساختهشده با این قالب را تقریباً روی هر سرویسی که توان ارائهٔ فایلهای ایستا را دارد میتوان میزبانی کرد؛ برای نمونه GitHub Pages و قابلیت Static website hosting در Azure Storage.
این قالب، نمونههایی برای شروع کار با بلیزر و همچنین چیدمانهای پایهٔ صفحه، کنترلهای ناوبری و استایلهای Bootstrap را در خود دارد.
شکل ۶: راهحل، ساختار پروژهای آشنا برای برنامههای ASP.NET دارد که چند بخش ویژهٔ بلیزر نیز به آن افزوده شده است.
ساختار پروژه ساده است و فقط منابع اصلی زیر را در بر میگیرد:
/wwwroot: منابع ایستای استاندارد وب، شامل CSS، JavaScript، JSON، تصویر و HTML
/Pages: صفحهها و قابلیتهای Razor برنامه
/Shared: مؤلفههای مشترک و چیدمانهای صفحه
App.razor: مؤلفهٔ ریشه و ظرف کل برنامه
Program.cs: راهاندازی اولیه و پیکربندی برنامه
بازنمایی درونخطی صفحهٔ 21 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 22 از ۱۲۱بازگشت به فهرست
در نگاه نخست، چند مفهوم آشنا دیده میشود، زیرا بلیزر رویکردی شبیه برنامههای ASP.NET دارد؛ این موضوع در شکل ۶ مشخص است. Program.cs نهتنها یکی از اجزای رایج برنامههای .NET است، بلکه بلیزر از مفهومی مشابه ASP.NET Core Razor Pages نیز استفاده میکند. صفحهها یا قابلیتهای برنامهٔ بلیزر در مسیر پروژهٔ Pages قرار میگیرند و مسیریابی از طریق دستور @page همان صفحه انجام میشود.
@page "/myRoute"
<!-- component markup -->
@code {
// component logic
}
تمام پروژههای بلیزر شامل صفحههای نمونهٔ Index، Counter و Fetch Data هستند. این موارد در همهٔ انواع پروژه یکساناند؛ بهجز Fetch Data، زیرا تفاوت اصلی انواع پروژه در محل میزبانی برنامه نسبت به دادهای است که مصرف میکند.
بررسی را با صفحهٔ Counter و نشانهگذاری و منطق مؤلفهٔ آن آغاز میکنیم.
Counter
صفحهٔ Counter یک مؤلفهٔ ساده است که با دستور صفحه تزئین شده است. این مؤلفه، ترکیب پایهٔ یک مؤلفهٔ بلیزر شامل مسیریابی، اتصال داده و اتصال/مدیریت رویداد را نشان میدهد. هر بخش از ساختار مؤلفه در شکل ۷ برجسته شده است.
بازنمایی درونخطی صفحهٔ 22 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 23 از ۱۲۱بازگشت به فهرست
شکل ۷: ساختار مؤلفهٔ Counter شامل دستورها، نشانهگذاری و منطقی است که با اتصال داده به هم پیوند خوردهاند.
مؤلفهٔ Counter از یک دکمهٔ سادهٔ HTML برای افزایش فیلد شمارنده استفاده میکند و مقدار شمارنده درون یک تگ پاراگراف نمایش داده میشود. چون بلیزر بهصورت برنامهٔ تکصفحهای عمل میکند، تمام تعاملهای مؤلفه در سمت کاربر رخ میدهند. چارچوب بلیزر با استفاده از اتصال داده، بهروزرسانی DOM مرورگر را مدیریت میکند. خروجی رندرشدهٔ مؤلفهٔ Counter در شکل ۸ دیده میشود.
شکل ۸: مؤلفهٔ Counter که در مرورگر رندر شده است.
بازنمایی درونخطی صفحهٔ 23 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 24 از ۱۲۱بازگشت به فهرست
با رفتن به صفحهٔ Fetch Data میبینیم بلیزر چگونه منابع دادهٔ محلی را مدیریت میکند.
Fetch Data
در نوع پروژهٔ WebAssembly، صفحهٔ Fetch Data مؤلفهای است که از دادههای یک فایل ایستای محلی استفاده میکند. مؤلفهٔ Fetch Data تزریق وابستگی و مفاهیم پایهٔ قالب Razor را نمایش میدهد. این نسخه از Fetch Data بسیار شبیه نمونهٔ موجود در قالب Full-Stack است؛ تفاوت اصلی در محلی است که داده از آن بارگذاری میشود.
در بالای مؤلفه و پس از دستور مسیریابی، تزریق وابستگی تعریف شده است. دستور @inject به بلیزر میگوید نمونهای از HttpClient را برای متغیر Http فراهم کند. سپس منطق مؤلفه با استفاده از HttpClient و متد GetJsonAsync داده را دریافت میکند و دادهٔ درخواست JSON را به آرایهای از اشیای WeatherForecast متصل میسازد:
@page "/fetchdata"
@inject HttpClient Http
<h1>Weather forecast</h1>
// omitted for brevity
@code {
WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http
.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
…
}
بازنمایی درونخطی صفحهٔ 24 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 25 از ۱۲۱بازگشت به فهرست
نمایش دادههای WeatherForecast با پیمایش مجموعهٔ forecasts و اتصال مقادیر به یک جدول HTML انجام میشود:
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
برای این نمونههای پایه هیچ سروری استفاده نمیشود و نیازی نیز به آن نیست. اگر قصد داریم برنامه را همراه با میزبانی و وبسرویسها توسعه دهیم، قالبهای Full-Stack یا Server-Side میتوانند نقطهٔ شروع بهتری باشند.
Blazor Full-Stack
قالب Blazor Full-Stack همان ساختار پروژهٔ قالب Client-Side را با چند بخش افزوده در بر میگیرد. مانند قالب Client-Side، هیچ HTMLای روی سرور رندر نمیشود و تمام فایلها - از جمله فایلهای دودویی .NET - بهصورت فایل ایستا به کاربر تحویل داده میشوند. تفاوت، اضافهشدن میزبانی ASP.NET Core، Web API و یک پروژهٔ Shared برای منطق مشترک برنامه است.
بازنمایی درونخطی صفحهٔ 25 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 26 از ۱۲۱بازگشت به فهرست
این قالب سه پروژه دارد:
- برنامهٔ بلیزر سمت کاربر:
Blazor.Client
- برنامهٔ سرور ASP.NET Core:
Blazor.Server
- پروژهٔ مشترک .NET Standard برای منطق مشترک برنامه:
Blazor.Shared
Blazor.Server
برنامهٔ سرور مسئول ارائهٔ برنامه و فراهمکردن نقاط پایانی Web API برای دادهها است. در Startup.cs تنظیمات MimeType را میبینیم که سرور را طوری پیکربندی میکند تا فایلهای *.wasm و *.dll قابل ارائه باشند. علاوه بر این، فشردهسازی فعال میشود تا اندازهٔ فایلهای دودویی هنگام انتقال به کاربر کاهش یابد. فشردهسازی از طریق میانافزار AddResponseCompression فعال میشود.
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults
.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
فرایند Startup همچنین جایی است که میانافزار برنامهٔ بلیزر با app.UseClientSideBlazorFiles<Client.Startup>() مقداردهی اولیه میشود. این دستور مشخص میکند که برنامهٔ Blazor.Client باید ارائه شود.
// This method gets called by the runtime. Use this method
// to configure the HTTP request pipeline.
app.UseClientSideBlazorFiles<Client.Startup>();
برنامه یک نمونهٔ ساده از کنترلر Web API و اکشن آن را نیز در بر دارد. در WeatherForecastController، اکشن WeatherForecasts مجموعهای تصادفی از پیشبینیهای هوا تولید میکند. در قالب Full-Stack، وب API مربوط به WeatherForecasts جایگزین فایل ایستای weather.json در قالب Client-Side میشود.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
…
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
…
return Enumerable.Range(1, 5)
بازنمایی درونخطی صفحهٔ 26 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 27 از ۱۲۱بازگشت به فهرست
.Select(index => new WeatherForecast…)
.ToArray();
}
}
Blazor.Client
تقریباً تمام برنامهٔ Client با قالب Client-Side یکسان است. بااینحال، نمونهٔ FetchData اندکی تفاوت دارد و در فراخوانی GetJsonAsync داده را از نقطهٔ پایانی Web API مربوط به WeatherForecast درخواست میکند.
@code {
WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http
.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
}
}
Blazor.Shared
چون راهحل شامل برنامهٔ سرور و کاربری است که هر دو از .NET استفاده میکنند، امکان اشتراک کد میان دو برنامه وجود دارد. این سناریو ویژهٔ بلیزر است، زیرا کاربر بهجای جاوااسکریپت، .NET را در سمت کاربر اجرا میکند. نمونهٔ موجود در قالب پروژه، از همان کلاس WeatherForecast در برنامهٔ سرور و سمت کاربر استفاده میکند.
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string Summary { get; set; }
public int TemperatureF =>
32 + (int)(TemperatureC / 0.5556);
}
کلاس WeatherForecast تنها یک نمونهٔ ابتدایی از کد مشترک است. کد مشترک دیگر میتواند شامل اعتبارسنجی، تبدیلکنندهها و منطق کسبوکار باشد؛ مشروط بر آنکه از مفاهیم وابسته به سیستم، ورودی/خروجی یا وب جدا شده باشد. اگر این ایده را گسترش دهیم، یک برنامهٔ فرضی میتواند کتابخانهها را با چارچوبهای دیگر .NET مانند Xamarin، Windows Desktop یا برنامههای وب مبتنی بر .NET به اشتراک بگذارد.
بازنمایی درونخطی صفحهٔ 27 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 28 از ۱۲۱بازگشت به فهرست
رندر در سرور
در دیگر فناوریهای وب .NET مانند ASP.NET MVC و Core MVC، قالبهای Razor روی سرور رندر و بهصورت HTML برای کاربر ارسال میشوند. برخی چارچوبهای جاوااسکریپت مانند Angular و React، در فرایندی به نام کامپایل پیش از اجرا (Ahead-of-Time Compilation یا AOT) یا رندر همشکل (Isomorphic Rendering)، مسئولیت رندر را میان کاربر و سرور تقسیم میکنند.
با وجود اینکه برنامهٔ کاربری بلیزر روی یک سرور .NET میزبانی میشود، در هر دو قالب Client-Side و Full-Stack همهٔ نماها در سمت کاربر رندر میشوند. در زمان نگارش، میتوان با سفارشیسازی گستردهٔ قالب Full-Stack، پیشرندر سمت سرور را فعال کرد.
Blazor Server App
قالب پروژهٔ Blazor Server-Side در شیوهٔ تحویل برنامهٔ بلیزر و تعامل آن با مرورگر، رویکردی کاملاً متفاوت دارد. در پیکربندی سمت سرور، بلیزر با استقرار یک برنامهٔ جاوااسکریپتی SignalR روی کاربر، مرورگر را بهعنوان «کاربر سبک» به کار میگیرد. در سمت سرور، بلیزر یک هاب SignalR پیادهسازی میکند که از طریق WebSocket با کاربر ارتباط دارد.
در مدل میزبانی سمت سرور، بلیزر درون یک برنامهٔ ASP.NET Core و روی سرور اجرا میشود. بهروزرسانیهای رابط کاربری، مدیریت رویدادها و فراخوانیهای جاوااسکریپت از طریق اتصال SignalR انجام میشوند. در این پیکربندی نیازی به وباسمبلی نیست و بلیزر روی زماناجرای ASP.NET Core سرور اجرا میشود.
در شکل ۹، نمودار بلوکی پیادهسازی Blazor Server را میبینیم. همهٔ بهروزرسانیهای رابط کاربری بهصورت تفاوتها (Diffs)، دوسویه و در قالب بستههای دودویی از طریق WebSocket ارسال میشوند. از دید کاربر، این برنامه با هیچ برنامهٔ وب دیگری تفاوتی ندارد.
در این پیکربندی نیازی به وباسمبلی نیست و بلیزر روی زماناجرای ASP.NET Core در سرور اجرا میشود.
بازنمایی درونخطی صفحهٔ 28 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 29 از ۱۲۱بازگشت به فهرست
شکل ۹: این نمودار، اجرای بلیزر در سمت سرور و تعامل آن با مرورگر از طریق اتصال SignalR را نشان میدهد.
با وجود تفاوت چشمگیر در نحوهٔ عملکرد بلیزر در سمت سرور، مدل واقعی برنامه تقریباً یکسان باقی میماند. در قالب پروژهٔ سمت سرور، تفاوتهای اندکی در کد نمونهٔ ارائهشده وجود دارد. قالب، مانند برنامهٔ Blazor WebAssembly، فقط یک پروژه دارد.
در برنامهٔ Blazor Server، فایل _Host.cshtml نقطهٔ ورود سمت کاربر به برنامه است. برخلاف برنامهٔ Blazor WebAssembly، فایل _Host.cshtml برای راهاندازی برنامه از Razor Pages استفاده میکند. ازاینرو نحو Razor را در صفحه و بهشکل Tag Helper زیر میبینیم: <component …>.
Tag Helper مربوط به component، رندر یک مؤلفهٔ Razor را از درون یک Razor Page فراخوانی میکند. مؤلفهٔ ریشهٔ برنامه از App.razor در همین نقطه فراخوانی میشود. هنگامی که برنامه برای عملیات سمت سرور پیکربندی شود، فایل جاوااسکریپتی blazor.server.js جایگزین blazor.webassembly.js میشود. blazor.server.js کاربر SignalR را راهاندازی میکند و ارتباط WebSocket با سرور را برقرار میسازد.
بازنمایی درونخطی صفحهٔ 29 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 30 از ۱۲۱بازگشت به فهرست
<body>
<app>
<component type="typeof(App)"
render-mode="ServerPrerendered" />
</app>
…
<script src="_framework/blazor.server.js"></script>
</body>
چون تمام برنامه در سمت سرور اجرا میشود، نمونهٔ FetchData در این نوع پروژه از دادههای یک سرویس محلی استفاده میکند. دستور @inject در این نمونه بهجای HttpClient - که در قالب Full-Stack دیدیم - نمونهای از WeatherForecastService را فراهم میکند. WeatherForecastService در نمونه، کلاسی ساده برای تولید دادههای تصادفی است؛ اما در یک سناریوی واقعی، این سرویس میتواند Context پایگاه دادهٔ Entity Framework، مخزن (Repository) یا منبع دیگری از داده باشد.
@using BlazorBookExamples.Data
@inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
// omitted for brevity
@code {
WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService
.GetForecastAsync(DateTime.Now);
}
}
WeatherForecastService و سرویسهای دیگر در متد ConfigureServices فایل Startup.cs به ظرف تزریق وابستگی اضافه میشوند.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
}
Server
پروژهٔ سروری که قالب ارائه میکند، یک میزبان سادهٔ ASP.NET Core است. در فایل Startup.cs این پروژه، میانافزار UseEndpoints فراخوانی میشود و هاب SignalR بلیزر را به مسیر صحیح نگاشت میکند.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env)
{
بازنمایی درونخطی صفحهٔ 30 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 31 از ۱۲۱بازگشت به فهرست
}
…
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
مزایا و معایب
از آنجا که برای راهاندازی کاربر فقط مقدار کمی جاوااسکریپت لازم است و هیچ اسمبلی .NET به کاربر منتقل نمیشود، برنامهٔ Blazor Server-Side از نظر ترافیک شبکه کارآمد است. حتی هنگام اجرا نیز ترافیک شبکه سبک باقی میماند، زیرا ارتباط میان «کاربر سبک» مرورگر و سرور در قالب بستههای دودویی کوچک انجام میشود. بااینحال، چون WebSocket به کار میرود، کاربر و سرور باید همواره به یکدیگر متصل باشند. بنابراین برنامههای Blazor Server-Side در حالت آفلاین کار نمیکنند.
جمعبندی انواع پروژهٔ بلیزر
هر نوع پروژه مجموعهای مشابه از نمونهها، از جمله مؤلفههای Counter و Fetch Data، دارد. نمونهٔ Fetch Data از پروژهای به پروژهٔ دیگر متفاوت است تا ویژگیهای خاص همان نوع پروژه را نشان دهد.
با قالبهای پروژهٔ Blazor WebAssembly، Full-Stack و Blazor Server، توسعهدهندگان میتوانند نقطهٔ شروعی را انتخاب کنند که بیشترین سازگاری را با نیازهای برنامهشان دارد. قالب Blazor WebAssembly بر اجرای کامل فایلهای ایستا در مرورگر تمرکز دارد؛ درحالیکه قالب Full-Stack میزبانی ASP.NET Core و Web API را نیز در بر میگیرد. قالب Blazor Server چارچوب بلیزر را روی سرور اجرا میکند و بهجای وباسمبلی به SignalR متکی است؛ در نتیجه، در ازای عملکرد مناسب، وابستگی دائمی به اتصال شبکه ایجاد میشود.
بازنمایی درونخطی صفحهٔ 31 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 32 از ۱۲۱بازگشت به فهرست
مبانی مؤلفهها در بلیزر
در فصل پیش، انواع پروژهای را که برای ساخت یک پروژهٔ جدید استفاده میشوند بررسی کردیم. در این فصل با شیوهٔ نوشتن مؤلفهها بیشتر آشنا میشویم.
همانطور که در فصل «آشنایی با بلیزر» بهاختصار گفتیم، چارچوب بلیزر از مدل مؤلفهٔ Razor استفاده میکند. در این فصل، هنگام ساخت مؤلفهای برای نمایش پیشبینی آبوهوا، دربارهٔ مؤلفههای Razor بیشتر میآموزیم. در این فرایند با مبانی ساخت مؤلفه، از جمله دستورها، پارامترها، محتوای فرزند/قالبها، مسیریابی و مدیریت رویداد آشنا خواهیم شد. برای سادگی، از منابعی استفاده میکنیم که قالب جدید Blazor Server app در اختیارمان قرار میدهد.
دستورها
دستورهای کامپایلر (Compiler Directives) برای ارائهٔ فرمان به Razor استفاده میشوند؛ فرمانهایی که معمولاً شیوهٔ تجزیهٔ مؤلفه را تغییر میدهند یا قابلیت متفاوتی را فعال میکنند. بیایید صفحهای جدید برای نمایش پیشبینی هفتگی آبوهوا بسازیم. در چارچوب بلیزر، صفحهها مؤلفههایی هستند که با دستور @page تزئین میشوند. پس از دستور @page یک مقدار رشتهای قرار میگیرد که مسیر صفحهٔ مؤلفه را تعریف میکند و URLای میسازد که میتوان در مرورگر به آن رفت؛ برای نمونه: @page "/my-url".
هرچند دستور @page مسیریابی را فعال میکند، توانایی ما برای استفاده از مؤلفه بهعنوان یک بلوک سازندهٔ پایه در دیگر مؤلفهها یا «صفحهها» را تغییر نمیدهد.
بازنمایی درونخطی صفحهٔ 32 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 33 از ۱۲۱بازگشت به فهرست
کار را با افزودن یک مؤلفهٔ Razor جدید به نام WeeklyForecast به برنامه آغاز میکنیم. در پروژه، پوشهٔ Pages را انتخاب و روی آن راستکلیک کنید؛ سپس Add > New Item را انتخاب کنید و مطابق شکل ۱۰، گزینهٔ Razor Component را برگزینید. نام مؤلفه را WeeklyForecast.razor بگذارید و برای پایان کار روی Create کلیک کنید.
شکل ۱۰: ساخت یک مؤلفهٔ Razor جدید با پنجرهٔ Add New Item.
مؤلفه با یک قالب اولیهٔ بسیار ساده ساخته میشود. این قالب را نگه میداریم و دستور @page با مقدار "/weeklyforecast" را در ابتدای فایل قرار میدهیم. بهترین رویه این است که همهٔ دستورها در بالای فایل قرار گیرند، مگر آنکه کاربرد خاصی جای دیگری را ضروری کند. برای نمونه، بلوک @code نیز یک دستور است که معمولاً در پایین مؤلفه قرار میگیرد؛ بااینحال جابهجایی آن خطایی ایجاد نمیکند.
@page "/weeklyforecast"
<h3>WeeklyForecast</h3>
@code {
}
اکنون میتوانیم برنامه را اجرا و تغییرات را در مرورگر مشاهده کنیم. پس از اجرای برنامه، /weeklyforecast را به نشانی اضافه کنید تا به صفحهٔ تازهساختهشده بروید. نتیجه باید شبیه شکل ۱۱ باشد.
بازنمایی درونخطی صفحهٔ 33 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 34 از ۱۲۱بازگشت به فهرست
شکل ۱۱: مؤلفهٔ WeeklyForecast که برای تعیین مسیر از دستور @page استفاده میکند.
از این صفحه برای ساخت نمونهٔ اولیهٔ محصول نهایی استفاده میکنیم. این کار کمک میکند تشخیص دهیم کدام بخشها را میتوان به اجزای قابلاستفادهٔ مجدد تبدیل کرد. اطلاعات آبوهوا را با HTML و کلاسهای CSS مربوط به Bootstrap و Open Iconic نمایش خواهیم داد.
درون صفحهٔ WeeklyForecast نخستین عنصر نمونهٔ رابط کاربری را اضافه میکنیم: دادهٔ پیشبینی یک روز. فعلاً از مقادیر ثابت استفاده میکنیم که بعداً با دادههای واقعی جایگزین خواهند شد. نشانهگذاری درون یک عنصر div قرار دارد که با کلاس card از Bootstrap ظاهر مناسبی پیدا میکند. درون بدنهٔ کارت، یک عنصر span با کلاسهای Open Iconic یعنی oi oi-rain قرار میدهیم تا آیکونی متناسب با وضعیت آبوهوا نمایش دهد.
بازنمایی درونخطی صفحهٔ 34 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 35 از ۱۲۱بازگشت به فهرست
@page "/weeklyforecast"
<h3>WeeklyForecast</h3>
<div class="card bg-light" style="width: 18rem;">
<div class="card-body text-center">
<span class="h1 oi oi-rain"></span>
<h1 class="card-title">17 C°</h1>
<p class="card-text">
Rainy weather expected Monday
</p>
</div>
</div>
…
تغییرات را ذخیره و صفحهٔ WeeklyForecast را دوباره بارگذاری میکنیم؛ اکنون کارت آبوهوا نمایش داده میشود. صفحهٔ رندرشده باید شبیه شکل ۱۲ باشد.
شکل ۱۲: نمونهٔ اولیهٔ صفحهٔ پیشبینی هفتگی که یک پیشبینی آبوهوا را نمایش میدهد.
صفحه کمکم شکل میگیرد؛ اما ما در حال ساخت پیشبینی هفتگی هستیم و درحالحاضر فقط یک روز داریم. صفحه را طوری تغییر میدهیم که بتواند دادهٔ پیشبینی پنج روز را نمایش دهد. چون کارتها تکرار خواهند شد، بخش نمایش را در یک ظرف Flexbox قرار میدهیم. از آنجا که Bootstrap به کار میبریم، میتوانیم از کلاس کمکی d-flex استفاده کنیم.
درون ظرف d-flex باید نمایش آبوهوا را تکرار کنیم. برای این کار از Razor و حلقهٔ foreach کمک میگیریم. برای ساخت یک حلقهٔ ساده بدون دادهٔ واقعی، در صفحهٔ بعد از Enumerable.Range استفاده میکنیم.
بازنمایی درونخطی صفحهٔ 35 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 36 از ۱۲۱بازگشت به فهرست
متد Enumerable.Range برای ما یک دنباله تولید میکند. حالا که چند کارت نمایش داده میشود، ویژگی عرض ثابت style="width: 18rem;" را حذف میکنیم تا عنصر در صورت نیاز گسترش یابد. برای ایجاد فاصلهٔ سفید میان کارتها، کلاس CSS m-2 یا margin 2 افزوده میشود.
…
<div class="d-flex">
@foreach (var item in Enumerable.Range(1, 5))
{
<div class="card bg-light m-2">
<div class="card-body text-center">
<span class="h1 oi oi-rain"></span>
<h1 class="card-title">17 C°</h1>
<p class="card-text">
Rainy weather expected Monday
</p>
</div>
</div>
}
</div>
…
نتیجه، پیشبینی پنجروزهای است که مطابق شکل ۱۳ در عرض صفحه تکرار میشود.
شکل ۱۳: صفحهٔ آبوهوای هفتگی که یک کارت پیشبینی ثابت را پنج بار در عرض صفحه تکرار میکند.
بازنمایی درونخطی صفحهٔ 36 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 37 از ۱۲۱بازگشت به فهرست
اکنون احتمالاً الگوی تکرار هر روز آبوهوا را میبینید. این بخش تکرارشونده میتواند بهسادگی یک مؤلفهٔ قابلاستفادهٔ مجدد باشد که اطلاعات یک روز را کپسوله میکند. بیایید این بخش را از صفحهٔ WeeklyForecast جدا کنیم و در فایل مؤلفهٔ مستقلی قرار دهیم.
در پوشهٔ /Shared پروژه، مؤلفهای جدید به نام WeatherDay میسازیم. محتوای اولیهٔ مؤلفهٔ تازه را حذف میکنیم و HTML درون حلقهٔ foreach از مؤلفهٔ WeeklyForecast را به مؤلفهٔ WeatherDay انتقال میدهیم. همین عملیات سادهٔ برش و چسباندن برای ساخت یک مؤلفهٔ پایهٔ Razor کافی است.
پس از انتقال نشانهگذاری به مؤلفهٔ WeatherDay، میتوانیم در WeeklyForecast آن را با نامش به کار ببریم. یک مؤلفهٔ WeatherDay به بدنهٔ حلقهٔ foreach اضافه میکنیم.
/Shared/WeatherDay.razor
<div class="card bg-light m-2">
<div class="card-body text-center">
<span class="h1 oi oi-rain"></span>
<h1 class="card-title">17 C°</h1>
<p class="card-text">
Rainy weather expected Monday
</p>
</div>
</div>
/Pages/WeeklyForecast.razor
…
<div class="d-flex">
@foreach (var item in Enumerable.Range(1, 5))
{
<WeatherDay></WeatherDay>
}
</div>
…
با اجرای دوبارهٔ صفحه، نتیجه باید کاملاً مشابه اجرای قبلی و همان شکل ۱۳ باشد. خروجی تغییر نکرده است، زیرا فقط مسئولیت رندر کارت را از WeeklyForecast به WeatherDay منتقل کردهایم.
پارامترها
اکنون برخی مقادیر ثابت را با پارامترهای مؤلفه جایگزین میکنیم. سادهترین راه درک پارامترهای مؤلفه در مدل Razor Component این است که آنها را ویژگیهای عمومیای بدانیم که با صفت ویژهٔ [Parameter] تزئین شدهاند.
بازنمایی درونخطی صفحهٔ 37 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 38 از ۱۲۱بازگشت به فهرست
در ادامه با صفتهای پارامتر دیگری نیز آشنا میشویم، اما صفت استاندارد [Parameter] برای بیشتر سناریوها مناسب است.
حالا که HTML مؤلفه درون WeatherDay جدا شده است، میتوانیم با پذیرش داده از طریق پارامترها، آن را پویاتر کنیم. مؤلفهٔ آبوهوا باید خلاصهٔ وضعیت هوا - مانند Rainy، Sunny یا Cloudy - را همراه با آیکون متناظر نمایش دهد. دما و روز هفته نیز نشان داده میشوند.
برای شروع چند پارامتر میسازیم و همزمان با حل چالشها قابلیتهای دقیقتری اضافه میکنیم. یک بلوک @code ایجاد میکنیم، سه ویژگی را به آن میافزاییم و هرکدام را با [Parameter] تزئین میکنیم.
@code {
[Parameter]
public string Summary { get; set; }
[Parameter]
public int TemperatureC { get; set; }
[Parameter]
public DayOfWeek DayOfWeek { get; set; }
}
اتصال دادهٔ یکطرفه
برای نمایش مقدار پارامتر از اتصال دادهٔ یکطرفه (One-Way Data Binding) استفاده میکنیم. این روش مقدار را در همان نقطهای که در نشانهگذاری قرار دارد مینویسد. در Razor کافی است نام ویژگی را با علامت @ آغاز کنیم؛ مانند @Summary.
برای هر پارامتر، متن ثابت نشانهگذاری را با مقدار متصل به داده جایگزین میکنیم. هنگامی که Razor خروجی نهایی را تولید میکند، همهٔ مقادیر بهطور خودکار به رشته تبدیل میشوند. لازم نیست مقداری مانند TemperatureC را با وجود نوع int بودن، دستی تبدیل کنیم؛ هرچند در صورت نیاز میتوانیم قالب نمایش را سفارشی کنیم.
<div class="card bg-light m-2">
<div class="card-body text-center">
<span class="h1 oi oi-rain"></span>
<h1 class="card-title">@TemperatureC C°</h1>
<p class="card-text">
@Summary weather expected @DayOfWeek
</p>
</div>
</div>
بازنمایی درونخطی صفحهٔ 38 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 39 از ۱۲۱بازگشت به فهرست
اکنون که مؤلفه پارامترهای متصل به داده دارد، به صفحهٔ WeeklyForecast بازمیگردیم و نمونهٔ مؤلفه را بهروزرسانی میکنیم تا از قابلیت جدید استفاده کند. مؤلفهٔ WeatherDay را در صفحه پیدا میکنیم و چند مقدار ثابت برای پارامترهای آن قرار میدهیم.
@foreach (var item in Enumerable.Range(1, 5))
{
<WeatherDay
TemperatureC="20"
Summary="Cloudy"
DayOfWeek="DayOfWeek.Friday">
</WeatherDay>
}
حالا دادهای پویا به صفحهٔ WeeklyForecast اضافه میکنیم. یک WeatherForecastService را به صفحه تزریق و از آن برای تولید داده استفاده خواهیم کرد. این سرویس در قالب پروژهٔ جدید وجود دارد و در پوشهٔ /Data قرار گرفته است. فایل WeatherForecastService.cs را باز کنید و شیوهٔ کار آن را ببینید. مجموعهای به نام Summaries وجود دارد که برای پرکردن ویژگی Summary استفاده میشود. متد GetForecastAsync بر اساس تاریخ آغاز، پنج مقدار تصادفی WeatherForecast تولید میکند.
فهرست Summaries را ساده میکنیم تا فقط شامل Cloudy، Rainy و Sunny باشد؛ این سه مقدار با سه آیکون موجود در پروژه متناظرند.
private static readonly string[] Summaries = new[]
{
//"Freezing", "Bracing", "Chilly", "Cool", "Mild",
//"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
"Cloudy", "Rainy", "Sunny"
};
در WeatherDay از یک ویژگی فقطخواندنی استفاده میکنیم تا بر اساس فیلد Summary آیکون مناسب تعیین شود. یک عبارت سهتایی کوتاه، رشتهٔ Summary را به نام آیکون متناظر تبدیل میکند.
string IconCssClass =>
Summary == "Cloudy" ? "cloud" :
Summary == "Rainy" ? "rain" :
"sun";
اکنون میتوان از ویژگی IconCssClass برای تغییر مقدار CSS آیکون مؤلفه استفاده کرد.
<span class="h1 oi oi-@IconCssClass"></span>
بازنمایی درونخطی صفحهٔ 39 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 40 از ۱۲۱بازگشت به فهرست
تزریق یک سرویس
اکنون مؤلفهٔ WeatherDay میتواند به دادهٔ پویا متصل شود. با دستور @inject، سرویس WeatherForecastService را به صفحه تزریق میکنیم. این وابستگی در دسترس است، زیرا قالب از قبل WeatherForecastService را در Startup.cs ثبت کرده است. با نگاه به متد ConfigureServices میتوانیم نحوهٔ پیکربندی تزریق وابستگی (Dependency Injection یا DI) را ببینیم. روشهای مختلف DI را در فصل دیگری که به این موضوع اختصاص دارد بررسی خواهیم کرد.
services.AddSingleton<WeatherForecastService>();
در صفحهٔ WeeklyForecast، زیر دستور @page، دستور @inject را اضافه میکنیم و هم نوعی را که باید Resolve شود و هم نام متغیری را که نمونهٔ آن نوع دریافت میکند مشخص میکنیم؛ برای نمونه: @inject T myTInstance. دستور @using نیز WeatherForecastService را در دامنه قرار میدهد.
@page "/weeklyforecast"
@using Data;
@inject WeatherForecastService WeatherService
متد چرخهٔ عمر OnInitializedAsync
اکنون که WeatherForecastService در دسترس است، میتوانیم متد GetForecastAsync را فراخوانی کنیم که آرایهای از اشیای WeatherForecast میسازد. داده باید هنگام نخستین مقداردهی مؤلفه بارگذاری شود. مؤلفههای Razor مقداردهی اولیه را از طریق متدهای چرخهٔ عمر OnInitializedAsync و OnInitialized مدیریت میکنند. این متدها بهطور خودکار از کلاس ComponentBase - پایهٔ همهٔ مؤلفههای Razor - به ارث میرسند.
بهترین رویه این است که هرجا ممکن باشد از کد ناهمگام استفاده کنیم، زیرا تجربهٔ کاربری کلی را بهتر میکند. افزون بر این، WeatherForecastService نیز قابلیت ناهمگام دارد؛ بنابراین برای دریافت داده همزمان با مقداردهی مؤلفه از OnInitializedAsync استفاده میکنیم.
یک فیلد برای نگهداری دادهای که در صفحه نمایش داده میشود اضافه میکنیم. آرایهای خالی از WeatherForecast به نام forecasts میسازیم. پس از آن، متد OnInitializedAsync را Override میکنیم و با انتظار برای فراخوانی GetForecastAsync، فیلد forecasts را مقداردهی میکنیم.
بازنمایی درونخطی صفحهٔ 40 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 41 از ۱۲۱بازگشت به فهرست
WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await WeatherService
.GetForecastAsync(DateTime.Now);
}
اکنون که از دادهٔ WeatherService استفاده میکنیم، باید حلقهٔ ثابت نما را با حلقهای جایگزین کنیم که آرایهٔ forecasts را پیمایش میکند. درون حلقهٔ جدید، به عضو جاری ارجاع میدهیم و مقدار ویژگی متناظر را روی پارامتر مؤلفه قرار میدهیم. کامپایلر .NET هوشمند است؛ بنابراین فقط برای مقادیر رشتهای لازم است از علامت @ استفاده کنیم تا رشتهٔ ثابت از مقدار ویژگی تشخیص داده شود.
@*foreach (var item in Enumerable.Range(1, 5))*@
@foreach (var forecast in forecasts)
{
<WeatherDay
TemperatureC="forecast.TemperatureC"
Summary="@forecast.Summary"
DayOfWeek="forecast.Date.DayOfWeek">
</WeatherDay>
}
چون دیگر با دادهٔ ثابت کار نمیکنیم، باید احتمال مقدار null را نیز در نظر بگیریم. مؤلفه یک بار پیش از اجرای OnInitializedAsync و بار دیگر پس از آن رندر میشود؛ بنابراین اگر هنگامی رندر شود که آرایهٔ forecasts هنوز null است، ورود به حلقهٔ foreach باعث پرتاب NullReferenceException خواهد شد.
برای جلوگیری از این وضعیت، از شرط if/else استفاده میکنیم تا هنگام نبود داده پیامی نمایش داده شود و استثنا رخ ندهد.
@if (forecasts == null)
{
<span>No Data</span>
}
else
{
foreach (var forecast in forecasts)
{
<WeatherDay
TemperatureC="forecast.TemperatureC"
Summary="@forecast.Summary"
DayOfWeek="forecast.Date.DayOfWeek">
</WeatherDay>
}
}
بازنمایی درونخطی صفحهٔ 41 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 42 از ۱۲۱بازگشت به فهرست
اکنون با رفتن به مسیر weeklyforecast میتوانیم داده را بهصورت پویا رندر کنیم؛ همانطور که در شکل ۱۴ دیده میشود.
شکل ۱۴: صفحهٔ آبوهوای هفتگی، کارت پیشبینی را برای پنج عضو در عرض صفحه نمایش میدهد.
مؤلفههای WeeklyForecast و WeatherDay در حال کاملشدناند. میتوانیم بر اساس مجموعهای از داده که هنگام مقداردهی اولیهٔ مؤلفه بارگذاری میشود، مؤلفهها را بهصورت پویا پر کنیم. داده از WeeklyForecast و از طریق اتصال دادهٔ یکطرفه مستقیماً به هر مؤلفهٔ WeatherDay جریان مییابد. این یکی از روشهای رایج نمایش داده است و هنگامی که رابط کاربری ثابتی میسازیم بهخوبی کار میکند.
اکنون مؤلفهٔ WeatherDay را گسترش میدهیم و با فراهمکردن یک ناحیهٔ قالب، امکان توسعهٔ بیشتر رابط کاربری آن را ایجاد میکنیم.
محتوای فرزند و قالبها
مؤلفههای قالبپذیر (Templated Components) مؤلفههایی هستند که یک یا چند قالب رابط کاربری را بهعنوان پارامتر میپذیرند. از قالبها میتوان برای سفارشیکردن بخشی از خروجی رندرشدهٔ مؤلفه استفاده کرد. برای نمایش شیوهٔ پیادهسازی قالب، کار را با همان WeatherDay ادامه میدهیم.
بازنمایی درونخطی صفحهٔ 42 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 43 از ۱۲۱بازگشت به فهرست
برای انعطافپذیرترکردن WeatherDay، ناحیهای برای قالب اضافه میکنیم که در شکل ۱۵ با کادر قرمز نشان داده شده است. این بخش به توسعهدهندگان اجازه میدهد هر HTML، کد Razor یا مؤلفهای را بهعنوان محتوای فرزند وارد کنند.
شکل ۱۵: یک کادر قرمز محل ایجاد ناحیهٔ قالب مؤلفه را مشخص میکند.
برای افزودن قالب به WeatherDay از کلاس RenderFragment استفاده میکنیم. RenderFragment نمایانگر بخشی از محتوای رابط کاربری است و بهصورت نمایندهای پیادهسازی میشود که محتوا را در RenderTreeBuilder - DOM مجازی بلیزر - مینویسد. در ظاهر ممکن است تصور شود محتوای فرزند بهصورت HTML خام نوشته میشود؛ اما RenderFragment کاملاً با معماری مؤلفهای بلیزر سازگار است.
در ظاهر ممکن است تصور شود محتوای فرزند بهصورت HTML خام نوشته میشود؛ اما RenderFragment از معماری مؤلفهای بلیزر پیروی میکند.
بازنمایی درونخطی صفحهٔ 43 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 44 از ۱۲۱بازگشت به فهرست
یک RenderFragment به مؤلفهٔ WeatherDay اضافه میکنیم. این عضو دقیقاً مانند هر پارامتر دیگر مؤلفه و با یک ویژگی و صفت [Parameter] افزوده میشود. در این نمونه نام ویژگی را CustomMessage میگذاریم.
@code {
[Parameter]
public RenderFragment CustomMessage { get; set; }
…
[Parameter]
public string Summary { get; set; }
}
برای استفاده از پارامتر، در محل دلخواهِ نشانهگذاری مؤلفه به RenderFragment ارجاع میدهیم. بین عناصر h1 و p، عبارت @CustomMessage قرار میگیرد و قالب در همین نقطه رندر میشود.
<div class="card bg-light m-2">
<div class="card-body text-center">
<span class="h1 oi oi-@IconCssClass"></span>
<h1 class="card-title">@TemperatureC C°</h1>
@CustomMessage
<p class="card-text">
@Summary weather expected @DayOfWeek
</p>
</div>
</div>
هنگام استفاده از WeatherDay، قالب با مشخصکردن نام آن در قالب یک تگ HTML یعنی <CustomMessage> اعمال میشود. درون این تگ میتوان از نشانهگذاری Razor، HTML، مؤلفههای دیگر یا هر ترکیبی از این موارد استفاده کرد.
این قابلیت را با یک شرط ساده به نمونه اضافه میکنیم: اگر پیشبینی Rainy بود، هشدار ویژهای نمایش داده شود. درون CustomMessage مقداری کد Razor و یک عنصر ساده با کلاس CSS بوتاسترپ alert alert-danger قرار میدهیم.
<WeatherDay
TemperatureC="forecast.TemperatureC"
Summary="@forecast.Summary"
DayOfWeek="forecast.Date.DayOfWeek">
<CustomMessage>
@if (forecast.Summary == "Rainy")
{
<div class="alert alert-danger">
Tornado Warning!
</div>
}
</CustomMessage>
بازنمایی درونخطی صفحهٔ 44 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 45 از ۱۲۱بازگشت به فهرست
</WeatherDay>
وقتی برنامه را اجرا و صفحهٔ weeklyforecast را مشاهده کنیم، اکنون مطابق شکل ۱۶ کادر هشدار Tornado Warning! درون مؤلفهٔ WeatherDay نمایش داده میشود.
شکل ۱۶: پیشبینی هفتگی با هشدار گردباد که در ناحیهٔ قالب مؤلفهٔ WeatherDay نمایش داده شده است.
تا اینجا، مؤلفههای WeeklyForecast و WeatherDay بیشتر قابلیتهایی را که معمولاً در معماری مؤلفهای میبینیم دارند. بااینحال، یکی از جنبههای مهم توسعهٔ رابط کاربری هنوز بررسی نشده است: تعاملپذیری. در ادامه با فراهمکردن امکان انتخاب یک عضو در پیشبینی هفتگی، نحوهٔ مدیریت رویدادها در بلیزر را بررسی میکنیم.
مدیریت رویداد
وقتی در C# به رویدادها و نمایندهها فکر میکنیم، احتمالاً کلیدواژههای event و delegate یا انواع نمایندهٔ Action و Func به ذهنمان میآیند. این گزینهها هنگام کار با اسمبلیهای استاندارد C# از نظر فنی معتبرند. اما کار با رابط کاربری برنامهٔ بلیزر و مؤلفههای Razor، الگوی معماری متفاوت و نمایندهٔ ویژهای به نام EventCallback را معرفی میکند.
EventCallback نوعی نماینده برای در معرض قراردادن رویدادها میان مؤلفهها است. یک مؤلفهٔ والد میتواند متد Callback خود را به EventCallback مؤلفهٔ فرزند اختصاص دهد.
بازنمایی درونخطی صفحهٔ 45 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 46 از ۱۲۱بازگشت به فهرست
وقتی EventCallback به این روش استفاده شود، مؤلفهٔ والد و فرزند هنگام رخدادن رویدادها بهطور خودکار دوباره رندر میشوند. استفاده از event، delegate، Action یا Func ممکن است باعث شود رابط کاربری مطابق انتظار بهروزرسانی نشود. به همین دلیل، در مؤلفههای Razor باید همیشه از EventCallback استفاده شود.
EventCallback نوعی نماینده است که برای ارائهٔ رویدادها میان مؤلفهها استفاده میشود.
برای نمایش نحوهٔ استفاده از EventCallback، کار را با مؤلفههای WeeklyForecast و WeatherDay ادامه میدهیم. قابلیتی اضافه میکنیم تا کاربر بتواند با کلیک روی یک روز، آن WeatherDay را در پیشبینی هفتگی انتخاب کند.
ابتدا پارامتر جدیدی به نام OnSelected به WeatherDay میافزاییم. این پارامتر باید یک EventCallback<DayOfWeek> باشد؛ یعنی رویداد، مقداری از نوع DayOfWeek را بهعنوان آرگومان بازمیگرداند. از این مقدار میتوانیم برای تشخیص عضوی که OnSelected را فعال کرده استفاده کنیم.
[Parameter]
public EventCallback<DayOfWeek> OnSelected { get; set; }
زیر پارامتر OnSelected، یک مدیریتکنندهٔ رویداد ساخته میشود که مسئول فعالکردن نمایندهٔ رویداد OnSelected در مؤلفهٔ والد است. برای فراخوانی OnSelected، متد InvokeAsync را با DayOfWeek جاری بهعنوان آرگومان فراخوانی میکنیم.
void HandleOnSelected()
{
OnSelected.InvokeAsync(this.DayOfWeek);
}
در ادامهٔ منطق WeatherDay، ویژگی Selected اضافه میشود تا مشخص کند مؤلفه در وضعیت انتخابشده قرار دارد یا نه. این ویژگی هم برای اتصال مقدار انتخابشده به مؤلفه استفاده میشود و هم برای تعیین CSS متناظر رابط کاربری.
با ویژگی خصوصی SelectedCss، بر اساس مقدار Selected کلاس CSS مناسب تعیین میشود. اگر عضو انتخاب شده باشد، کلاسهای bg-primary text-white مؤلفه را برجسته و رنگ متن را معکوس میکنند؛ در غیر این صورت مقدار پیشفرض bg-light خواهد بود.
[Parameter]
public bool Selected { get; set; }
بازنمایی درونخطی صفحهٔ 46 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 47 از ۱۲۱بازگشت به فهرست
private string SelectedCss => Selected
? "bg-primary text-white"
: "bg-light";
اکنون در نشانهگذاری مؤلفه میتوانیم هنگام کلیک کاربر، نمایندهٔ رویداد HandleOnSelected را فعال کنیم. به بیرونیترین عنصر div یک مدیریتکنندهٔ onclick متصل و آن را به متد HandleOnSelected نسبت میدهیم. همچنین div را تغییر میدهیم تا از مقدار SelectedCss استفاده کند و ظاهر رابط کاربری را تغییر دهد.
<div class="card m-2 @SelectedCss"
@onclick="HandleOnSelected">
<div class="card-body text-center">
…
</div>
</div>
اکنون WeatherDay قابل انتخاب است و با چند تغییر در کلاس WeatherForecast و مؤلفهٔ WeeklyForecast میتوانیم قابلیت جدید را به کار بگیریم. چون WeatherForecast داده و وضعیت نما را فراهم میکند، ویژگی Selected را به آن میافزاییم تا بتوانیم در مؤلفهها به آن متصل شویم.
public class WeatherForecast
{
public bool Selected { get; set; }
…
نشانهگذاری WeeklyForecast برای پیوند همهٔ اجزا چند بهروزرسانی دریافت میکند. در محل استفاده از WeatherDay، ویژگیهای جدید اعمال میشوند. متد OnSelected نمایندهٔ HandleItemSelected را میگیرد و مقدار Selected به ویژگی Selected عضو forecast متصل میشود.
<WeatherDay
TemperatureC="forecast.TemperatureC"
Summary="@forecast.Summary"
DayOfWeek="forecast.Date.DayOfWeek"
OnSelected="HandleItemSelected"
Selected="forecast.Selected">
پس از تکمیل تغییرات، میتوانیم برنامه را اجرا کنیم، به صفحهٔ weeklyforecast برویم و مطابق شکل ۱۷ اعضا را از نما انتخاب کنیم.
بازنمایی درونخطی صفحهٔ 47 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 48 از ۱۲۱بازگشت به فهرست
شکل ۱۷: عضو دوم فهرست پس از کلیک کاربر انتخاب شده است.
میتوانیم با افزودن هر نوع منطق سفارشی به متد HandleItemSelected این مفهوم را بیشتر گسترش دهیم. تصور کنید هنگام انتخاب یک عضو چه قابلیتهایی میتوان افزود؛ برای نمونه نمایش پیشبینی ساعتی، فهرستکردن کسبوکارهای تعطیل یا پیشنهاد فعالیتهای مناسب برای آن وضعیت هوا. این مفهوم در بسیاری از برنامههای تجاری که دادههایشان رابطهٔ والد-فرزند دارند قابل استفاده است.
جمعبندی مبانی مؤلفههای بلیزر
در سراسر این فصل، مبانی ساخت مؤلفه مانند دستورها، پارامترها، محتوای فرزند/قالبها، مسیریابی و مدیریت رویداد را آموختیم. هر نمونه، فرایند ساخت یک مؤلفه را مرحلهبهمرحله نشان داد و همزمان موضوعاتی را معرفی کرد که در بخشهای بعدی کتاب با جزئیات بیشتری بررسی خواهند شد. هدف این فصل ارائهٔ دانشی بود که هنگام استفاده از چارچوب بلیزر فوراً ما را به بهرهوری برساند.
بازنمایی درونخطی صفحهٔ 48 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 49 از ۱۲۱بازگشت به فهرست
رویکرد کدِ پشتصحنه
در فصل پیش دربارهٔ معماری مؤلفه آموختیم. در این فصل با استفاده از کلاسهای partial، نشانهگذاری مؤلفه را از منطق آن جدا میکنیم.
در این فصل میآموزیم چه زمانی استفاده از رویکرد کدِ پشتصحنه (Code-Behind) در توسعهٔ بلیزر سودمند است، چه مزایایی دارد و هنگام بازآرایی مؤلفههای موجود چه انتظاری باید داشته باشیم.
در بلیزر، معماری پیشفرض مؤلفه به این صورت است که تمام نشانهگذاری و منطق در یک فایل .razor ترکیب میشوند. وقتی مؤلفهها سادهاند، این رویکرد خوب کار میکند؛ اما با افزایش پیچیدگی، مدیریت تمام بخشهای مؤلفه دشوار میشود. رویکرد Code-Behind اجازه میدهد نشانهگذاری و منطق در فایلهای جداگانه قرار گیرند. در این فصل بررسی میکنیم چه زمانی این روش مفید است، چه سودی میبریم و در بازآرایی مؤلفههای موجود با چه مواردی روبهرو خواهیم شد.
رویکرد تکفایلی
کار را با مرور سریع رویکرد تکفایلی آغاز میکنیم. مؤلفهٔ FetchData از قالب پیشفرض بلیزر - که در شکل ۱۸ نمایش داده شده - نمونهٔ ما خواهد بود. مؤلفهٔ FetchData با پیمایش دادهای که یک سرویس فراهم میکند، جدولی HTML از پیشبینی آبوهوا نمایش میدهد. مؤلفه از دستورها، HTML/Razor و بلوک @code استفاده میکند؛ همگی اجزای معمول یک مؤلفهاند.
بازنمایی درونخطی صفحهٔ 49 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 50 از ۱۲۱بازگشت به فهرست
شکل ۱۸: مؤلفهٔ FetchData با رویکرد تکفایلی.
نشانهگذاری این مؤلفه نسبتاً طولانی است. با بیش از ۳۰ خط، خود نشانهگذاری طول فایل را افزایش میدهد و برای یافتن منطق مؤلفه که در بلوک @code قرار دارد مجبور میشویم به پایین پیمایش کنیم. در شکل ۱۹ میبینیم پیش از رسیدن به منطق مؤلفه، نزدیک به ۴۰ خط نشانهگذاری وجود دارد.
بازنمایی درونخطی صفحهٔ 50 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 51 از ۱۲۱بازگشت به فهرست
شکل ۱۹: بخش نشانهگذاری مؤلفهٔ FetchData در ۳۷ خط امتداد دارد.
به خاطر داشته باشید این مؤلفه هنوز در واقع بسیار ساده است. با افزایش پیچیدگی، هر بخش نیز بزرگتر خواهد شد؛ یعنی در نهایت دستورها، عبارتهای using، نشانهگذاری و منطق بیشتری در بلوک کد خواهیم داشت. هرچند داشتن یک فایل همهکاره ممکن است راحت به نظر برسد، بهدلیل پیمایش مداوم میان نشانهگذاری و منطق، نگهداری آن بهتدریج خستهکننده میشود.
مؤلفهٔ FetchData را با استفاده از فایل Code-Behind بازآرایی میکنیم تا ببینیم چگونه تجربهٔ کلی توسعهدهنده بهتر میشود.
رویکرد Code-Behind
Code-Behind اصطلاح رایجی برای تکنیکی است که در آن یک فایل کد جداگانه، تمام منطق صفحه، نما یا مؤلفهٔ متناظر را در بر میگیرد. ساخت Code-Behind در بلیزر چند مرحله دارد، اما خوشبختانه خود چارچوب از آن پشتیبانی میکند و راهاندازی بسیار ساده است. برای تکمیل Code-Behind باید یک کلاس بسازیم و سپس آن را به نشانهگذاری پیوند دهیم. پس از شکلگیری این ساختار، میتوانیم منطق موجود را به فایل جدید انتقال دهیم.
ساخت و پیوند Code-Behind
بازنمایی درونخطی صفحهٔ 51 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 52 از ۱۲۱بازگشت به فهرست
ابتدا به یک فایل کلاس نیاز داریم که Code-Behind را نمایندگی کند. این فایل یک کلاس partial استاندارد .NET است؛ بااینحال، نامگذاری اهمیت زیادی دارد. هنگام ساخت فایل Code-Behind باید هم نام فایل و هم نام کلاس را با دقت انتخاب کنیم، زیرا میتوانند تأثیر زیادی بر تجربهٔ توسعه داشته باشند.
از نام فایل آغاز میکنیم. هنگام افزودن فایل باید از قرارداد رایج زیر پیروی کنیم:
[componentName].razor.cs
Visual Studio پسوند .razor.cs را میشناسد و فایل را مطابق شکل ۲۰ بهدرستی زیر فایل مؤلفه در پنجرهٔ File Explorer قرار میدهد. چون مؤلفه از قبل نام یک کلاس را اشغال کرده است، باید کلاس جدید را با کلیدواژهٔ partial تعریف کنیم.
public partial class FetchData { }
شکل ۲۰: Visual Studio فایلهایی با نام یکسان و الگوی پسوند .razor.cs را بهصورت تودرتو نمایش میدهد.
اکنون میتوانیم تمام کد بلوک @code و چند دستور دیگر را به Code-Behind منتقل کنیم.
انتقال @code به Code-Behind
بیشتر منطق موجود در بلوک @code بدون هیچ تغییری مستقیماً به Code-Behind کپی میشود؛ بااینحال چند بهروزرسانی کوچک لازم است.
هنگام انتقال کد، اگر Code-Behind بهطور خودکار یک فضای نام را دریافت نکند، ممکن است به عبارتهای using بیشتری نیاز باشد. ابزارهای استاندارد در اینجا بسیار کمککنندهاند و میانبر [Ctrl]+[.] بهسرعت فضاهای نام گمشده را اضافه میکند.
بازنمایی درونخطی صفحهٔ 52 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 53 از ۱۲۱بازگشت به فهرست
کافی است هر موردی را که زیر آن خط قرمز کشیده شده انتخاب کنید و کلیدهای [Ctrl]+[.] را فشار دهید. اگر مشکل از فضای نام گمشده باشد، اصلاح پیشنهادی را بپذیرید تا تغییر اعمال شود.
// added by [ctrl] + [.]
using BlazorBookExamples.WeatherComponent.Data;
using Microsoft.AspNetCore.Components;
پس از انتقال همهٔ کدها، یک مرحلهٔ نهایی باقی میماند: تزریق وابستگی (DI).
تزریق وابستگی در Code-Behind بلیزر
تزریق وابستگی در نشانهگذاری Razor با دستور @inject مدیریت میشود. در کلاسهای Code-Behind نیز این قابلیت وجود دارد، اما نحو آن کاملاً متفاوت است و در نگاه نخست شاید آشکار نباشد. برای استفاده از تزریق وابستگی در فایل Code-Behind، صفت [Inject] را روی ویژگیای از نوع موردنظر قرار میدهیم؛ برای نمونه: [Inject] MyType MyProp { get; set; }.
[Inject]
WeatherForecastService ForecastService { get; set; }
با افزودن صفت [Inject]، مؤلفه اکنون میتواند سرویس را Resolve کند و دیگر به دستور مربوطه در نشانهگذاری Razor نیاز نداریم. میتوانیم نشانهگذاری را پاکسازی کنیم و دستورهای @inject و @using و همچنین بلوک @code را حذف نماییم.
نکات پایانی دربارهٔ Code-Behind
در آغاز، دستورها، نشانهگذاری و منطق مؤلفه همگی در یک فایل قرار داشتند. رویکرد تکفایلی بهسبب سادگی مزایایی دارد و برای مؤلفههای کوچک عالی است، اما در مقیاس بزرگ خوب عمل نمیکند. وقتی مؤلفه به نشانهگذاری یا منطق پیچیده نیاز پیدا کند، بهسادگی در فایل بزرگ گم میشویم یا بهدلیل دو شیوهٔ متفاوت بیان کد دچار سردرگمی میشویم. افزون بر این، دسترسی به برخی از ارزشمندترین ابزارهای بهرهوری Visual Studio را از دست میدهیم.
تبدیل یک مؤلفهٔ موجود به Code-Behind بسیار آسان است. فقط چند تغییر مربوط به عبارتهای using و تزریق وابستگی لازم است.
بازنمایی درونخطی صفحهٔ 53 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 54 از ۱۲۱بازگشت به فهرست
در بیشتر موارد، Visual Studio حتی ما را برای انجام اصلاحات درست و رسیدن به کامپایل موفق راهنمایی میکند.
شکل ۲۱: نمای کنارهم مؤلفهٔ FetchData؛ نشانهگذاری در سمت چپ و منطق در سمت راست.
پس از تکمیل انتقال، از نقشهای روشن و جداگانه برای نشانهگذاری و منطق و همچنین پیمایش بسیار کمتر بهرهمند میشویم. اگر لازم باشد کل مؤلفه را یکجا ببینیم، میتوانیم دو پنجره را مانند شکل ۲۱ کنار هم قرار دهیم و بر کار جاری تمرکز کنیم.
بازنمایی درونخطی صفحهٔ 54 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 55 از ۱۲۱بازگشت به فهرست
اتصال داده
فصلهای پیش ما را با نحو Razor آشنا کردند. در این فصل، شیوهٔ کار با اتصال داده، نحوهٔ تعریف اتصال و زمان فعالشدن رویدادهای اتصال را بررسی میکنیم.
اتصال داده (Data Binding) جریان داده میان یک یا چند مؤلفه و متغیر است. اتصال یکطرفه هنگامی مفید است که باید داده را در رابط کاربری نمایش دهیم؛ بهویژه برای سناریوهای فقطخواندنی. اتصال دادهٔ دوطرفه معمولاً برای واداشتن چند مؤلفه به بهروزرسانی وضعیت خود بر اساس مؤلفههای دیگر سامانه استفاده میشود. بنابراین، اتصال دوطرفه بیشتر همراه با مؤلفههای ورودی به کار میرود؛ جایی که مقدار یک ورودی مستقیماً از مقدار ورودی دیگری اثر میپذیرد.
برای درک بهتر نحوهٔ مدیریت اتصال داده در بلیزر، نمونهای ساده راهاندازی میکنیم. ابزاری برای تبدیل اینچ و سانتیمتر میسازیم که از دو ورودی HTML برای تبدیل در هر دو جهت استفاده میکند. نمونه را در چند مرحله میسازیم و هر مرحله یکی از مفاهیم اصلی اتصال داده را آشکار میکند. شکل ۲۲ نمونهٔ نهایی را نشان میدهد که در مراحل بعد بررسی خواهیم کرد.
بازنمایی درونخطی صفحهٔ 55 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 56 از ۱۲۱بازگشت به فهرست
شکل ۲۲: در این نمونه، هرکدام از ورودیها میتوانند مقداری را به اینچ یا سانتیمتر تبدیل کنند. دو ورودی با اتصال دادهٔ دوطرفه همگام نگه داشته میشوند؛ برای نمونه: 1 in = 2.54 cm.
برای این مجموعه نمونهها، مؤلفهای جدید میسازیم. زیر پوشهٔ Pages مؤلفهای به نام Converter اضافه و مسیر صفحه را نیز متناسب با آن تنظیم کنید؛ مانند @page "/converter".
نخستین آزمایش، مشاهدهٔ رفتار پیشفرض دستور @ هنگام استفاده روی یک عنصر HTML است. کار را با افزودن یک ورودی HTML به مؤلفه و تنظیم مقدار آن روی ۱ و نوع آن روی number آغاز میکنیم.
<input value="1" type="number" />
اجرای برنامه در این مرحله صفحهای چندان هیجانانگیز تولید نمیکند. صفحه ورودیای دارد که مقدار پیشفرض آن ۱ است و اجازه میدهد هر مقدار عددی تعیین کنیم. هرچند عملکرد زیادی ندارد، زمینهٔ لازم برای نمونهٔ بعدی را فراهم میکند.
اتصال یکطرفه
از اتصال دادهٔ یکطرفه برای تنظیم مقادیر عددی مؤلفه استفاده میکنیم. یک عنصر HTML دوم، یعنی تگ پاراگراف p، اضافه میکنیم که متن «مقدار اینچ برابر است با: ۱» را نمایش دهد.
<input value="1" type="number" />
<p>The value of inches is: 1</p>
پس از نشانهگذاری، بلوک @code افزوده میشود تا منطق مؤلفه را در آن بنویسیم. درون بلوک کد، فیلدی از نوع double با نام inches اضافه میکنیم و مقدار پیشفرض آن را ۱ قرار میدهیم.
@code {
بازنمایی درونخطی صفحهٔ 56 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 57 از ۱۲۱بازگشت به فهرست
double inches = 1; // default value
}
حالا که فیلد inches در دسترس است، به بخش نشانهگذاری بازمیگردیم و مقدار ثابت ۱ را هم در ورودی و هم در متن پاراگراف با آن جایگزین میکنیم.
<input value="@inches" type="number" />
<p>The value of inches is: @inches</p>
@code {
double inches = 1; // default value
}
لحظهای تصور کنید این مؤلفه چگونه رفتار خواهد کرد: وضعیت اولیه چگونه است و وقتی عدد تازهای در ورودی وارد شود چه اتفاقی میافتد؟ در ابتدا میتوان تشخیص داد که ورودی مقدار ۱ را دارد و متن میگوید «مقدار اینچ ۱ است».
آنچه ممکن است غافلگیرکننده باشد، رفتار برنامه پس از ورود عدد جدید است. وقتی مقدار ورودی تغییر کند، فقط مقدار خود ورودی عوض میشود و مقدار فیلد inches تغییر نمیکند. هنگام نوشتن value="@inches"، ویژگی value به عدد جاری در inches تنظیم میشود؛ درست مانند آنکه در نمونهٔ قبلی آن را دستی روی ۱ گذاشته باشیم. به بیان دیگر، value بهعنوان یک ارجاع زنده به فیلد inches تنظیم نشده است. این خروجی فقطخواندنی، اتصال دادهٔ یکطرفه محسوب میشود.
هنگام نوشتن value="@inches"، مقدار value به عدد جاری در inches تنظیم میشود؛ درست مانند آنکه آن را دستی روی ۱ قرار داده باشیم. این مقدار یک ارجاع زنده به فیلد inches نیست.
رویدادهای اتصال
برای آنکه هنگام تغییر مقدار ورودی توسط کاربر، هم فیلد inches و هم متن خروجی بهدرستی بهروزرسانی شوند، باید از یک رویداد استفاده کنیم. با مدیریت رویداد onchange ورودی، میتوانیم منطق کسبوکار لازم برای بهروزرسانی فیلد inches را اجرا کنیم.
ابتدا مدیریتکنندهای به نام UpdateValue میسازیم. این متد یک ChangeEventArgs میگیرد که مقدار تعیینشده توسط کاربر را در خود دارد. سپس میتوانیم با آرگومان رویداد، فیلد inches را روی مقدار جدید قرار دهیم. چون inches از نوع double است، باید محافظتهای لازم را اضافه و آرگومان رویداد را بهدرستی Parse کنیم.
بازنمایی درونخطی صفحهٔ 57 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 58 از ۱۲۱بازگشت به فهرست
void UpdateValue(ChangeEventArgs e)
{
double val = 0; // Failing to parse will set to 0
double.TryParse(e.Value.ToString(), out val);
inches = val;
}
اکنون در نشانهگذاری، نمایندهٔ UpdateValue را به رویداد onchange ورودی اختصاص میدهیم.
<input value="@inches"
@onchange="UpdateValue"
type="number" />
همهچیز برای همگام نگهداشتن مقدار ورودی، فیلد inches و متن نمایش آماده است. وقتی کاربر مقدار ورودی را تغییر دهد، متد UpdateValue فراخوانی و فیلد پشتیبان بهروزرسانی میشود. از آنجا که onchange یک EventCallback است، رابط کاربری دوباره رندر میشود و متن خروجی نیز مطابق انتظار تغییر میکند.
اتصال دوطرفه
استفاده از ویژگی value ورودی و مدیریت دستی رویداد onchange بسیار مفید است، اما راه سادهتری نیز وجود دارد. نمونه را با اتصال دوطرفه و دستور @bind ادامه میدهیم. نکتهٔ جالب این است که پیادهسازی ما تا اینجا بسیار شبیه نحوهٔ کار داخلی @bind است.
وقتی @bind بهصورت پیشفرض روی یک ورودی فراخوانی شود، ویژگی value تنظیم و بهروزرسانی میشود. افزون بر این، onchange رویداد پیشفرضی است که اتصال داده را فعال میکند. بلیزر همچنین کارهای اضافی لازم برای تبدیل خودکار نوعها را انجام میدهد.
در مؤلفهٔ ورودی نمونه، میتوانیم ویژگی value را با @bind جایگزین کنیم. چون @bind رویداد onchange و تبدیلهای نوع لازم را نیز مدیریت میکند، دستور @onchange و متد UpdateValue قابل حذفاند. نتیجه این است که هر سه مقدار - فیلد inches، مقدار ورودی و نمایش inches - همگام میشوند؛ همانطور که در شکل ۲۳ دیده میشود.
<input type="number" @bind="inches" />
<p>The value of inches is: @inches</p>
@code {
double inches = 1; // default value
}
بازنمایی درونخطی صفحهٔ 58 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 59 از ۱۲۱بازگشت به فهرست
شکل ۲۳: با تغییر مقدار ورودی، فیلد پشتیبان بهروزرسانی میشود و متن «The value of inches» دوباره رندر میگردد.
اکنون که یک نمونهٔ سادهٔ اتصال دوطرفه داریم، آن را با افزودن ورودی دوم برای سانتیمتر گسترش میدهیم. ورودی سانتیمتر تقریباً با ورودی اینچ یکسان است. فیلدی به نام centimeters با مقدار اولیهٔ ۲٫۵۴ میسازیم و یک ورودی عددی متصل به آن اضافه میکنیم.
<input type="number" @bind="inches" />
<input type="number" @bind="centimeters" />
@code {
double inches = 1; // default value
double centimeters = 2.54; // default value
}
هر ورودی هنوز بهطور مستقل به فیلد متناظر خود متصل است. باید مقداری منطق کسبوکار برای انجام تبدیل واقعی اضافه کنیم. چون @bind مدیریتکنندهٔ رویداد را درون خود پنهان میکند، از ویژگیهای C# برای انجام کار استفاده خواهیم کرد.
ویژگیای به نام Inches اضافه میکنیم و اتصال را از فیلد inches به این ویژگی تغییر میدهیم. ویژگی Inches هنگام خواندن، فقط فیلد پشتیبان را برمیگرداند. هنگام تنظیم مقدار Inches، تبدیل با ضرب مقدار در ۲٫۵۴ انجام میشود و هر دو فیلد centimeters و inches بهروزرسانی میشوند. اتصال داده تضمین میکند تغییرات ایجادشده توسط setter در رابط کاربری منعکس شوند.
double inches = 1; // default value
public double Inches
{
get => inches;
set
{
centimeters = value * 2.54;
inches = value;
}
}
بازنمایی درونخطی صفحهٔ 59 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 60 از ۱۲۱بازگشت به فهرست
همین منطق را برای سانتیمتر تکرار میکنیم: ویژگی دیگری میافزاییم و هنگام تنظیم مقدار، تبدیل لازم برای فیلد inches را اعمال میکنیم. عناصر HTML دیگری مانند برچسبها نیز برای تکمیل رابط کاربری افزوده میشوند.
@page "/converter"
<h1>Convert Inch to/from Centimeter</h1>
<label>Inches</label>
<input type="number" @bind="Inches" />
<span> = </span>
<input type="number" @bind="Centimeters" />
<label>Centimeters</label>
@code {
double inches = 1; // default value
double centimeters = 2.54; // default value
public double Inches
{
get => inches;
set
{
centimeters = value * 2.54;
inches = value;
}
}
public double Centimeters
{
get => centimeters;
set
{
inches = value / 2.54;
centimeters = value;
}
}
}
اکنون ابزار تبدیل نمایشدادهشده در شکل ۲۲ کامل است. ورودیها بهطور کامل به داده متصلاند و وقتی کاربر هرکدام از مقادیر را تغییر دهد، همهٔ مقدارها همگام باقی میمانند.
اتصال دوطرفهٔ پیشرفته
بازنمایی درونخطی صفحهٔ 60 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 61 از ۱۲۱بازگشت به فهرست
مؤلفهٔ تبدیل کامل شده و اکنون با تغییر هرکدام از ورودیها بهروزرسانی میشود. به هدف رسیدهایم، اما هنوز جای بهبود وجود دارد. درحالحاضر، پیش از آنکه اتصال داده انجام شود باید رویداد onchange فعال گردد؛ یعنی بهروزرسانی زمانی رخ میدهد که عنصر تمرکز خود را از دست بدهد، مثلاً وقتی روی جای دیگری کلیک کنیم یا با کلید Tab از ورودی خارج شویم.
بهجای رفتار پیشفرض onchange، نمونه را طوری تغییر میدهیم که به oninput متصل شود؛ رویدادی که بلافاصله هنگام تایپ رخ میدهد. برای تغییر رویداد پیشفرض اتصال، از دستور @bind:event استفاده و نام رویداد "oninput" را مشخص میکنیم.
<input type="number"
@bind="Inches"
@bind:event="oninput" />
<input type="number"
@bind="Centimeters"
@bind:event="oninput" />
اکنون رابط کاربری همزمان با تایپ کاربر یا هر تغییر دیگری در مقدار ورودی بهروزرسانی میشود.
دستور اتصال انعطافپذیر است و میتوان آن را برای ویژگیهای مشخص نیز پیکربندی کرد. برای نمونه، میتوان ویژگی پیشفرض و رویداد متناظر را با نحو زیر تعیین کرد:
@bind-propertyName @bind-propertyName:event="eventName"
نمونهٔ زیر همان اثر قبلی را دارد، اما نشان میدهد چگونه بهطور صریح به ویژگی value متصل شویم.
<input type="number"
@bind-value="Inches"
@bind-value:event="oninput" />
<input type="number"
@bind-value="Centimeters"
@bind-value:event="oninput" />
جمعبندی اتصال داده
اتصال داده در بلیزر نهفقط راحت، بلکه انعطافپذیر است. چه از اتصال یکطرفه استفاده کنیم و چه دوطرفه، کنترل کاملی بر صفت اتصال و رویدادی داریم که بهروزرسانی را فعال میکند. این سادگی، نیاز به نوشتن دستی کدهای تکراری را کاهش میدهد. هنگامی که اتصال دوطرفه همراه با getter و setterهای ویژگیهای C# استفاده شود، میتوانیم شیوهٔ مدیریت مقادیر متصلشده را با دقت تنظیم کنیم.
بازنمایی درونخطی صفحهٔ 61 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 62 از ۱۲۱بازگشت به فهرست
توضیح RenderTree بلیزر
بلیزر بر پایهٔ انتزاعی از DOM به نام RenderTree ساخته شده است. در این فصل میآموزیم انتزاع DOM دقیقاً چیست، RenderTree برای چه کاری استفاده میشود و چرا توسعهدهندگان بلیزر باید آن را بشناسند.
انتزاع مدل شیء سند (Document Object Model یا DOM) ممکن است ترسناک و پیچیده به نظر برسد، اما در برنامههای وب مدرن به امری معمول تبدیل شده است. دلیل اصلی این است که بهروزرسانی محتوای رندرشده در مرورگر از نظر محاسباتی پرهزینه است. انتزاعهای DOM میان برنامه و مرورگر واسطه میشوند تا مقدار بخشی از صفحه که دوباره رندر میشود کاهش یابد.
برای درک واقعی تأثیر RenderTree بلیزر بر برنامه، ابتدا باید مبانی را مرور کنیم.
با یک تعریف کوتاه از DOM آغاز میکنیم. مدل شیء سند یا DOM یک رابط مستقل از پلتفرم و زبان است که سند XML یا HTML را بهصورت ساختار درختی در نظر میگیرد. در ساختار درختی DOM، هر گره یک شیء است که بخشی از سند را تشکیل میدهد؛ بنابراین DOM ساختار سندی با یک درخت منطقی است.
DOM رابطی مستقل از پلتفرم و زبان است که سند XML یا HTML را بهصورت ساختار درختی در نظر میگیرد.
بازنمایی درونخطی صفحهٔ 62 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 63 از ۱۲۱بازگشت به فهرست
هنگامی که برنامهٔ وب در مرورگر بارگذاری میشود، یک DOM جاوااسکریپتی ساخته میشود. این درخت از اشیا بهعنوان رابط میان جاوااسکریپت و سند واقعی در مرورگر عمل میکند. هنگام ساخت برنامههای وب پویا یا برنامههای تکصفحهای (SPA) با جاوااسکریپت، از سطح API مربوط به DOM استفاده میکنیم.
استفاده از DOM برای ساخت، بهروزرسانی و حذف عناصر HTML، تغییر CSS و دیگر ویژگیها «دستکاری DOM» (DOM Manipulation) نامیده میشود. افزون بر دستکاری DOM، میتوانیم با کمک آن رویدادها را ایجاد و مدیریت کنیم.
در نمونهٔ کد زیر، یک صفحهٔ وب پایه با دو عنصر h1 و p داریم. هنگامی که مرورگر سند را بارگذاری میکند، DOMای ساخته میشود که عناصر HTML را نمایندگی میکند. در شکل ۲۴، نمایی از DOM را بهصورت گرههای یک درخت میبینیم.
<!DOCTYPE html>
<html>
<body>
<h1>Hello World</h1>
<p id="beta">This is a sample document.</p>
</body>
</html>
شکل ۲۴: یک سند HTML بهصورت درختی از گرهها بارگذاری میشود؛ هر شیء نمایندهٔ عنصری در DOM است.
بازنمایی درونخطی صفحهٔ 63 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 64 از ۱۲۱بازگشت به فهرست
با جاوااسکریپت میتوانیم با ارجاع صریح به اشیای درخت در DOM حرکت کنیم. از گرهٔ ریشهٔ document آغاز میکنیم و در فرزندان اشیا پیش میرویم تا به شیء یا ویژگی موردنظر برسیم. برای نمونه، میتوانیم فرزند دوم شاخهٔ body را با document.body.children[1] بگیریم و سپس مقدار innerText را بهعنوان ویژگی بخوانیم.
document.body.children[1].innerText
"This is a sample document."
راه سادهتر برای دریافت همان عنصر، استفاده از تابعی است که DOM را بر اساس یک پرسوجوی مشخص جستوجو میکند. چند متد کمکی برای جستوجوی DOM با انتخابگرهای مختلف وجود دارد. برای نمونه، عنصر p را با شناسهٔ beta و تابع getElementById دریافت میکنیم.
document.getElementById("beta").innerText
"This is a sample document."
در طول تاریخ وب، چارچوبها کار با DOM را سادهتر کردهاند. jQuery چارچوبی با API گسترده برای دستکاری DOM است. در نمونهٔ زیر دوباره متن عنصر p را دریافت میکنیم. با متد $ در jQuery میتوانیم بهآسانی عنصر را با صفت id پیدا و به متن آن دسترسی پیدا کنیم.
//jQuery
$("#beta").text()
"This is a sample document."
نقطهٔ قوت jQuery متدهای کمکیای است که مقدار کد لازم برای یافتن و دستکاری اشیا را کاهش میدهند. بااینحال، بزرگترین ضعف این رویکرد، مدیریت ناکارآمد بهروزرسانیها بهسبب تغییر مستقیم عناصر DOM است. چون دستکاری مستقیم DOM از نظر محاسباتی پرهزینه است، باید با احتیاط انجام شود.
چون دستکاری مستقیم DOM از نظر محاسباتی پرهزینه است، باید با احتیاط انجام شود.
در بیشتر برنامهها معمول است که چند عملیات برای بهروزرسانی DOM انجام شود. با رویکرد معمول جاوااسکریپت یا jQuery ممکن است یک گره را از درخت حذف و محتوای جدیدی جایگزین آن کنیم. هنگام این نوع بهروزرسانی، عناصر و فرزندانشان اغلب حتی وقتی نیازی به تغییر ندارند حذف و دوباره ساخته میشوند.
بازنمایی درونخطی صفحهٔ 64 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 65 از ۱۲۱بازگشت به فهرست
در نمونهٔ زیر، چند عنصر مشابه با انتخابگر عام n-elements حذف میشوند. سپس عناصر دوباره جایگزین میشوند؛ حتی اگر فقط نیازمند اصلاح بوده باشند. همانطور که در شکل ۲۵ میبینیم، عناصر زیادی حذف و جایگزین میشوند، درحالیکه تنها دو مورد به بهروزرسانی نیاز داشتند.
// 1 = initial state
$("n-elements").remove() // 2-3
$("blue").append(modifiedElement1) // 4
$("green").append(modifiedElement2) // 4
$("orange").append(modifiedElement3) // 4
شکل ۲۵: ۱) وضعیت اولیه؛ ۲) عناصر مشابه برای حذف انتخاب میشوند؛ ۳) عناصر و فرزندانشان از DOM حذف میشوند؛ ۴) همهٔ عناصر جایگزین میشوند، درحالیکه فقط برخی تغییر کردهاند.
در یک برنامهٔ بلیزر، مسئول ایجاد مستقیم تغییر در DOM نیستیم. بلیزر بهجای آن، یک لایهٔ انتزاعی میان DOM و کدی که برای برنامه مینویسیم قرار میدهد. انتزاع DOM در بلیزر RenderTree نام دارد و نسخهای سبک از وضعیت DOM است.
بازنمایی درونخطی صفحهٔ 65 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 66 از ۱۲۱بازگشت به فهرست
RenderTree را میتوان کارآمدتر از DOM بهروزرسانی کرد و چند تغییر را در یک بهروزرسانی واحد DOM ادغام نمود. برای بیشینهکردن کارایی، RenderTree از الگوریتم مقایسهٔ تفاوتها (Diffing Algorithm) استفاده میکند تا فقط عناصر ضروری در DOM مرورگر تغییر کنند.
اگر در یک محدودهٔ کاری واحد چند بهروزرسانی روی عناصر برنامهٔ بلیزر انجام دهیم، DOM فقط تغییرات حاصل از تفاوت نهایی را دریافت میکند. هنگام انجام کار، نسخهٔ جدیدی از RenderTree بر اساس تغییرات ناشی از کد یا اتصال داده ساخته میشود. وقتی مؤلفه آمادهٔ رندر دوباره باشد، وضعیت جاری با وضعیت جدید مقایسه و یک Diff تولید میشود. در زمان بهروزرسانی، فقط مقادیر متفاوت روی DOM اعمال میشوند.
برای دیدن اینکه RenderTree چگونه میتواند بهروزرسانیهای DOM را کاهش دهد، دقیقتر نگاه میکنیم. در شکل ۲۶، وضعیت اولیه شامل سه عنصر سبز، آبی و نارنجی است که قرار است تغییر کنند.
شکل ۲۶: وضعیت اولیهٔ RenderTree در سمت چپ و DOM در سمت راست. عناصر دارای مقادیر سبز، آبی و نارنجی توسط کد تحت تأثیر قرار خواهند گرفت.
در شکل ۲۷، چند مرحلهٔ کار درون یک چرخهٔ واحد انجام میشود. اعضا حذف و جایگزین میشوند، اما نتیجه فقط مقدار سبز و آبی را با هم عوض میکند. پس از پایان چرخهٔ عمر، تفاوتها با یکدیگر تطبیق داده میشوند.
بازنمایی درونخطی صفحهٔ 66 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 67 از ۱۲۱بازگشت به فهرست
شکل ۲۷: ۱) RenderTree جاری؛ ۲ تا ۴) برخی عناصر حذف، جایگزین و بهروزرسانی میشوند؛ ۵) وضعیت جاری و جدید برای یافتن تفاوت مقایسه میشوند.
شکل ۲۸: تفاوت RenderTree برای بهروزرسانی فقط عناصری استفاده میشود که در طول عملیات تغییر کردهاند.
ساخت RenderTree
در یک برنامهٔ بلیزر، مؤلفههای Razor با پسوند .razor در واقع کاملاً متفاوت از نشانهگذاری سنتی Razor Pages یا Viewها با پسوند .cshtml پردازش میشوند. Razor در زمینهٔ MVC یا Razor Pages فرایندی یکطرفه است که در سمت سرور به HTML رندر میشود.
بازنمایی درونخطی صفحهٔ 67 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 68 از ۱۲۱بازگشت به فهرست
یک مؤلفه در بلیزر رویکرد متفاوتی دارد: نشانهگذاری آن برای تولید یک کلاس C# استفاده میشود که RenderTree را میسازد. برای دیدن نحوهٔ ایجاد RenderTree، این فرایند را دقیقتر بررسی میکنیم.
هنگامی که یک مؤلفهٔ Razor ساخته میشود، فایل .razor به پروژه افزوده میشود و محتوای آن برای تولید یک کلاس C# به کار میرود. کلاس تولیدشده از ComponentBase ارث میبرد؛ کلاسی که متد BuildRenderTree مؤلفه را در بر دارد، همانطور که در شکل ۲۹ نشان داده شده است.
BuildRenderTree متدی است که یک شیء RenderTreeBuilder دریافت میکند و با ترجمهٔ نشانهگذاری به اشیای RenderTree، مؤلفه را به درخت اضافه مینماید.
شکل ۲۹: نمودار کلاس ComponentBase با متد BuildRenderTree که برجسته شده است.
بازنمایی درونخطی صفحهٔ 68 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 69 از ۱۲۱بازگشت به فهرست
با استفاده از نمونهٔ مؤلفهٔ Counter موجود در قالب .NET میتوانیم ببینیم کد مؤلفه چگونه به یک کلاس تولیدشده تبدیل میشود. در مؤلفهٔ Counter، اجزای مهم زیر را میتوانیم در RenderTree نهایی شناسایی کنیم:
- دستور مسیریابی صفحه
- عنصر
h1 بهعنوان یک عنصر پایهٔ HTML
- عنصر
p و currentCount بهعنوان ترکیبی از محتوای ثابت و فیلد متصل به داده
- دکمه با مدیریتکنندهٔ رویداد
onclick به نام IncrementCount
- بلوک کد شامل کد C#
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary"
@onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
کد نمونهٔ Counter برای تولید کلاسی با متد دقیق BuildRenderTree استفاده میشود که اشیای درخت را توصیف میکند. با بررسی کلاس تولیدشده میبینیم اجزای اصلی چگونه به کد خالص C# ترجمه شدهاند:
- دستور صفحه به یک صفت روی کلاس تبدیل میشود.
AddMarkupContent محتوای HTML مانند عنصر h1 را تعریف میکند.
- عناصر ترکیبی مانند
p و currentCount به گرههای جداگانه با انواع محتوای مشخص، یعنی OpenElement و AddContent، تبدیل میشوند.
- دکمه اشیای صفت مربوط به CSS و مدیریتکنندهٔ رویداد
onclick را در بر میگیرد.
- کد درون بلوک کد بهعنوان C# ارزیابی میشود.
Counter یک کلاس عمومی است که از ComponentBase ارث میبرد.
[Route("/counter")]
public class Counter : ComponentBase
{
بازنمایی درونخطی صفحهٔ 69 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 70 از ۱۲۱بازگشت به فهرست
private int currentCount = 0;
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddMarkupContent(0, "<h1>Counter</h1>\r\n\r\n");
builder.OpenElement(1, "p");
builder.AddContent(2, "Current count: ");
builder.AddContent(3, this.currentCount);
builder.CloseElement();
builder.AddMarkupContent(4, "\r\n\r\n");
builder.OpenElement(5, "button");
builder.AddAttribute(6, "class", "btn btn-primary");
builder.AddAttribute<MouseEventArgs>(
7,
"onclick",
EventCallback.Factory.Create<MouseEventArgs>(
this,
new Action(this, Counter.IncrementCount)));
builder.AddContent(8, "Click me");
builder.CloseElement();
}
private void IncrementCount()
{
this.currentCount++;
}
}
میبینیم نشانهگذاری و کد چگونه به قطعهای بسیار ساختیافته از منطق تبدیل میشوند. همهٔ بخشهای مؤلفه در RenderTree نمایندگی میشوند تا بتوان آنها را بهطور کارآمد به DOM منتقل کرد.
هر عضو در RenderTree یک شمارهٔ توالی نیز دارد؛ مانند AddContent(num, value). شمارههای توالی برای کمک به الگوریتم Diff و افزایش کارایی استفاده میشوند. یک عدد صحیح خام، شاخصی فوری در اختیار سامانه قرار میدهد تا با بررسی ترتیب و وجود یا نبود شمارهٔ توالی، تشخیص دهد تغییری رخ داده است یا نه. برای نمونه، با مقایسهٔ دنبالهٔ اشیای 1,2,3 با 1,3 میتوان نتیجه گرفت که عضو ۲ از DOM حذف شده است.
RenderTree ابزار قدرتمندی است که ابزارهای هوشمند آن را از ما پنهان کردهاند. همانطور که در نمونههای قبلی دیدیم، مؤلفههای ما فقط کلاسهای استاندارد C# هستند. میتوان این کلاسها را با ارثبری از ComponentBase و نوشتن دستی متد RenderTreeBuilder ساخت؛ اما این کار توصیه نمیشود و رویهای نامناسب است.
RenderTreeهای دستی هنگامی مشکلساز میشوند که شمارهٔ توالی عددی خطی و ثابت نباشد. الگوریتم Diff به پیشبینیپذیری کامل نیاز دارد؛ در غیر این صورت ممکن است مؤلفه بیدلیل دوباره رندر شود و کارایی آن از بین برود.
بازنمایی درونخطی صفحهٔ 70 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 71 از ۱۲۱بازگشت به فهرست
RenderTreeهایی که دستی نوشته میشوند، اگر شمارهٔ توالی آنها عددی خطی و ثابت نباشد میتوانند مشکلساز شوند.
بهینهسازی رندر مؤلفه
وقتی در بلیزر با فهرستی از عناصر یا مؤلفهها کار میکنیم، باید رفتار فهرست و هدف استفاده از مؤلفهها را در نظر بگیریم. در نهایت، الگوریتم Diff بلیزر باید تصمیم بگیرد کدام عناصر یا مؤلفهها حفظ شوند و اشیای RenderTree چگونه به آنها نگاشت شوند. معمولاً میتوان جزئیات این الگوریتم را نادیده گرفت، اما در مواردی ممکن است بخواهیم فرایند را کنترل کنیم:
- فهرستی که رندر میشود - برای نمونه در یک بلوک
@foreach - و هر عضو آن شناسهای یکتا دارد.
- فهرستی با عناصر فرزند که ممکن است با درج، حذف یا تغییر ترتیب اعضا تغییر کند.
- مواردی که رندر دوباره به تفاوت رفتاری قابل مشاهدهای مانند ازدسترفتن تمرکز عنصر منجر میشود.
فرایند نگاشت RenderTree را میتوان با صفت دستور @key کنترل کرد. با افزودن @key به الگوریتم Diff میگوییم عناصر یا مؤلفههای مرتبط با مقدار کلید را حفظ کند. نمونهای را بررسی میکنیم که به @key نیاز دارد و هر سه معیار بالا را برآورده میسازد.
یک فهرست نامرتب ul ساخته میشود. درون هر عضو li، یک h1 مقدار کلاس Color را نمایش میدهد و یک input نیز بهشکل چکباکس وجود دارد. برای شبیهسازی عملیاتی مانند مرتبسازی، درج یا حذف اعضا، دکمهای برای معکوسکردن فهرست اضافه میکنیم. دکمه از تابع درونخطی items = items.Reverse() استفاده میکند تا با کلیک، آرایهٔ اعضا را معکوس کند.
<ul class="list-group">
@foreach (var item in items)
{
<li class="list-group-item">
<h1>@item.Value</h1>
<input type="checkbox" />
</li>
}
</ul>
<button @onclick="_ => items = items.Reverse()">Reverse</button>
بازنمایی درونخطی صفحهٔ 71 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 72 از ۱۲۱بازگشت به فهرست
@code {
// class Color {int Id, string Value}
IEnumerable<Color> items = new Color[] {
new Item {Id = 0, Value = "Green" },
new Item {Id = 1, Value = "Blue" },
new Item {Id = 2, Value = "Orange" },
new Item {Id = 3, Value = "Purple" }
};
}
وقتی برنامه را اجرا کنیم، فهرست با یک چکباکس برای هر عضو رندر میشود. اگر چکباکس عضو «Green» را انتخاب و سپس فهرست را معکوس کنیم، چکباکس انتخابشده همچنان در بالای فهرست باقی میماند و اکنون در عضو «Purple» قرار گرفته است. دلیل این رفتار آن است که الگوریتم Diff فقط متن هر عنصر h1 را بهروزرسانی کرده است. وضعیت اولیه و معکوسشده در شکل ۳۰ نمایش داده شدهاند؛ توجه کنید جای چکباکس تغییر نکرده است.
شکل ۳۰: خطای رندر قابل مشاهده است؛ هنگام معکوسشدن آرایه، چکباکس جابهجا نمیشود و DOM زمینهٔ رابطهٔ عنصر را از دست میدهد.
میتوانیم از دستور @key برای ارائهٔ اطلاعات بیشتر به RenderTree استفاده کنیم. @key مشخص میکند هر عضو فهرست چگونه با فرزندانش ارتباط دارد. با این اطلاعات اضافی، الگوریتم Diff میتواند ساختار عنصر را حفظ کند. در نمونه، Id عضو را به @key اختصاص میدهیم و برنامه را دوباره اجرا میکنیم.
@foreach (var item in items)
{
<li @key="item.Id" class="list-group-item">
<h1>@item.Value</h1>
<input type="checkbox" />
بازنمایی درونخطی صفحهٔ 72 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 73 از ۱۲۱بازگشت به فهرست
</li>
}
با اعمال دستور @key، RenderTree اعضای فهرست و عناصر فرزند مرتبط با آنها را ایجاد، جابهجا یا حذف میکند. اگر چکباکس عضو «Green» را انتخاب و فهرست را معکوس کنیم، چکباکس انتخابشده نیز جابهجا میشود؛ زیرا RenderTree کل گروه عناصر li را درون فهرست منتقل میکند. این رفتار در شکل ۳۱ دیده میشود.
شکل ۳۱: با استفاده از صفت کلید، عناصر رابطهٔ خود را حفظ میکنند و هنگام بهروزرسانی DOM، چکباکس در ظرف مناسب باقی میماند.
در این نمونه، سناریویی ایدهآل داشتیم که معیارهای نیاز به @key را برآورده میکرد و توانستیم خطاهای بصری ناشی از رندر دوبارهٔ فهرست را برطرف کنیم. بااینحال، کاربردها همیشه تا این اندازه آشکار نیستند؛ بنابراین باید با دقت پیامدهای اعمال @key را بررسی کنیم.
وقتی @key استفاده نشود، بلیزر تا حد امکان نمونههای عناصر و مؤلفههای فرزند را حفظ میکند. مزیت @key این است که بهجای آنکه الگوریتم Diff نگاشت را انتخاب کند، ما کنترل میکنیم نمونههای مدل چگونه به نمونههای حفظشدهٔ مؤلفه نگاشت شوند. استفاده از @key هزینهٔ اندکی برای عملیات Diff دارد؛ اما اگر عناصر بهدرستی توسط RenderTree حفظ شوند، میتواند در مجموع سودمند باشد.
جمعبندی
هرچند RenderTree با استفاده از نحو Razor در فایلهای .razor از ما پنهان شده است، درک تأثیر آن بر شیوهٔ نوشتن کد اهمیت دارد. همانطور که در نمونه دیدیم، شناخت RenderTree و نحوهٔ کار آن هنگام نوشتن مؤلفههایی که یک سلسلهمراتب را مدیریت میکنند ضروری است.
بازنمایی درونخطی صفحهٔ 73 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 74 از ۱۲۱بازگشت به فهرست
صفت @key هنگام کار با مجموعهها و سلسلهمراتب اهمیت ویژهای دارد؛ زیرا کمک میکند RenderTree بهینه شود و از خطاهای رندر جلوگیری گردد.
RenderTree موضوعی است که هنگام بررسی فصل بعد دربارهٔ تعامل با جاوااسکریپت نیز باید در نظر بگیریم، زیرا جاوااسکریپت میتواند بر DOM اثر بگذارد.
بازنمایی درونخطی صفحهٔ 74 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 75 از ۱۲۱بازگشت به فهرست
قابلیت تعامل با جاوااسکریپت
در فصل پیش آموختیم مؤلفهها چگونه رندر میشوند. در این فصل میبینیم جاوااسکریپت چگونه میتواند با برنامههای بلیزر ارتباط برقرار کند.
بلیزر دارای API تعامل با جاوااسکریپت (JavaScript Interoperability یا Interop) است. از طریق JavaScript Interop، یک برنامهٔ بلیزر میتواند توابع جاوااسکریپت را از C# فراخوانی کند. در جهت عکس، متدهای C# نیز میتوانند از کد جاوااسکریپت فراخوانی شوند. بنابراین میتوان Callbackهای رفتوبرگشتی داشت که به هر دو پلتفرم اجازه میدهند با هم کار کنند.
تعامل با جاوااسکریپت برای توسعهٔ بلیزر یک ضرورت است. از آنجا که بلیزر از طریق وباسمبلی دسترسی کامل به DOM ندارد، بسیاری از APIهای DOM که معمولاً به آنها نیاز داریم با دسترسی مستقیم به جاوااسکریپت پشتیبانی میشوند. کاربردهای این تعامل شامل فراخوانی APIهای DOM مانند GeoLocation، MatchMedia، Web Storage یا localStorage، کوکیهای مرورگر و دستکاری مستقیم DOM است.
هرچند دسترسی و دستکاری مستقیم DOM از طریق Interop ممکن است، باید تأثیر احتمالی آن بر RenderTree بلیزر را در نظر گرفت.
API تعامل با جاوااسکریپت چهار بخش اصلی دارد:
IJSRuntime: لایهٔ انتزاعی زماناجرای جاوااسکریپت که متد InvokeAsync را فراهم میکند.
InvokeAsync: برای فراخوانی یک متد جاوااسکریپت و بازگرداندن مقدار استفاده میشود.
InvokeVoidAsync: متد توسعهای برای فراخوانی متد جاوااسکریپتی که مقدار بازگشتی ندارد.
بازنمایی درونخطی صفحهٔ 75 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 76 از ۱۲۱بازگشت به فهرست
JSInvokable: صفتی برای مشخصکردن متد .NET که میتواند از جاوااسکریپت فراخوانی شود.
برای درک بهتر زمان استفاده از JavaScript Interop و متدهای لازم، چند نمونه را بررسی میکنیم تا با Interop تعامل داشته باشیم و ببینیم چرا جاوااسکریپت برای یک برنامهٔ بلیزر ضروری است.
فراخوانی جاوااسکریپت از .NET
در این نمونه میآموزیم چگونه جاوااسکریپت را از .NET فراخوانی کنیم. هدف، ساخت قابلیتی است که مطابق شکل ۳۲ به برنامه اجازه دهد میان پوستهٔ روشن و تاریک جابهجا شود.
شکل ۳۲: یک دکمهٔ تغییر وضعیت به کاربران اجازه میدهد میان پوستهٔ روشن و تاریک برنامه جابهجا شوند.
چون برنامههای بلیزر از فناوریهای استاندارد وب ساخته شدهاند، بهترین راه پیادهسازی این قابلیت، تغییر CSS برنامه است. برای تغییر کامل پوسته باید میان دو فایل CSS مجزا که تمام مقادیر هر پوسته را دارند جابهجا شویم: light.css و dark.css. فرض میکنیم این فایلها از قبل مطابق شکل ۳۳ فراهم شدهاند. خود CSS موضوع بحث ما نیست؛ فقط باید بدانیم با اعمال هر پوسته، برنامه از پالت روشن یا تاریک استفاده خواهد کرد.
بازنمایی درونخطی صفحهٔ 76 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 77 از ۱۲۱بازگشت به فهرست
شکل ۳۳: پروژه، پوستهٔ روشن و تاریک را بهصورت فایلهای CSS جداگانه در پوشهٔ wwwroot در بر دارد.
برنامههای بلیزر از فایل _Host.cshtml یا index.html برای میزبانی صفحهٔ SPA سمت کاربر استفاده میکنند. بخش پویای برنامه درون عنصر app صفحه قرار دارد. مؤلفهها و کد نمیتوانند بر عناصر بیرون از app اثر بگذارند؛ از جمله عناصر head صفحه.
<head>
<!-- Blazor has no scope here -->
<meta charset="utf-8" />
<base href="~/" />
...
<link href="css/light.css" rel="stylesheet" />
<!-- /Blazor has no scope here -->
</head>
<body>
<app>
<!-- Blazor (RenderTree) exist here -->
<component type="typeof(App)"
render-mode="ServerPrerendered" />
<!-- /Blazor (RenderTree) exist here -->
</app>
</body>
برای آنکه برنامه بتواند میان پوستهٔ روشن و تاریک جابهجا شود، باید عنصر link درون head را جایگزین کنیم. چون بلیزر در دامنهٔ این عنصر نیست، برای اصلاح head به JavaScript Interop متکی خواهیم بود. از آنجا که جاوااسکریپت ما روی عناصری بیرون از دامنهٔ بلیزر کار میکند، احتمال تداخل با RenderTree وجود ندارد.
پیش از نوشتن کد Interop، مؤلفهای میسازیم که در نهایت پوستهٔ برنامه را تغییر دهد. نمونهٔ مؤلفهٔ انتخاب پوسته در شکل ۳۲ دیده میشود. مؤلفهٔ جدید ThemeChooser دکمهای به کاربر ارائه میکند. فشردن دکمه، پرچم بولی isDark را تغییر میدهد و متن را از Light به Dark یا برعکس تبدیل میکند. مقدار متنی پوسته با ویژگی SetThemeName نمایندگی میشود. وقتی مؤلفه نمایش داده شود، متن آن چنین خواهد بود: Click here to switch themes [Dark Mode].
بازنمایی درونخطی صفحهٔ 77 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 78 از ۱۲۱بازگشت به فهرست
در این مرحله، مؤلفه توان محدودی دارد و فقط پیام متنی را تغییر میدهد، نه پوستهٔ واقعی برنامه را.
<div class="alert alert-info my-2">
Click here to switch themes
<TelerikButton OnClick="SwapTheme"
Primary="true"
Icon="@IconName.InvertColors">
@SetThemeName Mode
</TelerikButton>
</div>
@code {
bool isDark;
string SetThemeName => isDark ? "Dark" : "Light";
async Task SwapTheme()
{
isDark = !isDark;
}
}
سپس فایل جاوااسکریپتی themeChooser.js را به برنامه اضافه میکنیم که کد لازم برای تغییر عنصر link برنامه را دارد. مطابق شکل ۳۴، فایل themeChooser.js در پوشهٔ wwwroot برنامه قرار میگیرد.
شکل ۳۴: فایل themeChooser.js که قابلیت Interop را فراهم میکند در پوشهٔ wwwroot ذخیره شده است.
برای آنکه برنامهٔ بلیزر بتواند به تابع جاوااسکریپت دسترسی پیدا کند، تابع باید در دامنهٔ سراسری window در دسترس باشد. برای این کار فضای نام themeChooser را به window اضافه و تابع setTheme را زیر آن تعریف میکنیم. نامگذاری اهمیت دارد، زیرا بعداً باید این توابع را از کد بلیزر فراخوانی کنیم و IntelliSense نمیتواند اشتباههای نامگذاری را اصلاح کند.
بازنمایی درونخطی صفحهٔ 78 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 79 از ۱۲۱بازگشت به فهرست
window.themeChooser = {
setTheme: function (themeName) { … theme swap code }
}
در تابع setTheme، یک عنصر link جدید با استفاده از پارامتر themeName ساخته میشود. هنگام فراخوانی تابع، themeName از کد C# ارسال خواهد شد. این نام با مسیر فایل CSS و پسوند .css ترکیب و در صفت href لینک قرار میگیرد. در پایان، عنصر head در DOM پیدا میشود، لینک اصلی حذف و لینک جدید جایگزین آن میشود. مرورگر تغییر را تشخیص میدهد و صفحه را بهطور خودکار با منبع CSS جدید بهروزرسانی میکند.
window.themeChooser = {
setTheme: function (themeName) {
// Build the new css link
let newLink = document.createElement("link");
newLink.setAttribute("id", "theme");
newLink.setAttribute("rel", "stylesheet");
newLink.setAttribute("type", "text/css");
newLink.setAttribute("href", `css/${themeName}.css`);
// Remove and replace the theme
let head = document.getElementsByTagName("head")[0];
head.querySelector("#theme").remove();
head.appendChild(newLink);
}
}
سپس فایل themeChooser.js را بهعنوان منبع ایستا به برنامه اضافه میکنیم. در فایل میزبان برنامه، فقط با یک تگ script به آن ارجاع میدهیم.
<head>
…
<link id="theme" href="css/light.css" rel="stylesheet" />
<script src="js/themeChooser.js"></script>
</head>
اکنون برنامه همهٔ اجزای لازم برای استفاده از کد themeChooser.js را دارد. به مؤلفهٔ بلیزر بازمیگردیم و کدی برای فراخوانی تابع setTheme از طریق JavaScript Interop اضافه میکنیم.
برای فراخوانی جاوااسکریپت از .NET از انتزاع IJSRuntime استفاده میشود. نمونهٔ IJSRuntime با تزریق وابستگی ساخته میشود و در پیکربندی پیشفرض سرویسهای بلیزر وجود دارد. از این نمونه میتوان متدهای InvokeAsync<T> و InvokeVoidAsync را فراخوانی کرد. در اینجا از InvokeVoidAsync استفاده میکنیم، زیرا تابع setTheme مقدار بازگشتی ندارد.
بازنمایی درونخطی صفحهٔ 79 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 80 از ۱۲۱بازگشت به فهرست
متد SwapTheme با فراخوانی await js.InvokeVoidAsync بهروزرسانی میشود. پارامتر نخست، فضای نام و نام تابع جاوااسکریپت را مشخص میکند. نام پوسته بهعنوان پارامتر دوم ارسال و به تابع setTheme در جاوااسکریپت منتقل میشود. در این نمونه فقط یک رشتهٔ ساده ارسال میکنیم، اما هر شیء قابل سریالسازی به JSON قابل استفاده است.
@inject IJSRuntime js
<div class="alert alert-info my-2">
Click here to switch themes
<TelerikButton OnClick="SwapTheme"
Primary="true"
Icon="@IconName.InvertColors">
@SetThemeName Mode
</TelerikButton>
</div>
@code {
bool isDark;
string SetThemeName => isDark ? "Dark" : "Light";
async Task SwapTheme()
{
isDark = !isDark;
await js.InvokeVoidAsync(
"themeChooser.setTheme",
SetThemeName);
}
}
این قابلیت اکنون کامل است و برنامه میتواند مطابق شکل ۳۵ با یک کلیک میان پوستهٔ روشن و تاریک جابهجا شود.
شکل ۳۵: مؤلفهٔ انتخاب پوسته با استفاده از JavaScript Interop، پوستهٔ برنامه را از روشن به تاریک تغییر میدهد.
بازنمایی درونخطی صفحهٔ 80 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 81 از ۱۲۱بازگشت به فهرست
در نمونهٔ قبلی دیدیم چگونه با JavaScript Interop توابع جاوااسکریپت را از درون C# اجرا کنیم. توانستیم آرگومانهایی به جاوااسکریپت بفرستیم و از مقادیر آنها برای تغییر پوستهٔ برنامه استفاده کنیم. در نمونهٔ بعدی میبینیم چگونه با ارسال مقدار از جاوااسکریپت به C# و اجرای کد C# از جاوااسکریپت، عملیات رفتوبرگشتی انجام دهیم.
فراخوانی .NET از جاوااسکریپت
در این نمونه میآموزیم چگونه مقادیر را از جاوااسکریپت به .NET بفرستیم و .NET را از جاوااسکریپت فراخوانی کنیم. هدف، ساخت قابلیتی است که مطابق شکل ۳۸ به برنامه اجازه دهد از API مکانیابی وب استفاده کند.
مکانیابی (Geolocation) گزینهٔ مناسبی برای JavaScript Interop است، زیرا بلیزر API مستقیمی برای آن ندارد. برای دسترسی به Geolocation API باید توابع جاوااسکریپت را فراخوانی کنیم تا API اجرا شود و سپس نتایج از طریق Callback به .NET بازگردند.
پیش از آغاز، مختصراً نحوهٔ کار Geolocation وب را مرور میکنیم. رابط Geolocation یک مشخصات W3C است که بیشتر مرورگرهای مدرن آن را از طریق ویژگی فقطخواندنی Navigator.geolocation پیادهسازی میکنند. این ویژگی شیئی از نوع Geolocation بازمیگرداند که با مجموعهای از توابع به مکان دستگاه دسترسی میدهد.
در این نمونه از تابع getCurrentPosition استفاده میکنیم تا شیء GeolocationCoordinates شامل عرض و طول جغرافیایی دستگاه کاربر بازگردانده شود. این قابلیت به برنامه اجازه میدهد بر اساس موقعیت کاربر نتایج سفارشی ارائه کند. در طول نمونه، کلاسهایی برای نمایش این اشیا در .NET اضافه میکنیم تا هنگام انتقال مقدارها میان جاوااسکریپت و C#، سریالسازی و بازسریالسازی سادهتر شود.
کار را با نمونهای ساده آغاز میکنیم که مرورگر را بررسی میکند تا مشخص شود Geolocation API پشتیبانی میشود یا نه. وجود navigator.geolocation را بررسی و نتیجه را بهصورت مقدار بولی بازمیگردانیم. ابتدا فضای نام blazorGeolocation را به window اضافه میکنیم. درون آن تابع hasGeolocationFeature را میسازیم که بررسی را انجام و مقدار true/false بازمیگرداند. این قطعهٔ کوتاه جاوااسکریپت نخستین کد Interop ما برای تعامل با Geolocation API است.
window.blazorGeolocation = {
hasGeolocaitonFeature: function () {
return navigator.geolocation ? true : false;
}
};
بازنمایی درونخطی صفحهٔ 81 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 82 از ۱۲۱بازگشت به فهرست
فایل را با نام blazorGeolocation.js ذخیره و در بخش head فایل _Host.cshtml یا index.html برنامه به آن ارجاع میدهیم.
<head>
…
<script src="~/js/geoLocation.js"></script>
</head>
نیاز بعدی، ساخت همتای .NET است که هم تابع hasGeolocationFeature را فراخوانی کند و هم نتیجه را بازگرداند. کلاس جدیدی به نام Geolocation میسازیم که Geolocation API را در .NET نمایندگی میکند. سازندهٔ این کلاس به یک نمونهٔ IJSRuntime نیاز دارد که درون کلاس استفاده خواهد شد.
public class Geolocation
{
private readonly IJSRuntime js;
public Geolocation(IJSRuntime js)
{
this.js = js;
}
}
در نمونهٔ قبلی IJSRuntime را بررسی کردیم و با InvokeVoidAsync یک متد جاوااسکریپت را از C# فعال نمودیم. این بار از متد InvokeAsync<T> استفاده میکنیم که علاوه بر فعالکردن تابع جاوااسکریپت، انتظار دریافت نتیجهای از نوع T را دارد. در اینجا T همان مقدار بولی بازگشتی از hasGeoloactionFeature است.
متدی در کلاس Geolocation میسازیم که فراخوانی Interop را مدیریت و یک ValueTask<bool> بازگرداند. ValueTask تضمین میکند فراخوانی HasGeolocationFeature مقدار bool یا Task<bool> برگرداند.
public class Geolocation
{
…
public async ValueTask<bool> HasGeolocationFeature() =>
await js.InvokeAsync<bool>(
"blazorGeolocation.hasGeolocaitonFeature");
}
اکنون میتوانیم Interop را در یک مؤلفه آزمایش کنیم. باید IJSRuntime را به مؤلفه تزریق کنیم تا به شیء Geolocation داده شود. سپس فیلد بولی hasGeolocation را برای نگهداری نتیجهٔ فراخوانی میسازیم.
برای اطمینان از بارگذاری کامل صفحه و دسترسپذیری APIهای جاوااسکریپت، از متد چرخهٔ عمر OnAfterRenderAsync مؤلفه استفاده خواهیم کرد.
بازنمایی درونخطی صفحهٔ 82 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 83 از ۱۲۱بازگشت به فهرست
در OnAfterRenderAsync یک کلاس Geolocation جدید ساخته، متد HasGeolocationFeature فراخوانی و نتیجه در hasGeolocation ذخیره میشود. برای پایان عملیات، StateHasChanged را فراخوانی میکنیم تا رابط کاربری دوباره رندر و پیام Browser has Geolocation نمایش داده شود.
@inject IJSRuntime js
@using GeoLocation
@page "/"
<h1>Hello, world!</h1>
@if (hasGeoLocation)
{
<p>Browser has Geolocation</p>
}
@code {
bool hasGeoLocation;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var geo = new Geolocation(js);
hasGeoLocation = await geo.HasGeolocationFeature();
StateHasChanged();
}
}
}
هنگام بارگذاری برنامه، مرورگر تشخیص میدهد کد به قابلیت Geolocation دسترسی دارد و مطابق شکل ۳۶ فوراً از کاربر مجوز میخواهد. همزمان صفحه نیز مطابق شکل ۳۷ در دسترسبودن Geolocation را نشان میدهد.
بازنمایی درونخطی صفحهٔ 83 فایل اصلی برای حفظ کامل شکلها، نمودارها، اسکرینشاتها و چیدمان مرجع.
ترجمهٔ صفحهٔ 84 از ۱۲۱بازگشت به فهرست
شکل ۳۶: هنگامی که برای نخستین بار از جاوااسکریپت به Geolocation ارجاع داده شود، مرورگر از کاربر اجازهٔ استفاده از موقعیت مکانی را درخواست میکند.
شکل ۳۷: با بارگذاری صفحه، پیام Browser has Geolocation نمایش داده میشود و نشان میدهد جاوااسکریپت مقداری بازگردانده است.
تا اینجا Interop فقط یک نوع بولی ساده بازمیگرداند که در دسترسبودن قابلیت Geolocation را مشخص میکند. در مراحل بعد متدی اضافه میکنیم که نوع پیچیدهای شامل مختصات کاربر را از جاوااسکریپت دریافت کند. برای ارسال و دریافت نوعهای پیچیده میان C# و جاوااسکریپت، اشیا باید قابل سریالسازی به JSON باشند.
از تابع getCurrentPosition در Geolocation API استفاده خواهیم کرد. این تابع انتظار یک Callback دارد که شیء Position را بازمیگرداند.
Position {
coords: {
latitude: 38.2099893,
longitude: -85.21792610000001,
…
},
