<div> @(Html.Kendo().TreeView() .Name("treeview") .TemplateId("treeview-template") .HtmlAttributes(new { @class = "demo-section" }) .DragAndDrop(true) .BindTo(Model.Where(e=>e.ParentFolderID==null).OrderBy(e=>e.Order), mappings => { mappings.For<DAL.Folder>(binding => binding .ItemDataBound((item, folder) => { item.Text = folder.FolderName; item.SpriteCssClasses = "folder"; item.Expanded=true; item.Id = folder.FolderID.ToString(); }) .Children(folder => folder.Folder1)); mappings.For<DAL.Folder>(binding => binding .ItemDataBound((item, folder) => { item.Text = folder.FolderName; item.SpriteCssClasses = " folder"; item.Expanded = true; item.Id = folder.FolderID.ToString(); })); }) ) </div>
<style type="text/css" scoped> .demo-section { width: 200px; } #treeview .k-sprite ,#treeview2 .k-sprite { background-image: url("@Url.Content("/Content/kendo/images/coloricons-sprite.png")"); } .rootfolder { background-position: 0 0; } .folder { background-position: 0 -16px; } .pdf { background-position: 0 -32px; } .html { background-position: 0 -48px; } .image { background-position: 0 -64px; } .delete-link,.edit-link { width: 12px; height: 12px; overflow: hidden; display: inline-block; vertical-align: top; margin: 2px 0 0 3px; -webkit-border-radius: 5px; -mox-border-radius: 5px; border-radius: 5px; } .delete-link{ background: transparent url("@Url.Content("/Content/kendo/images/close.png")") no-repeat 50% 50%; } .edit-link{ background: transparent url("@Url.Content("/Content/kendo/images/edit.png")") no-repeat 50% 50%; } </style>
<a href="#" id="serialize">ذخیره</a>
$('#serialize').click(function () { serialized = serialize(); window.location.href = "Folder/SaveMenu?serial=" + serialized + "!"; }); function serialize() { var tree = $("#treeview").data("kendoTreeView"); var json = treeToJson(tree.dataSource.view()); return JSON.stringify(json); } function treeToJson(nodes) { return $.map(nodes, function (n, i) { var result = { id: n.id}; //var result = { text: n.text, id: n.id, expanded: n.expanded, checked: n.checked }; if (n.hasChildren) result.items = treeToJson(n.children.view()); return result; }); }
var tree = $("#treeview").data("kendoTreeView"); var json = treeToJson(tree.dataSource.view());
var result = { id: n.id}; //var result = { text: n.text, id: n.id, expanded: n.expanded, checked: n.checked }; if (n.hasChildren) result.items = treeToJson(n.children.view());
return JSON.stringify(json);
window.location.href = "Folder/SaveMenu?serial=" + serialized + "!";
"[{\"id\":\"2\"},{\"id\":\"5\",\"items\":[{\"id\":\"3\"},{\"id\":\"6\"},{\"id\":\"7\"}]}]!"
همانطور که میبینید گره دوم که "پوشه چهارم45" نام دارد شامل سه فرزند است که در رشته داده شده با عنوان item شناخته شده است. حال باید این رشته با برنامه نویسی سی شارپ جداسازی کرد :
string serialized; Dictionary<int, int> numbers = new Dictionary<int, int>();
public ActionResult SaveMenu(string serial) { var newfolders = new List<Folder>(); serialized = serial; calculte_serialized(0); return RedirectToAction("Index"); }
void calculte_serialized(int parent) { while (serialized.Length > 0) { var id_index=serialized.IndexOf("id"); if (id_index == -1) { return; } serialized = serialized.Substring(id_index + 5); var quote_index = serialized.IndexOf("\""); var id=serialized.Substring(0, quote_index); numbers.Add(int.Parse(id), parent); serialized = serialized.Substring(quote_index); var condition = serialized.Substring(0,3); switch (condition) { case "\"},": break; case "\",\"": calculte_serialized(int.Parse(id)); break; case "\"}]": return; break; default: break; } } }
calculte_serialized با گرفتن 0 کار خود را شروع میکند یعنی از گره هایی که پدر ندارند و تمام idها را همراه با پدرشان در یک دیکشنری میریزد و هرکجا که به فرزندی برخورد به صورت بازگشتی فراخوانی میشود. پس از اجرای کامل آن ما درخت را در یک دیکشنری به صورت عنصرهای مجزا در اختیار داریم که میتوانیم در پایگاه داده ذخیره کنیم.
اصول کلی طراحی یک اعتبارسنج ساده
در قسمت قبل، تمام اطلاعات فرم لاگین را درون شیء account خاصیت state قرار دادیم. در اینجا نیز شبیه به چنین شیءای را برای ذخیره سازی خطاهای اعتبارسنجی فیلدهای فرم، تعریف میکنیم:
class LoginForm extends Component { state = { account: { username: "", password: "" }, errors: { username: "Username is required" } };
البته در ابتدای کار، خاصیت errors را با یک شیء خالی ({}) مقدار دهی میکنیم و سپس در متد مدیریت ارسال فرم به سرور:
validate = () => { return { username: "Username is required." }; }; handleSubmit = e => { e.preventDefault(); const errors = this.validate(); console.log("Validation errors", errors); this.setState({ errors }); if (errors) { return; } // call the server console.log("Submitted!"); };
- اگر خطایی وجود داشت، به مرحلهی بعد که ارسال فرم به سمت سرور میباشد، نخواهیم رسید و کار را با یک return، خاتمه میدهیم.
- علت فراخوانی متد setState در اینجا، درخواست رندر مجدد فرم، با توجه به خطاهای اعتبارسنجی ممکنی است که به خاصیت errors، اضافه یا به روز رسانی کردهایم.
- نمونهای از خروجی متد validate را نیز در اینجا مشاهده میکنید که تشکیل شدهاست از یک شیء، که هر خاصیت آن، به نام یک فیلد موجود در فرم، اشاره میکند.
پیاده سازی یک اعتبارسنج ساده
در اینجا یک نمونه پیاده سازی ساده و ابتدایی منطق اعتبارسنجی فیلدهای فرم را ملاحظه میکنید:
validate = () => { const { account } = this.state; const errors = {}; if (account.username.trim() === "") { errors.username = "Username is required."; } if (account.password.trim() === "") { errors.password = "Password is required."; } return Object.keys(errors).length === 0 ? null : errors; };
- سپس یک شیء خالی error را تعریف کردهایم.
- در ادامه با توجه به اینکه مقادیر المانهای فرم در state وجود دارند، خالی بودن آنها را بررسی میکنیم. اگر خالی بودند، یک خاصیت جدید را با همان نام المان مورد بررسی، به شیء errors اضافه کرده و پیام خطایی را درج میکنیم.
- در نهایت این شیء errors و یا نال را (در صورت عدم وجود خطایی) بازگشت میدهیم.
برای آزمایش آن، پس از اجرای برنامه، یکبار بدون وارد کردن اطلاعاتی، بر روی دکمهی Login کلیک کنید؛ یکبار هم با وارد کردن اطلاعاتی در فیلدهای مختلف. در این بین کنسول توسعه دهندگان مرورگر را نیز جهت مشاهدهی شیءهای error لاگ شده، بررسی نمائید.
نمایش خطاهای اعتبارسنجی فیلدهای فرم
در قسمت قبل، کامپوننت جدید src\components\common\input.jsx را جهت کاهش کدهای تکراری تعاریف المانهای ورودی، ایجاد کردیم. در اینجا نیز میتوان کار نمایش خطاهای اعتبارسنجی را قرار داد:
import React from "react"; const Input = ({ name, label, value, error, onChange }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input value={value} onChange={onChange} id={name} name={name} type="text" className="form-control" /> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Input;
- سپس با توجه به نکتهی «رندر شرطی عناصر در کامپوننتهای React» در قسمت 5، اگر error مقداری داشته باشد و به true تفسیر شود، آنگاه به صورت خودکار، div ای که دارای کلاسهای بوت استرپی اخطار است به همراه متن خطا، رندر خواهد شد؛ در غیراینصورت هیچ div ای به صفحه اضافه نمیشود.
- اکنون متد رندر کامپوننت فرم لاگین را به صورت زیر تکمیل میکنیم:
render() { const { account, errors } = this.state; return ( <form onSubmit={this.handleSubmit}> <Input name="username" label="Username" value={account.username} onChange={this.handleChange} error={errors.username} /> <Input name="password" label="Password" value={account.password} onChange={this.handleChange} error={errors.password} /> <button className="btn btn-primary">Login</button> </form> ); }
تا اینجا اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، با کلیک بر روی دکمهی Login، خطاهای اعتبارسنجی به صورت زیر ظاهر میشوند:
در این حالت اگر هر دو فیلد را تکمیل کرده و بر روی دکمهی لاگین کلیک کنیم، به خطای زیر در کنسول توسعه دهندگان مرورگر میرسیم:
loginForm.jsx:55 Uncaught TypeError: Cannot read property 'username' of null at LoginForm.render (loginForm.jsx:55)
this.setState({ errors: errors || {} });
اعتبارسنجی فیلدهای یک فرم در حین ورود اطلاعات در آنها
تا اینجا نحوهی اعتبارسنجی فیلدهای ورودی را در حین submit بررسی کردیم. شبیه به همین روش را به حالت onChange و متد handleChange فرم لاگین که در قسمت قبل تکمیل کردیم نیز میتوان اعمال کرد:
handleChange = ({ currentTarget: input }) => { const errors = { ...this.state.errors }; //cloning an object const errorMessage = this.validateProperty(input); if (errorMessage) { errors[input.name] = errorMessage; } else { delete errors[input.name]; } const account = { ...this.state.account }; //cloning an object account[input.name] = input.value; this.setState({ account, errors }); };
- سپس اینبار فقط نیاز داریم اعتبار اطلاعات ورودی یک فیلد را بررسی کنیم و متد validate فعلی، فیلدهای کل فرم را با هم تعیین اعتبار میکند. به همین جهت متد جدید validateProperty را به صورت زیر تعریف میکنیم. اگر این متد خروجی داشت، خاصیت متناظر با آنرا در شیء errors به روز رسانی میکنیم؛ در غیراینصورت این خاصیت را از شیء errors حذف میکنیم تا پیام اشتباهی را نمایش ندهد. در نهایت توسط متد setState، مقدار خاصیت errors را با شیء errors جاری به روز رسانی میکنیم:
validateProperty = ({ name, value }) => { if (name === "username") { if (value.trim() === "") { return "Username is required."; } // ... } if (name === "password") { if (value.trim() === "") { return "Password is required."; } // ... } };
پس از ذخیره سازی این تغییرات، برای آزمایش آن، یکبار حرف a را بجای username وارد کنید و سپس آنرا حذف کنید. بلافاصله پیام خطای مرتبطی نمایش داده خواهد شد و اگر مجددا عبارتی را وارد کنیم، این پیام محو میشود که معادل قسمت delete در کدهای فوق است.
معرفی Joi
تا اینجا، هدف نمایش ساختار یک اعتبارسنج ساده بود. این روش مقیاس پذیر نیست و در ادامه آنرا با یک کتابخانهی اعتبارسنجی بسیار پیشرفته به نام Joi، جایگزین خواهیم کرد که نمونه مثالهای آنرا در اینجا میتوانید مشاهده کنید:
const Joi = require('@hapi/joi'); const schema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), password: Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/), email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }) }); schema.validate({ username: 'abc', birth_year: 1994 });
برای نصب آن، پس از باز کردن پوشهی اصلی برنامه توسط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
> npm install @hapi/joi --save > npm i --save-dev @types/hapi__joi
سپس به کامپوننت فرم لاگین مراجعه کرده و در ابتدای آن، Joi را import میکنیم:
import Joi from "@hapi/joi";
schema = { username: Joi.string() .required() .label("Username"), password: Joi.string() .required() .label("Password") };
سپس ابتدای متد validate قبلی را به صورت زیر بازنویسی میکنیم:
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult);
اکنون اگر برنامه را اجرا کرده و بدون ورود اطلاعاتی، بر روی دکمهی لاگین کلیک کنیم، خروجی زیر در کنسول توسعه دهندگان مرورگر ظاهر میشود:
همانطور که مشاهده میکنید، خروجی Joi، یک شیء است که اگر دارای خاصیت error بود، یعنی خطای اعتبارسنجی رخدادهاست. سپس باید خاصیت آرایهای details این شیء error را جهت یافتن خواص مشکل دار بررسی کرد. هر خاصیت در اینجا با path مشخص میشود. بنابراین قدم بعدی، تبدیل این ساختار، به ساختار شیء errors موجود در state کامپوننت جاری است تا مابقی برنامه بتواند بدون تغییری از آن استفاده کند.
نگاشت شیء دریافتی از Joi، به شیء errors موجود در state کامپوننت لاگین
خاصیت error شیء دریافتی از متد validate کتابخانهی Joi، تنها زمانی ظاهر میشود که خطایی وجود داشته باشد. همچنین خاصیت details آن نیز آرایهای از اشیاء با خواص message و path است. این path نیز یک آرایه است که اولین المان آن، نام خاصیت در حال بررسی است. اکنون میخواهیم این آرایه را تبدیل به یک شیء قابل درک برای برنامه کنیم:
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult); if (!validationResult.error) { return null; } const errors = {}; for (let item of validationResult.error.details) { errors[item.path[0]] = item.message; } return errors; };
اکنون اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، همانند قبل میتوان خطاهای اعتبارسنجی در حین submit را مشاهده کرد:
بازنویسی متد validateProperty توسط Joi
تا اینجا متد validate ساده و ابتدایی خود را با استفاده از امکانات کتابخانهی Joi، بازنویسی کردیم. اکنون نوبت بازنویسی متد اعتبارسنجی در حین تایپ اطلاعات است:
validateProperty = ({ name, value }) => { const userInputObject = { [name]: value }; const propertySchema = Joi.object({ [name]: this.schema[name] }); const { error } = propertySchema.validate(userInputObject, { abortEarly: true }); return error ? error.details[0].message : null; };
تا اینجا بجای this.state.account که به کل فرم اشاره میکند، شیء اختصاصیتر userInputObject را ایجاد کردهایم که معادل اطلاعات فیلد ورودی کاربر است. یک چنین نکتهای را در مورد schema نیز باید رعایت کرد. در اینجا نمیتوان اعتبارسنجی را با this.schema شروع کرد؛ چون این شیء نیز به اطلاعات کل فرم اشاره میکند و نه تک فیلدی که هم اکنون در حال کار با آن هستیم. بنابراین نیاز است یک Joi.object جدید را بر اساس name رسیده، از this.schema کلی، استخراج و تولید کرد؛ مانند propertySchema که مشاهده میکنید.
اکنون میتوان متد validate این شیء اسکیمای جدید را فراخوانی کرد و خاصیت error شیء حاصل از آنرا توسط Object Destructuring، استخراج نمود. در اینجا abortEarly به true تنظیم شدهاست (البته حالت پیشفرض آن است و نیازی به ذکر صریح آن نیست). علت اینجا است که نمایش خطاهای بیش از اندازه به کاربر، برای او گیج کننده خواهند بود؛ به همین جهت هربار، اولین خطای حاصل را به او نمایش میدهیم.
غیرفعالسازی دکمهی submit در صورت شکست اعتبارسنجیهای فرم
در ادامه میخواهیم دکمهی submit فرم لاگین، تا زمانیکه اعتبارسنجی آن با موفقیت به پایان برسد، غیرفعال باشد:
<button disabled={this.validate()} className="btn btn-primary"> Login </button>
فراخوانی setStateهای تعریف شدهی در متدهای رویدادگردان این فرم هستند که سبب رندر مجدد کامپوننت شده و در این بین در متد رندر، کار بررسی مجدد مقدار نهایی متد validate صورت میگیرد تا بر اساس آن، فعال یا غیرفعال بودن دکمهی Login، مشخص شود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-19.zip
In this post, we are going to write about what we consider to be the best practices while developing the .NET Core Web API project. How we can make it better and how to make it more maintainable.
We are going to go through the following sections:
در این مخزن روشهای بهینه و توصیه شده جهت ساخت برنامههای تحت وب با استفاده از Net Core. در قالب 12 پروژه پیاده سازی شده است که منبع خوبی جهت الگو برداری است.
Boilerplate for ASP.NET Core reference application with Entity Framework Core, demonstrating a layered application architecture with DDD best practices. Implements NLayer Hexagonal architecture (Core, Application, Infrastructure and Presentation Layers) and Domain Driven Design (Entities, Repositories, Domain/Application Services, DTO's...) and aimed to be a Clean Architecture, with applying SOLID principles in order to use for a project template. Also implements best practices like loosely-coupled, dependency-inverted architecture and using design patterns such as Dependency Injection, logging, validation, exception handling, localization and so on.
Angular 5.2 منتشر شد
در نگارش RC4 پوشهی bundles هم به router جدید اضافه شدهاست و نیازی به تغییری در فایل systemjs.config.js نیست.
constructor(private _productService: ProductService, private _router: Router) { }
بررسی تغییرات Blazor 8x - قسمت هشتم - مدیریت انتقال اطلاعات Pre-Rendering سمت سرور، به جزایر تعاملی
بررسی نحوهی عملکرد سرویس PersistentComponentState
سرویس PersistentComponentState، در داتنت 6، به Blazor اضافه شد و امکان جدیدی نیست. قسمتی از این مباحث جدید SSR که بهنظر مختص به Blazor 8x هستند، پیشتر هم وجود داشتند؛ تحت عنوان pre-rendering. برای مثال فقط کافی بودن تا در برنامههای Blazor Server قبلی، فایل Host.cshtml_ را به صورت زیر ویرایش کرد تا pre-rendering فعال شود:
<component type="typeof(App)" render-mode="ServerPrerendered" />
<body> <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> <persist-component-state /> </body>
@page "/" @implements IDisposable @inject PersistentComponentState applicationState @inject IUserRepository userRepository @foreach(var user in users) { <ShowUserInformation user="@item"></ShowUserInformation> } @code { private const string cachingKey = "something_unique"; private List<User> users = new(); private PersistingComponentStateSubscription persistingSubscription; protected override async Task OnInitializedAsync() { persistingSubscription = applicationState.RegisterOnPersisting(PersistData); if (applicationState.TryTakeFromJson<List<User>>(cachingKey, out var restored)) { users = restored; } else { users = await userRepository.GetAllUsers(); } } public void Dispose() { persistingSubscription.Dispose(); } private Task PersistData() { applicationState.PersistAsJson(cachingKey, users); return Task.CompletedTask; } }
- ابتدا تزریق سرویس PersistentComponentState را مشاهده میکنید. این همان کامپوننتی است که کار کش کردن اطلاعات را مدیریت میکند.
- سپس فراخوانی متد RegisterOnPersisting انجام شدهاست. متدی که توسط آن ثبت میشود، در حین عملیات pre-rendering فراخوانی میشود تا اطلاعاتی را کش کند. نحوهی این کش شدن را در ادامهی مطلب بررسی میکنیم.
- سپس فراخوانی متد TryTakeFromJson رخدادهاست. اگر این متد اطلاعاتی را برگرداند، یعنی pre-rendering سمت سرور پیشتر انجام شده و اطلاعاتی کش شده، برای دریافت در سمت کلاینت، وجود داشته و نیازی به مراجعهی مجدد و دوباره، به بانک اطلاعاتی نیست.
- دراینجا یک قسمت Dispose را هم مشاهده میکنید تا اگر کاربر به صفحهی دیگری مراجعه کرد، متد ثبت شدهی در اینجا، از لیست مواردی که باید در حین pre-rendering سریالایز شوند، حذف گردد.
اگر از این روش استفاده نشود، به علت دوبار فراخوانی شدن متد OnInitializedAsync یک کامپوننت به همراه pre-rendering، اطلاعاتی که در حین pre-rendering کامپوننت از بانک اطلاعاتی، برای تولید محتوای استاتیک صفحه در سمت سرور دریافت شده، با فعالسازی آتی تعاملی سمت کلاینت آن کامپوننت، از دست خواهد رفت و در این حالت کلاینت باید مجددا این اطلاعات را از بانک اطلاعاتی درخواست کند. بنابراین هدف از persist-component-state، حفظ دادهها در بین دو رندر است؛ رندر اولی که در سمت سرور انجام میشود و رندر دومی که متعاقبا در سمت کلاینت رخ میدهد.
یک نکته: به یک چنین قابلیتی در فریمورکهای دیگر «hydration» هم گفته میشود که در اصل یک شیء دیکشنری است که مقدار شیء حالت را در سمت سرور دریافت کرده و آنرا در زمان فعال شدن سمت کلاینت کامپوننت، برای استفادهی مجدد مهیا میکند.
سؤال: اطلاعات سرویس PersistentComponentState کجا ذخیره میشوند؟
همانطور که در مثال فوق هم مشاهده کردید، سرویس PersistentComponentState، این state را به صورت JSON ذخیره میکند. اما ... این اطلاعات دقیقا کجا ذخیره میشوند؟
برای مشاهدهی آنها، فقط کافی است به source صفحه مراجعه کنید تا با دو مقدار مخفی رمزنگاری شدهی زیر در انتهای HTML صفحه مواجه شوید!
<!--Blazor-Server-Component-State:CfDJXyz-> <!--Blazor-WebAssembly-Component-State:eyJVc2Xyz->
مایکروسافت از این نوع کارها پیشتر در ASP.NET Web forms توسط ViewStateها انجام دادهاست! البته ViewStateها جهت نگهداری اطلاعات وضعیت کلاینت، به سمت سرور ارسال میشوند؛ اما این Component-Stateهای مخفی، فقط یکبار توسط قسمت کلاینت خوانده میشوند و هدف آنها ارسال اطلاعاتی به سمت سرور نیست.
یک نکته: همانطور که عنوان شد، در نگارشهای قبلی Blazor، از تگهلپری به نام persist-component-state برای درج این اطلاعات در انتهای صفحه استفاده میکردند. استفاده از این تگهلپر در Blazor 8x منسوخ شده و به صورت خودکار دادههای تمام سرویسهای PersistentComponentState فعالی که توسط PersistAsJson اطلاعاتی را ذخیره میکنند، جمع آوری و به صورت یکجا در انتهای صفحه به صورت رمزنگاری شده، درج میکنند.
روش خاموش کردن Pre-rendering
برای خاموش کردن pre-rendering نیاز است پارامتر همنامی را به نحو زیر با false مقدار دهی کرد:
@rendermode renderMode @code { private static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false); }
بازنویسی مثال قسمت قبل با استفاده از سرویس PersistentComponentState
در قسمت قبل هرچند روش مواجه شدن با مشکل دوبار فراخوانی شدن متد آغازین یک کامپوننت را در سمت سرور و کلاینت بررسی کردیم، اما ... در نهایت دوبار مراجعه به بانک اطلاعاتی انجام میشود؛ یکبار در زمان pre-rendering و با مراجعهی مستقیم به یک سرویس سمت سرور و یکبار دیگر هم در زمان فراخوانی httpClient.GetFromJsonAsync در سمت کلاینت برای دریافت اطلاعات مورد نیاز از یک Web API Endpoint. اگر بخواهیم اطلاعات لیست محصولات دریافتی سمت سرور را به سمت کلاینت منتقل کنیم و مجددا آنرا از بانک اطلاعاتی دریافت نکنیم، راهحل سوم آن، استفاده از سرویس PersistentComponentState است.
در کدهای زیر، بازنویسی کامپوننت محصولات مشابه را مشاهده میکنید که کمی پیچیدهتر شدهاست. اینبار لیست محصولات مشابه، در همان بار اول رندر کامپوننت نمایش داده میشوند و نه پس از کلیک بر روی دکمهای. به همین جهت باید کار مدیریت دوبار فراخوانی متد رویدادگردان OnInitializedAsync را به درستی انجام داد. متد OnInitializedAsync یکبار در حین پیشرندر سمت سرور اجرا میشود و بار دیگر زمانیکه وباسمبلی جاری در مرورگر به صورت کامل دریافت شده و فعال میشود؛ یعنی برای بار دوم، کدهای اجرایی آن سمت کلاینت هستند.
در این مثال با استفاده از سرویس PersistentComponentState، اطلاعات دریافت شدهی در حین pre-rendering ابتدایی رخدادهی در سمت سرور، در متد OnPersistingAsync، کش شده و در حین فعال شدن وباسمبلی مرتبط در سمت کلاینت، با استفاده از متد PersistentState.TryTakeFromJson، از کش خوانده میشوند و دیگر دوبار رفت و برگشت به بانک اطلاعاتی را شاهد نخواهیم بود.
@implements IDisposable @inject IProductStore ProductStore @inject PersistentComponentState PersistentState <h3>Related products</h3> @if (_relatedProducts == null) { <p>Loading...</p> } else { <div class="mt-3"> @foreach (var item in _relatedProducts) { <a href="/ProductDetails/@item.Id"> <div class="col-sm"> <h5 class="mt-0">@item.Title (@item.Price)</h5> </div> </a> } </div> } @code{ private const string StateCachingKey = "products"; private IList<Product>? _relatedProducts; private PersistingComponentStateSubscription _persistingSubscription; [Parameter] public int ProductId { get; set; } protected override async Task OnInitializedAsync() { _persistingSubscription = PersistentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); if (PersistentState.TryTakeFromJson<IList<Product>>(StateCachingKey, out var restored)) { _relatedProducts = restored; } else { await Task.Delay(500); // Simulates asynchronous loading _relatedProducts = await ProductStore.GetRelatedProducts(ProductId); } } private Task OnPersistingAsync() { PersistentState.PersistAsJson(StateCachingKey, _relatedProducts); return Task.CompletedTask; } public void Dispose() { _persistingSubscription.Dispose(); } }
نکتهی مهم: فعلا کدهای فوق فقط برای حالت بارگذاری اولیهی کامپوننت درست کار میکنند. یعنی اگر نگارش وباسمبلی کامپوننت پس از وقوع پیشرندر سمت سرور، در مرورگر دریافت و بارگذاری کامل شود؛ اما برای دفعات بعدی مراجعهی به این صفحه با استفاده از enhanced navigation و راهبری بهبود یافته که در قسمت ششم این سری بررسی کردیم ... دیگر کار نمیکنند و در این حالت کش شدنی رخ نمیدهد که در نتیجه، شاهد دوبار رفت و برگشت به بانک اطلاعاتی خواهیم بود و اساسا استفادهی از PersistentComponentState را زیر سؤال میبرد؛ چون در حین راهبری بهبود یافته، از آن استفاده نمیشود (قسمت PersistentState.TryTakeFromJson آن، هیچگاه اطلاعاتی را از کش نمیخواند). اطلاعات بیشتر