اشتراکها
یکی از نکات جالب رندر کامپوننتها در Blazor، امکان فراخوانی بازگشتی آنها است؛ یعنی یک کامپوننت میتواند خودش را نیز فراخوانی کند. از همین قابلیت میتوان جهت نمایش ساختارهای درختی، مانند مدلهای خود ارجاع دهندهی EF استفاده کرد.
مدل برنامه، جهت تامین دادههای خود ارجاع دهنده و درختی
فرض کنید قصد داریم لیستی از کامنتهای تو در تو را مدل سازی کنیم که در آن هر کامنت، میتواند چندین کامنت تا بینهایت سطح تو در تو را داشته باشد:
برای نمونه بر اساس این مدل، منبع دادهی فرضی زیر را تهیه میکنیم:
این قطعه کد partial class که مربوط به فایل TreeView.razor.cs برنامهاست، در حقیقت کدهای پشت صحنهی کامپوننت مثال TreeView.razor است که در ادامه آنرا توسعه خواهیم داد. در نهایت قرار است بتوانیم آنرا به صورت زیر رندر کنیم:
طراحی کامپوننت DntTreeView
برای اینکه بتوانیم به یک کامپوننت با قابلیت استفادهی مجدد بررسیم، کدهای نمایش اطلاعات تو در تو و درختی را توسط کامپوننت سفارشی DntTreeView پیاده سازی خواهیم کرد. پیشنیازهای آن نیز به صورت زیر است:
- این کامپوننت باید جنریک باشد؛ یعنی باید به صورت زیر شروع شود:
چون باید بتوان یک لیست جنریک <IEnumerable<TRecord را به آن، جهت رندر ارسال کرد و قرار نیست این کامپوننت، تنها به شیء سفارشی Comment مثال جاری ما وابسته باشد. بنابراین اولین خاصیت آن، شیء جنریک Items است که لیست کامنتها/عناصر را دریافت میکند:
- هنگام رندر هر آیتم کامنت باید بتوان یک قالب سفارشی را از کاربر دریافت کرد. نمیخواهیم صرفا برای مثال Text شیء Comment فوق را به صورت متنی و ساده نمایش دهیم. میخواهیم در حین رندر، کل شیء TRecord جاری را به مصرف کننده ارسال و یک قالب سفارشی را از آن دریافت کنیم. یعنی باید یک RenderFragment جنریک را به صورت زیر نیز داشته باشیم تا مصرف کننده بتواند TRecord در حال رندر را دریافت و قالب Htmlای خودش را بازگشت دهد:
- همچنین همیشه باید به فکر عدم وجود اطلاعاتی برای نمایش نیز بود. به همین جهت بهتر است قالب دیگری را نیز از مصرف کننده برای اینکار درخواست کنیم و نحوهی رندر سفارشی این قسمت را نیز به مصرف کننده واگذار کنیم:
- زمانیکه با شیء از پیش تعریف شدهی Comment این مثال کار میکنیم، کاملا مشخص است که خاصیت Comments آن تو در تو است:
اما زمانیکه با یک کامپوننت جنریک کار میکنیم، نیاز است از مصرف کننده، نام این خاصیت تو در تو را به نحو واضحی دریافت کنیم؛ به صورت زیر:
دلیل استفاده از Expression Funcها را در مطلب «static reflection» میتوانید مطالعه کنید. زمانیکه قرار است از کامپوننت DntTreeView استفاده کنیم، ابتدا نوع جنریک آنرا مشخص میکنیم، سپس لیست اشیاء ارسالی به آنرا و در ادامه با استفاده از ChildrenSelector به صورت زیر، مشخص میکنیم که خاصیت Comments است که به همراه Children میباشد و تو در تو است:
و مرسوم است جهت بالابردن کارآیی Expression Funcها، آنها را کامپایل و کش کنیم که نمونهای از روش آنرا به صورت زیر مشاهده میکنید:
تا اینجا ساختار کدهای پشت صحنهی DntTreeView.razor.cs مشخص شد. اکنون UI این کامپوننت را به صورت زیر تکمیل میکنیم:
در ابتدای کار، اگر آیتمی برای نمایش وجود نداشته باشد، EmptyContentTemplate دریافتی از استفاده کننده را رندر میکنیم. در غیراینصورت، حلقهای را بر روی لیست Items ایجاد کرده و آنها را یکی نمایش میدهیم. این نمایش، نکات زیر را به همراه دارد:
- نمایش توسط کامپوننت دومی به نام DntTreeViewChildrenItem انجام میشود که آنهم جنریک است و شیء item جاری را توسط خاصیت ParentItem دریافت میکند.
- در اینجا یک CascadingValue اشاره کننده به شیء this را هم مشاهده میکنید. این روش، یکی از روشهای اجازه دادن دسترسی به خواص و امکانات یک کامپوننت والد، در کامپوننتهای فرزند است که در ادامه از آن استفاده خواهیم کرد.
تکمیل کامپوننت بازگشتی DntTreeViewChildrenItem.razor
اگر به حلقهی foreach (var item in Items) در کامپوننت DntTreeView.razor دقت کنید، یک سطح را بیشتر پوشش نمیدهد؛ اما کامنتهای ما چندسطحی و تو در تو هستند و عمق آنها هم مشخص نیست. به همین جهت نیاز است به نحوی بتوان یک طراحی recursive و بازگشتی را در کامپوننتهای Blazor داشت که خوشبختانه این مورد پیشبینی شدهاست و هر کامپوننت Blazor، میتواند خودش را نیز فراخوانی کند:
اینها کدهای DntTreeViewChildrenItem.razor هستند که در آن، ابتدا ItemTemplate دریافتی از والد یا همان DntTreeView.razor رندر میشود. سپس به کمک CompiledChildrenSelector ای که عنوان شد، یک شیء Children را تشکیل داده و آنرا به خودش (فراخوانی مجدد DntTreeViewChildrenItem در اینجا)، ارسال میکند. این فراخوانی بازگشتی، سبب رندر تمام سطوح تو در توی شیء جاری میشود.
کدهای پشت صحنهی این کامپوننت یعنی فایل DntTreeViewChildrenItem.razor.cs به صورت زیر است:
با استفاده از یک پارامتر از نوع CascadingParameter، میتوان به اطلاعات شیء CascadingValue ای که در کامپوننت والد DntTreeView.razor قرا دادیم، دسترسی پیدا کنیم. سپس یکبار هم بررسی میکنیم که آیا نال هست یا خیر. یعنی قرار نیست که این کامپوننت فرزند، درجائی به صورت مستقیم استفاده شود. فقط قرار است داخل کامپوننت والد فراخوانی شود. به همین جهت اگر این CascadingParameter نال بود، یعنی این کامپوننت فرزند، به اشتباه فراخوانی شده و با صدور استثنائی این مساله را گوشزد میکنیم. اکنون که به SafeOwnerTreeView یا همان نمونهای از شیء والد دسترسی پیدا کردیم، میتوانیم پارامتر CompiledChildrenSelector آنرا نیز فراخوانی کرده و توسط آن، به شیء تو در توی جدیدی در صورت وجود، جهت رندر بازگشتی آن رسید.
یعنی این کامپوننت ابتدا ParentItem، یا اولین سطح ممکن و در دسترس را رندر میکند. سپس با استفاده از Expression Func مهیای در کامپوننت والد، شیء فرزند را در صورت وجود یافته و سپس به صورت بازگشتی آنرا با فراخوانی مجدد خودش ، رندر میکند.
روش استفاده از کامپوننت DntTreeView
اکنون که کار توسعهی کامپوننت جنریک DntTreeView پایان یافت، روش استفادهی از آن به صورت زیر است:
همانطور که مشاهده میکنید، چون کامپوننت جنریک است، باید نوع TRecord را که در مثال ما، شیء Comment است، مشخص کرد. سپس لیست نظرات، خاصیت تو در تو، قالب سفارشی نمایش Text نظرات (با توجه به Context دریافتی که امکان دسترسی به شیء جاری در حال رندر را میسر میکند) و همچنین قالب سفارشی نبود اطلاعاتی برای نمایش را تعریف میکنیم.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorTreeView.zip
کامپوننت توسعه یافتهی در اینجا در هر دو حالت Blazor WASM و Blazor Server کار میکند.
مدل برنامه، جهت تامین دادههای خود ارجاع دهنده و درختی
فرض کنید قصد داریم لیستی از کامنتهای تو در تو را مدل سازی کنیم که در آن هر کامنت، میتواند چندین کامنت تا بینهایت سطح تو در تو را داشته باشد:
namespace BlazorTreeView.ViewModels; public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
using BlazorTreeView.ViewModels; namespace BlazorTreeView.Pages; public partial class TreeView { private IReadOnlyDictionary<string, object> ChildrenHtmlAttributes { get; } = new Dictionary<string, object>(StringComparer.Ordinal) { { "style", "list-style: none;" }, }; private IList<Comment> Comments { get; } = new List<Comment> { new() { Text = "پاسخ یک", }, new() { Text = "پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخ اول به پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخی به پاسخ اول پاسخ دو", }, }, }, new() { Text = "پاسخ دوم به پاسخ دو", }, }, }, new() { Text = "پاسخ سوم", }, }; }
طراحی کامپوننت DntTreeView
برای اینکه بتوانیم به یک کامپوننت با قابلیت استفادهی مجدد بررسیم، کدهای نمایش اطلاعات تو در تو و درختی را توسط کامپوننت سفارشی DntTreeView پیاده سازی خواهیم کرد. پیشنیازهای آن نیز به صورت زیر است:
- این کامپوننت باید جنریک باشد؛ یعنی باید به صورت زیر شروع شود:
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeView<TRecord> {
/// <summary> /// The treeview's self-referencing items /// </summary> [Parameter] public IEnumerable<TRecord>? Items { set; get; }
/// <summary> /// The treeview item's template /// </summary> [Parameter] public RenderFragment<TRecord>? ItemTemplate { set; get; }
/// <summary> /// The content displayed if the list is empty /// </summary> [Parameter] public RenderFragment? EmptyContentTemplate { set; get; }
public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
/// <summary> /// The property which returns the children items /// </summary> [Parameter] public Expression<Func<TRecord, IEnumerable<TRecord>>>? ChildrenSelector { set; get; }
<DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments"
public partial class DntTreeView<TRecord> { private Expression? _lastCompiledExpression; internal Func<TRecord, IEnumerable<TRecord>>? CompiledChildrenSelector { private set; get; } // ... protected override void OnParametersSet() { if (_lastCompiledExpression != ChildrenSelector) { CompiledChildrenSelector = ChildrenSelector?.Compile(); _lastCompiledExpression = ChildrenSelector; } } }
@namespace BlazorTreeView.Pages.Components @typeparam TRecord @if (Items is null || !Items.Any()) { @EmptyContentTemplate } else { <CascadingValue Value="this"> <ul @attributes="AdditionalAttributes"> @foreach (var item in Items) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> </CascadingValue> }
- نمایش توسط کامپوننت دومی به نام DntTreeViewChildrenItem انجام میشود که آنهم جنریک است و شیء item جاری را توسط خاصیت ParentItem دریافت میکند.
- در اینجا یک CascadingValue اشاره کننده به شیء this را هم مشاهده میکنید. این روش، یکی از روشهای اجازه دادن دسترسی به خواص و امکانات یک کامپوننت والد، در کامپوننتهای فرزند است که در ادامه از آن استفاده خواهیم کرد.
تکمیل کامپوننت بازگشتی DntTreeViewChildrenItem.razor
اگر به حلقهی foreach (var item in Items) در کامپوننت DntTreeView.razor دقت کنید، یک سطح را بیشتر پوشش نمیدهد؛ اما کامنتهای ما چندسطحی و تو در تو هستند و عمق آنها هم مشخص نیست. به همین جهت نیاز است به نحوی بتوان یک طراحی recursive و بازگشتی را در کامپوننتهای Blazor داشت که خوشبختانه این مورد پیشبینی شدهاست و هر کامپوننت Blazor، میتواند خودش را نیز فراخوانی کند:
@namespace BlazorTreeView.Pages.Components @typeparam TRecord <li @attributes="@SafeOwnerTreeView.ChildrenHtmlAttributes" @key="ParentItem?.GetHashCode()"> @if (SafeOwnerTreeView.ItemTemplate is not null && ParentItem is not null) { @SafeOwnerTreeView.ItemTemplate(ParentItem) } @if (Children is not null) { <ul> @foreach (var item in Children) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> } </li>
کدهای پشت صحنهی این کامپوننت یعنی فایل DntTreeViewChildrenItem.razor.cs به صورت زیر است:
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeViewChildrenItem<TRecord> { /// <summary> /// Defines the owner of this component. /// </summary> [CascadingParameter] public DntTreeView<TRecord>? OwnerTreeView { get; set; } private DntTreeView<TRecord> SafeOwnerTreeView => OwnerTreeView ?? throw new InvalidOperationException("`DntTreeViewChildrenItem` should be placed inside of a `DntTreeView`."); /// <summary> /// Nested parent item to display /// </summary> [Parameter] public TRecord? ParentItem { set; get; } private IEnumerable<TRecord>? Children => ParentItem is null || SafeOwnerTreeView.CompiledChildrenSelector is null ? null : SafeOwnerTreeView.CompiledChildrenSelector(ParentItem); }
یعنی این کامپوننت ابتدا ParentItem، یا اولین سطح ممکن و در دسترس را رندر میکند. سپس با استفاده از Expression Func مهیای در کامپوننت والد، شیء فرزند را در صورت وجود یافته و سپس به صورت بازگشتی آنرا با فراخوانی مجدد خودش ، رندر میکند.
روش استفاده از کامپوننت DntTreeView
اکنون که کار توسعهی کامپوننت جنریک DntTreeView پایان یافت، روش استفادهی از آن به صورت زیر است:
<div class="card" dir="rtl"> <div class="card-header"> DntTreeView </div> <div class="card-body"> <DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments" style="list-style: none;" ChildrenHtmlAttributes="ChildrenHtmlAttributes"> <ItemTemplate Context="record"> <div class="card mb-1"> <div class="card-body"> <span>@record.Text</span> </div> </div> </ItemTemplate> <EmptyContentTemplate> <div class="alert alert-warning"> There is no item to display! </div> </EmptyContentTemplate> </DntTreeView> </div> </div>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorTreeView.zip
کامپوننت توسعه یافتهی در اینجا در هر دو حالت Blazor WASM و Blazor Server کار میکند.
اشتراکها
نصب و اجرای برنامه Node.JS
اشتراکها
کاهش علاقهی Oracle به Java
اضافه شدن قابلیت کامپایل AOT به Blazor 6x
Blazor WebAssembly 6x به همراه قابلیتی است به نام ahead-of-time (AOT) compilation که در این حالت، کدهای دات نتی برنامه، مستقیما به native WebAssembly کامپایل میشوند. این مورد سبب بالا رفتن کارآیی برنامه خواهد شد؛ در عوض بالا رفتن حجم نهایی قابل توزیع.
اگر از AOT compilation استفاده نشود (یعنی حالت متداول)، Blazor WebAssembly در مرورگر، به کمک مفسر IL یا همان NET Intermediate Language. که به صورت WebAssembly تهیه شدهاست، اجرا خواهد شد. یک چنین حالتی به دلیل استفادهی از مفسر، نسبت به حالت استفادهی از JIT سمت سرور (یا همان NET just-in-time (JIT) runtime.)، اندکی کندتر است. AOT compilation جهت رفع یک چنین کمبودی ارائه شدهاست تا کدهای دات نتی را مستقیما و بدون نیاز به مفسر، تبدیل به یک native WebAssembly کند. این مورد سرعت و کارآیی برنامههایی را که کارهای محاسباتی مبتنی بر CPU را انجام میدهند، به نحو قابل ملاحظهای افزایش میدهد. در مقابل باید درنظر داشت که حجم نهایی WebAssemblyهای واقعی تولید شده، از نمونهی IL آنها بالاتر است (حدود 2 برابر) که مدت زمان ابتدایی دریافت برنامه را افزایش میدهند.
روش فعالسازی کامپایل AOT
ابتدا نیاز است work load آنرا توسط دستور زیر دریافت کرد (ابزارهای کامپایل AOT، جزئی از SDK نیستند):
سپس برای فعالسازی آن میتوان تنظیم زیر را به فایل csporj پروژههای WASM اضافه کرد:
در این حالت با publish برنامه توسط دستور dotnet publish -c Release، مراحل تولید native WebAssemblyها طی میشوند و باید درنظر داشت که به علت کند بودن این پروسه، تنها در زمان publish نهایی، شاهد این عملیات خواهید بود و نه در زمان اجرای برنامه در حالت توسعه.
یک نکته: هنوز در نگارش 6.0 RTM، یکسری از قابلیتهای AOT اضافه نشدهاند که باید منتظر سرویسپکهای آن بود. برای مثال اگر این کامپایل، بر روی پروژهای که فقط سورس کد است اجرا شود، با موفقیت به پایان میرسد؛ اما با اضافه شدن کتابخانههای ثالث ممکن است با شکست مواجه شود. اگر در این حالت خطایی را دریافت کردید، عملیات publish را به صورت dotnet publish -p:RunAOTCompilation=true -bl انجام دهید. سوئیچ bl- سبب میشود تا فایلی به نام msbuild.binlog در ریشهی پروژهی شما تولید شود. این فایل در حقیقت لاگ باینری MSBuild است که توسط برنامهی Viewer آن قابل مشاهدهاست. در اینجا به دنبال exit codeها بگردید؛ یک نمونهی آن.
Blazor WebAssembly 6x به همراه قابلیتی است به نام ahead-of-time (AOT) compilation که در این حالت، کدهای دات نتی برنامه، مستقیما به native WebAssembly کامپایل میشوند. این مورد سبب بالا رفتن کارآیی برنامه خواهد شد؛ در عوض بالا رفتن حجم نهایی قابل توزیع.
اگر از AOT compilation استفاده نشود (یعنی حالت متداول)، Blazor WebAssembly در مرورگر، به کمک مفسر IL یا همان NET Intermediate Language. که به صورت WebAssembly تهیه شدهاست، اجرا خواهد شد. یک چنین حالتی به دلیل استفادهی از مفسر، نسبت به حالت استفادهی از JIT سمت سرور (یا همان NET just-in-time (JIT) runtime.)، اندکی کندتر است. AOT compilation جهت رفع یک چنین کمبودی ارائه شدهاست تا کدهای دات نتی را مستقیما و بدون نیاز به مفسر، تبدیل به یک native WebAssembly کند. این مورد سرعت و کارآیی برنامههایی را که کارهای محاسباتی مبتنی بر CPU را انجام میدهند، به نحو قابل ملاحظهای افزایش میدهد. در مقابل باید درنظر داشت که حجم نهایی WebAssemblyهای واقعی تولید شده، از نمونهی IL آنها بالاتر است (حدود 2 برابر) که مدت زمان ابتدایی دریافت برنامه را افزایش میدهند.
روش فعالسازی کامپایل AOT
ابتدا نیاز است work load آنرا توسط دستور زیر دریافت کرد (ابزارهای کامپایل AOT، جزئی از SDK نیستند):
dotnet workload install wasm-tools
<PropertyGroup> <RunAOTCompilation>true</RunAOTCompilation> </PropertyGroup>
یک نکته: هنوز در نگارش 6.0 RTM، یکسری از قابلیتهای AOT اضافه نشدهاند که باید منتظر سرویسپکهای آن بود. برای مثال اگر این کامپایل، بر روی پروژهای که فقط سورس کد است اجرا شود، با موفقیت به پایان میرسد؛ اما با اضافه شدن کتابخانههای ثالث ممکن است با شکست مواجه شود. اگر در این حالت خطایی را دریافت کردید، عملیات publish را به صورت dotnet publish -p:RunAOTCompilation=true -bl انجام دهید. سوئیچ bl- سبب میشود تا فایلی به نام msbuild.binlog در ریشهی پروژهی شما تولید شود. این فایل در حقیقت لاگ باینری MSBuild است که توسط برنامهی Viewer آن قابل مشاهدهاست. در اینجا به دنبال exit codeها بگردید؛ یک نمونهی آن.
- مزایای الگوی MVP | blog.afsharm.com
- کدهای #C قابل انتقال (Portable) هست یا خیر؟ | www.idevcenter.com
- عکس هفته (۱) | 1nevesht.com
- ده قلم رایگان فارسی یونیکد و استاندارد | persianlanguage.ir
- جزئیاتی جدیدی در مورد DirectX 11.1 در نمایشگاه BUILD منتشر شد | مجله اینترنتی گویا آیتی [del.icio.us] | www.gooyait.com
- بیش از ۳۰۰ ویژگی ویندوز۸ که مایکروسافت نشان نداد | mymicrosoftlife.net
- اینترنت اکسپلور در ویندوز ۸ از فلش پشتیبانی نمی کند | site.i-phone.ir
- WinRT vs. Silverlight - Part 1 - XML Namespace | www.iter.dk
- WinRT vs. Silverlight - Part 0 | www.iter.dk
- Windows 8 Running on ARM | channel9.msdn.com
- VisualSVN Server 2.1.11 Released | www.visualsvn.com
- Visual Studio vNext: DirectX 11 Development Experience | channel9.msdn.com
- Visual Studio vNext: Concurrency Visualizer | channel9.msdn.com
- Silverlight 5 in Action Book Update, and Announcing the Next Big Book | feedproxy.google.com
- September 2011 Office Security Update Release | blogs.technet.com
- New JavaScript editing features for Web development in Visual Studio 11 Developer Preview | blogs.msdn.com
- Internet Explorer 10 to dump plug-in support for Metro | www.neowin.net
- Back to Basics: Big O notation issues with older .NET code and improving for loops with LINQ deferred execution | feedproxy.google.com
- An MSDN Library for the Windows Dev Center | thirdblogfromthesun.com
C# Application From Start to Finish: Tournament Tracker Course - YouTube
28 videos, 192,192 views, Last updated on Jan 13, 2019
Follow along in this free course as I show you how to create an application in C# from idea through the finished product. Everything is shown on screen and in great detail. Learn how to use SQL databases, CSV text files, custom events, Linq, Lambda expressions, emailing, and more. Everything you learn will be in context of a real application.