React components for efficiently rendering large lists and tabular data
اشتراکها
فریم ورک Material UI
اشتراکها
NET 6 Preview 3. منتشر شد
You can download .NET 6 Preview 3, for Windows, macOS, and Linux.
نظرات مطالب
React 16x - قسمت 31 - React Hooks - بخش 2 - مقایسه حالتهای مختلف مدیریت حالت با useState Hook
با تشکر
در مورد نوع کامپوننتها 2 سوال داشتم ، ممنون میشم راهنمایی بفرمایید.
ترجیحا سعی کنیم کامپوننتها را به صورت Stateless Functional Component بنویسیم؟
آیا Stateless Functional Component حجم کمتری یا سرعت بهتری نسبت به Class Components دارند؟
در مورد نوع کامپوننتها 2 سوال داشتم ، ممنون میشم راهنمایی بفرمایید.
ترجیحا سعی کنیم کامپوننتها را به صورت Stateless Functional Component بنویسیم؟
آیا Stateless Functional Component حجم کمتری یا سرعت بهتری نسبت به Class Components دارند؟
در زمان ارائهی ASP.NET Core 2.1، ویژگی جدیدی به نام [ApiController] ارائه شد که با استفاده از آن، یکسری اعمال توکار جهت سهولت کار با Web API توسط خود فریمورک انجام میشوند؛ برای مثال عدم نیاز به بررسی وضعیت ModelState و بررسی خودکار آن با علامتگذاری یک کنترلر به صورت ApiController. یکی دیگر از این ویژگیهای توکار، تبدیل خروجی تمام status codeهای بزرگتر و یا مساوی 400 یا همان Bad Request، به شیء جدید و استاندارد ProblemDetails است:
بازگشت یک چنین خروجی یکدست و استانداردی، استفادهی از آنرا توسط کلاینتها، ساده و قابل پیشبینی میکند. البته باید درنظر داشت که اگر در اینحالت، برنامه یک استثنای معمولی را سبب شود، ProblemDetails ای بازگشت داده نمیشود. اگر برنامه در حالت توسعه اجرا شود، با استفاده از میانافزار app.UseDeveloperExceptionPage، یک صفحهی نمایش جزئیات خطا ظاهر میشود و اگر برنامه در حالت تولید و ارائهی نهایی اجرا شود، یک صفحهی خالی (بدون داشتن response body) با status code مساوی 500 بازگشت داده میشود. این کمبود ویژه و امکانات سفارشی سازی بیشتر آن، به صورت توکار به ASP.NET Core 7x اضافه شدهاند و دیگر نیازی به استفاده از کتابخانههای ثالث دیگری برای انجام آن نیست.
ProblemDetails بر اساس RFC7807 طراحی شدهاست
RFC7807، قالب استانداردی را برای ارائهی خطاهای HTTP APIها تعریف میکند تا نیازی به وجود تعاریف متعددی در این زمینه نباشد و خروجی آن قابل پیشبینی و قابل بررسی توسط تمام کلاینتهای یک API باشد. کلاس ProblemDetails در ASP.NET Core نیز بر همین اساس طراحی شدهاست.
این RFC دو فرمت خروجی را بر اساس مقدار مشخص شدهی در هدر Content-Type بازگشت داده شده، مجاز میداند:
که با توجه به این هدر ارسالی، اگر از یک کلاینت از نوع HttpClient استفاده کنیم، میتوان بر اساس مقدار ویژهی «application/problem+json» تشخیص داد که خروجی API دریافتی، به همراه خطا است و نحوهی پردازش آن به صورت زیر خواهد بود:
در اینجا بدنهی اصلی شیء ProblemDetails بازگشت داده شده، میتواند به همراه اعضای زیر باشد:
- type: یک رشتهاست که به آدرس مستندات HTML ای مرتبط با خطای بازگشت داده شده، اشاره میکند.
- title: رشتهای است که خلاصهی خطای رخداده را بیان میکند.
- detail: رشتهای است که توضیحات بیشتری را در مورد خطای رخداده، بیان میکند.
- instance: رشتهای است که به آدرس محل بروز خطا اشاره میکند.
- status: عددی است که بیانگر HTTP status code بازگشتی از سمت سرور است.
البته اگر ویژگی ApiController بر روی کنترلرهای خود استفاده نمیکنید، میتوانید این خروجی را به صورت زیر هم با استفاده از return Problem، تولید کنید:
امکان افزودن اعضای سفارشی به شیء ProblemDetails
امکان بسط این خروجی، با افزودن اعضای سفارشی نیز پیشبینی شدهاست. یک نمونهی متداول و پرکاربرد آن، بازگشت خطاهای مرتبط با اعتبارسنجی اطلاعات رسیدهاست:
در اینجا عضو جدید errors را بنابر نیاز این مسالهی خاص، مشاهده میکنید که در صورت استفاده از ویژگی ApiController بر روی کنترلرهای Web API، به صورت خودکار توسط ASP.NET Core تولید میشود و نیازی به تنظیم خاصی و یا کدنویسی اضافهتری ندارد. کلاس مخصوص آن نیز ValidationProblemDetails است.
جهت افزودن اعضای سفارشی دیگری به شیء ProblemDetails میتوان به صورت زیر عمل کرد:
شیء ProblemDetails، به همراه خاصیت Extensions است که میتوان به آن یک <Dictionary<string, object را انتساب داد و نمونهای از آنرا در مثال فوق مشاهده میکنید. این مثال سبب میشود تا عضو جدیدی با کلید دلخواه invalidParams، به همراه لیستی از name و reasonها به خروجی نهایی اضافه شود. مقدار این کلید، از نوع object است؛ یعنی هر شیء دلخواهی را در اینجا میتوان تعریف و استفاده کرد.
معرفی سرویس جدید ProblemDetails در دات نت 7
در دات نت 7 میتوان سرویسهای جدید ProblemDetails را به نحو زیر به برنامه اضافه کرد:
پس از آن به 3 روش مختلف میتوان از امکانات این سرویسها استفاده کرد:
الف) با اضافه کردن میانافزار مدیریت خطاها
پس از آن، هر استثنای مدیریت نشدهای نیز به صورت یک ProblemDetails ظاهر میشود و دیگر همانند قبل، سبب نمایش یک صفحهی خالی نخواهد شد.
ب) با افزودن میانافزار StatusCodePages
در این حالت مواردی که استثناء شمرده نمیشوند مانند 404، در صورت بروز رسیدن به یک مسیریابی یافت نشده و یا 405، در صورت درخواست یک HTTP method غیرمعتبر نیز توسط یک ProblemDetails استاندارد مدیریت میشوند.
ج) با افزودن میانافزار صفحهی استثناءهای توسعه دهندهها
به این ترتیب در خروجی ProblemDetails، اطلاعات بیشتری از استثناء رخداده، مانند استکتریس آن ظاهر خواهد شد.
امکان بازگشت سادهتر یک ProblemDetails سفارشی در دات نت 7
برای سفارشی سازی خروجی ProblemDetails، علاوه بر راهحلی که پیشتر در این مطلب مطرح شد، میتوان در دات نت 7 از روش تکمیلی ذیل نیز استفاده کرد:
به این ترتیب در صورت لزوم میتوان یک عضو سفارشی سراسری را به تمام اشیاء ProblemDetails برنامه به صورت خودکار اضافه کرد و یا اگر میخواهیم این مورد را کمی اختصاصیتر کنیم، میتوان به صورت زیر عمل کرد:
الف) تعریف یک ErrorFeature سفارشی
در ASP.NET Core میتوان به شیء HttpContext.Features قابل تنظیم در هر اکشن متدی، اشیاء دلخواهی را مانند شیء سفارشی فوق، اضافه کرد و سپس در قسمت options.CustomizeProblemDetails تنظیماتی که ذکر شد، به دریافت و تنظیم آن، واکنش نشان داد.
ب) تنظیم مقدار ErrorFeature سفارشی در اکشن متدها
پس از تعریف شیءایی که قرار است به HttpContext.Features اضافه شود، اکنون روش تنظیم و مقدار دهی آنرا در یک اکشن متد، در مثال فوق مشاهده میکنید.
ج) واکنش نشان دادن به دریافت ErrorFeature سفارشی
پس از تنظیم HttpContext.Features در اکشن متدی، میتوان در options.CustomizeProblemDetails فوق، توسط متد ctx.HttpContext.Features.Get به آن شیء خاص تنظیم شده، در صورت وجود دسترسی یافت و سپس جزئیات بیشتری را از آن استخراج و مقادیر ctx.ProblemDetails جاری را که قرار است به کاربر بازگشت داده شوند، بازنویسی کرد و یا تغییر داد.
امکان تبدیل سادهتر اطلاعات استثناءهای سفارشی به یک ProblemDetails سفارشی در دات نت 7
بجای استفاده از تنظیمات services.AddProblemDetails جهت بازنویسی مقدار شیء ProblemDetails بازگشتی، میتوان جزئیات میانافزار app.UseExceptionHandler را نیز سفارشی سازی کرد و به بروز استثناءهای خاصی واکنش نشان داد. برای مثال فرض کنید یک استثنای سفارشی را به صورت زیر طراحی کردهاید:
و سپس در اکشن متدی، سبب بروز آن شدهاید:
اکنون میتوان در میانافزار مدیریت استثناءهای برنامه، نسبت به مدیریت این استثناء خاص، واکشن نشان داد و ProblemDetails متناظری را تولید و بازگشت داد:
در اینجا نحوهی کار با سرویس توکار IProblemDetailsService و سپس دسترسی به IExceptionHandlerFeature و استثنای صادر شده را مشاهده میکنید. پس از آن بر اساس نوع و اطلاعات این استثناء، میتوان یک ProblemDetails مخصوص را تولید و در خروجی ثبت کرد.
{ "type": "https://example.com/probs/out-of-credit", "title": "You do not have enough credit.", "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc", "status": 403, }
ProblemDetails بر اساس RFC7807 طراحی شدهاست
RFC7807، قالب استانداردی را برای ارائهی خطاهای HTTP APIها تعریف میکند تا نیازی به وجود تعاریف متعددی در این زمینه نباشد و خروجی آن قابل پیشبینی و قابل بررسی توسط تمام کلاینتهای یک API باشد. کلاس ProblemDetails در ASP.NET Core نیز بر همین اساس طراحی شدهاست.
این RFC دو فرمت خروجی را بر اساس مقدار مشخص شدهی در هدر Content-Type بازگشت داده شده، مجاز میداند:
- JSON: “application/problem+json” media type
- XML: “application/problem+xml” media type
که با توجه به این هدر ارسالی، اگر از یک کلاینت از نوع HttpClient استفاده کنیم، میتوان بر اساس مقدار ویژهی «application/problem+json» تشخیص داد که خروجی API دریافتی، به همراه خطا است و نحوهی پردازش آن به صورت زیر خواهد بود:
var mediaType = response.Content.Headers.ContentType?.MediaType; if (mediaType != null && mediaType.Equals("application/problem+json", StringComparison.InvariantCultureIgnoreCase)) { var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>(null, ct) ?? new ProblemDetails(); // ... }
- type: یک رشتهاست که به آدرس مستندات HTML ای مرتبط با خطای بازگشت داده شده، اشاره میکند.
- title: رشتهای است که خلاصهی خطای رخداده را بیان میکند.
- detail: رشتهای است که توضیحات بیشتری را در مورد خطای رخداده، بیان میکند.
- instance: رشتهای است که به آدرس محل بروز خطا اشاره میکند.
- status: عددی است که بیانگر HTTP status code بازگشتی از سمت سرور است.
البته اگر ویژگی ApiController بر روی کنترلرهای خود استفاده نمیکنید، میتوانید این خروجی را به صورت زیر هم با استفاده از return Problem، تولید کنید:
[HttpPost("/sales/products/{sku}/availableForSale")] public async Task<IActionResult> AvailableForSale([FromRoute] string sku) { return Problem( "Product is already Available For Sale.", "/sales/products/1/availableForSale", 400, "Cannot set product as available.", "http://example.com/problems/already-available"); }
امکان بسط این خروجی، با افزودن اعضای سفارشی نیز پیشبینی شدهاست. یک نمونهی متداول و پرکاربرد آن، بازگشت خطاهای مرتبط با اعتبارسنجی اطلاعات رسیدهاست:
HTTP/1.1 400 Bad Request Content-Type: application/problem+json Content-Language: en { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "User": [ "The user name is not verified." ] } }
جهت افزودن اعضای سفارشی دیگری به شیء ProblemDetails میتوان به صورت زیر عمل کرد:
namespace WebApplication.Controllers { [ApiController] [Route("[controller]")] public class DemoController : ControllerBase { [HttpPost] public ActionResult Post() { var problemDetails = new ProblemDetails { Detail = "The request parameters failed to validate.", Instance = null, Status = 400, Title = "Validation Error", Type = "https://example.net/validation-error", }; problemDetails.Extensions.Add("invalidParams", new List<ValidationProblemDetailsParam>() { new("name", "Cannot be blank."), new("age", "Must be great or equals to 18.") }); return new ObjectResult(problemDetails) { StatusCode = 400 }; } } public class ValidationProblemDetailsParam { public ValidationProblemDetailsParam(string name, string reason) { Name = name; Reason = reason; } public string Name { get; set; } public string Reason { get; set; } } }
معرفی سرویس جدید ProblemDetails در دات نت 7
در دات نت 7 میتوان سرویسهای جدید ProblemDetails را به نحو زیر به برنامه اضافه کرد:
services.AddProblemDetails();
الف) با اضافه کردن میانافزار مدیریت خطاها
app.UseExceptionHandler();
ب) با افزودن میانافزار StatusCodePages
app.UseStatusCodePages();
ج) با افزودن میانافزار صفحهی استثناءهای توسعه دهندهها
app.UseDeveloperExceptionPage();
امکان بازگشت سادهتر یک ProblemDetails سفارشی در دات نت 7
برای سفارشی سازی خروجی ProblemDetails، علاوه بر راهحلی که پیشتر در این مطلب مطرح شد، میتوان در دات نت 7 از روش تکمیلی ذیل نیز استفاده کرد:
builder.Services.AddProblemDetails(options => options.CustomizeProblemDetails = ctx => ctx.ProblemDetails.Extensions.Add("MachineName", Environment.MachineName));
الف) تعریف یک ErrorFeature سفارشی
public class MyErrorFeature { public ErrorType Error { get; set; } } public enum ErrorType { ArgumentException }
ب) تنظیم مقدار ErrorFeature سفارشی در اکشن متدها
[HttpGet("{value}")] public IActionResult MyErrorTest(int value) { if (value <= 0) { var errorType = new MyErrorFeature { Error = ErrorType.ArgumentException }; HttpContext.Features.Set(errorType); return BadRequest(); } return Ok(value); }
ج) واکنش نشان دادن به دریافت ErrorFeature سفارشی
services.AddProblemDetails(options => options.CustomizeProblemDetails = ctx => { var MyErrorFeature = ctx.HttpContext.Features.Get<MyErrorFeature>(); if (MyErrorFeature is not null) { (string Title, string Detail, string Type) details = MyErrorFeature.Error switch { ErrorType.ArgumentException => ( nameof(ArgumentException), "This is an argument-exception.", "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1" ), _ => ( nameof(Exception), "default-exception", "https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1" ) }; ctx.ProblemDetails.Title = details.Title; ctx.ProblemDetails.Detail = details.Detail; ctx.ProblemDetails.Type = details.Type; } } );
امکان تبدیل سادهتر اطلاعات استثناءهای سفارشی به یک ProblemDetails سفارشی در دات نت 7
بجای استفاده از تنظیمات services.AddProblemDetails جهت بازنویسی مقدار شیء ProblemDetails بازگشتی، میتوان جزئیات میانافزار app.UseExceptionHandler را نیز سفارشی سازی کرد و به بروز استثناءهای خاصی واکنش نشان داد. برای مثال فرض کنید یک استثنای سفارشی را به صورت زیر طراحی کردهاید:
public class MyCustomException : Exception { public MyCustomException( string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest ) : base(message) { StatusCode = statusCode; } public HttpStatusCode StatusCode { get; } }
[HttpGet("{value}")] public IActionResult MyErrorTest(int value) { if (value <= 0) { throw new MyCustomException("The value should be positive!"); } return Ok(value); }
app.UseExceptionHandler(exceptionHandlerApp => { exceptionHandlerApp.Run(async context => { context.Response.ContentType = "application/problem+json"; if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService) { var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>(); var exceptionType = exceptionHandlerFeature?.Error; if (exceptionType is not null) { (string Title, string Detail, string Type, int StatusCode) details = exceptionType switch { MyCustomException MyCustomException => ( exceptionType.GetType().Name, exceptionType.Message, "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1", context.Response.StatusCode = (int)MyCustomException.StatusCode ), _ => ( exceptionType.GetType().Name, exceptionType.Message, "https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1", context.Response.StatusCode = StatusCodes.Status500InternalServerError ) }; await problemDetailsService.WriteAsync(new ProblemDetailsContext { HttpContext = context, ProblemDetails = { Title = details.Title, Detail = details.Detail, Type = details.Type, Status = details.StatusCode } }); } } }); });
An alternative query compiler for Microsoft.EntityFrameworkCore.SqlServer that supports complex queries and operators and FOR JSON.
با سلام؛ قبل از هر چیز ممنون از آموزش خوبتون. یک سوال در رابطه با قسمت اول این آموزش داشتم. من دقیقا با آموزش شما پیش رفتم و در قسمت هفتم آموزشتون در اجرای پروژه دچار مشکل شدم و در مرورگرم با خطای زیر مواجه شدم:
HTTP Error 403.14 - Forbidden The Web server is configured to not list the contents of this directory. Most likely causes: A default document is not configured for the requested URL, and directory browsing is not enabled on the server. Things you can try: If you do not want to enable directory browsing, ensure that a default document is configured and that the file exists. Enable directory browsing using IIS Manager. Open IIS Manager. In the Features view, double-click Directory Browsing. On the Directory Browsing page, in the Actions pane, click Enable. Verify that the configuration/system.webServer/directoryBrowse@enabled attribute is set to true in the site or application configuration file. Detailed Error Information: Module DirectoryListingModule Notification ExecuteRequestHandler Handler StaticFile Error Code 0x00000000 Requested URL http://localhost:80/3724/ Physical Path C:\inetpub\wwwroot\3724\ Logon Method Anonymous Logon User Anonymous
اشتراکها
croc ابزاری است که به هر دو کامپیوتری امکان انتقال ساده و ایمن فایلها و فولدرها را می دهد.
croc
is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, croc is the only CLI file-transfer tool that does all of the following:
- allows any two computers to transfer data (using a relay)
- provides end-to-end encryption (using PAKE)
- enables easy cross-platform transfers (Windows, Linux, Mac)
- allows multiple file transfers
- allows resuming transfers that are interrupted
- local server or port-forwarding not needed
- ipv6-first with ipv4 fallback
پیشنهادها
C# 7 - Ref Returns and Ref Locals
Ref Returns In C# 7.0
Why ref locals allow only a single binding
C# 7.0 – Ref returns and Locals
C# 7 Feature Proposal: Ref Returns and Locals
C# 7.0 Out Vars and Ref Returns
C#7: Better Performance with Ref Locals, and Ref and Async Returns
مستندات رسمی
Why ref locals allow only a single binding
C# 7.0 – Ref returns and Locals
C# 7 Feature Proposal: Ref Returns and Locals
C# 7.0 Out Vars and Ref Returns
C#7: Better Performance with Ref Locals, and Ref and Async Returns
مستندات رسمی
پیش از ارائه نهایی مطلب، تمام کدهای آن با VS 2017 RTM آزمایش و بررسی شوند.
در این قسمت میخواهیم بجای دریافت اطلاعات توضیحات یک اتاق، توسط یک text area متداول، برای مثال از Quill rich text editor استفاده کنیم. برای این منظور میتوان از کامپوننت Blazor محصور کنندهی آن به نام Blazored TextEditor کمک گرفت.
نصب کامپوننت Blazored TextEditor
ابتدا نیاز است بستهی نیوگت آنرا با اجرای دستور زیر، به پروژهی Blazor خود اضافه کرد:
و همچنین کتابخانهی اصلی quill را نیز در مسیر wwwroot/lib/quill نصب میکنیم:
سپس به فایل Pages\_Host.cshtml مراجعه کرده و ابتدا مداخل تعریف فایلهای CSS آنرا اضافه میکنیم:
و در ادامه سه مدخل اسکریپتی زیر را نیز به قسمت پیش از بسته شدن تگ body، اضافه میکنیم:
اگر برنامهی مورد نظر از نوع Blazor WASM است، این تنظیمات به فایل wwwroot\index.html منتقل میشوند.
و در آخر جهت سهولت کار با این کامپوننت میتوان فضای نام آنرا به فایل BlazorServer.App\_Imports.razor به صورت زیر اضافه کرد:
استفاده از کامپوننت Blazored.TextEditor در کامپوننت HotelRoomUpsert.razor
میخواهیم در کامپوننت HotelRoomUpsert.razor مثال این سری، بجای کامپوننت InputTextArea مورد استفاده، از یک HTML Editor استفاده کنیم:
- در اینجا قسمت محتوای EditorContent مثال آنرا خالی کردهایم.
- همانطور که ملاحظه میکنید، این تعریف به همراه یک ارجاع به وهلهای از آن نیز هست:
به همین جهت نیاز است فیلد متناظر با آنرا در قسمت کدهای کامپوننت، به صورت زیر تعریف کرد:
تا اینجا اگر برنامه را اجرا کنیم، به خروجی زیر میرسیم:
برای تغییر اندازه و مقدار placeholder پیشفرض آن، میتوان به صورت زیر عمل کرد:
تنظیم و دریافت متن نمایشی HTML Editor
مطابق مستندات این کامپوننت، روش تنظیم متن نمایشی آن، به کمک متد LoadHTMLContent است. به همین جهت متد زیر را به کدهای کامپوننت جاری اضافه میکنیم:
بنابراین روش متداول two-way binding در اینجا کار نمیکند و باید متن این ادیتور را به نحو فوق تنظیم کرد و برای مثال در زمان بارگذاری اولیهی این کامپوننت و در حالت ویرایش، متن دریافتی از بانک اطلاعاتی را به ادیتور فوق ارسال نمود:
و یا در زمان ثبت اولیه و یا حتی در حالت ویرایش اطلاعات در متد HandleHotelRoomUpsert، با استفاده از متد GetHTML آن، خاصیت HotelRoomModel.Details را مقدار دهی اولیه کرد:
مشکل! ادیتور در زمان ویرایش یک رکورد، اطلاعات پیشین را نمایش نمیدهد!
پس از اعمال تغییرات فوق، برنامه را اجرا میکنیم. سپس یک اتاق جدید را اضافه کرده و در لیست نمایش اتاقها، گزینهی ویرایش آنرا انتخاب میکنیم. در این حالت هرچند کار مقدار دهی HotelRoomModel.Details در زمان ثبت اطلاعات انجام شده، اما ... در زمان ویرایش چیزی نمایش داده نمیشود و تغییراتی را که به متد رویدادگردان OnInitializedAsync اضافه کردهایم، عمل نمیکنند.
در این مورد در قسمت بررسی چرخهی حیات کامپوننتها توضیحاتی ابتدایی ارائه شد:
«رویدادهای OnAfterRender و OnAfterRenderAsync
پس از هر بار رندر کامپوننت، این متدها فراخوانی میشوند. در این مرحله کار بارگذاری کامپوننت، دریافت اطلاعات و نمایش آنها به پایان رسیدهاست. یکی از کاربردهای آن، آغاز کامپوننتهای جاوا اسکریپتی است که برای کار، نیاز به DOM را دارند؛ مانند نمایش یک modal بوت استرپی.»
بنابراین در این حالت خاص که ادیتور جاوا اسکریپتی مورد استفاده، پس از رندر کامل UI نمایش داده میشود، قرار دادن متد SetHTML در روال رویدادگردان OnInitializedAsync کار نخواهد کرد و باید آنرا به روال رویدادگردان OnAfterRender انتقال دهیم:
پس از این تغییرات هم باز متن وارد شدهی در قسمت توضیحات، در حالت ویرایش نمایش داده نمیشود! علت آنرا نیز در مطلب بررسی چرخهی حیات کامپوننتها بررسی کردیم: «یک نکته: هر تغییری که در مقادیر فیلدها در این رویدادها صورت گیرند، به UI اعمال نمیشوند؛ چون در مرحلهی آخر رندر UI قرار دارند.» به همین جهت نیاز به فراخوانی دستی StateHasChanged وجود دارد:
مشکل! اگر در این حالت سعی کنیم متنی را در ادیتور وارد کنیم، میسر نیست و همچنین CPU Usage سیستم به 100 درصد رسیدهاست!
علت اینجا است که فراخوانی StateHasChanged، هر چند سبب رندر مجدد UI میشود، اما چون در پایان کار رندر قرار داریم، یک حلقهی بینهایت را سبب خواهد شد. به همین جهت باید در متد OnAfterRenderAsync، بر اساس پارامتر firstRender، از رندرهای بعدی جلوگیری کرد:
در اینجا هم مدیریت firstRender را مشاهده میکنید، تا دیگر یک حلقهی بینهایت رخ ندهد و هم حلقهای را جهت منتظر ماندن تا بارگذاری کامل Quill در این مثال. این افزونهی جاوا اسکریپتی، حتی پس از پایان رندر کامپوننت هم نیاز به مدت زمانی دارد تا بتواند کامل بارگذاری شده و قابل استفاده شود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-20.zip
نصب کامپوننت Blazored TextEditor
ابتدا نیاز است بستهی نیوگت آنرا با اجرای دستور زیر، به پروژهی Blazor خود اضافه کرد:
dotnet add package Blazored.TextEditor
libman install quill --provider unpkg --destination wwwroot/lib/quill
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>BlazorServer.App</title> <base href="~/" /> <link href="lib/quill/dist/quill.snow.css" rel="stylesheet" /> <link href="lib/quill/dist/quill.bubble.css" rel="stylesheet" />
<script src="lib/quill/dist/quill.min.js"></script> <script src="_content/Blazored.TextEditor/quill-blot-formatter.min.js"></script> <script src="_content/Blazored.TextEditor/Blazored-BlazorQuill.js"></script> <script src="_framework/blazor.server.js"></script> </body>
و در آخر جهت سهولت کار با این کامپوننت میتوان فضای نام آنرا به فایل BlazorServer.App\_Imports.razor به صورت زیر اضافه کرد:
@using Blazored.TextEditor
استفاده از کامپوننت Blazored.TextEditor در کامپوننت HotelRoomUpsert.razor
میخواهیم در کامپوننت HotelRoomUpsert.razor مثال این سری، بجای کامپوننت InputTextArea مورد استفاده، از یک HTML Editor استفاده کنیم:
<div class="form-group"> <label>Details</label> @*<InputTextArea @bind-Value="HotelRoomModel.Details" class="form-control"></InputTextArea>*@ <BlazoredTextEditor @ref="@QuillHtml"> <ToolbarContent> <select class="ql-header"> <option selected=""></option> <option value="1"></option> <option value="2"></option> <option value="3"></option> <option value="4"></option> <option value="5"></option> </select> <span class="ql-formats"> <button class="ql-bold"></button> <button class="ql-italic"></button> <button class="ql-underline"></button> <button class="ql-strike"></button> </span> <span class="ql-formats"> <select class="ql-color"></select> <select class="ql-background"></select> </span> <span class="ql-formats"> <button class="ql-list" value="ordered"></button> <button class="ql-list" value="bullet"></button> </span> <span class="ql-formats"> <button class="ql-link"></button> </span> </ToolbarContent> <EditorContent> </EditorContent> </BlazoredTextEditor> </div>
- همانطور که ملاحظه میکنید، این تعریف به همراه یک ارجاع به وهلهای از آن نیز هست:
<BlazoredTextEditor @ref="@QuillHtml">
@code { private BlazoredTextEditor QuillHtml;
برای تغییر اندازه و مقدار placeholder پیشفرض آن، میتوان به صورت زیر عمل کرد:
<div class="form-group pb-4" style="height:250px;"> <label>Details</label> <BlazoredTextEditor @ref="@QuillHtml" Placeholder="Please enter the room's detail">
تنظیم و دریافت متن نمایشی HTML Editor
مطابق مستندات این کامپوننت، روش تنظیم متن نمایشی آن، به کمک متد LoadHTMLContent است. به همین جهت متد زیر را به کدهای کامپوننت جاری اضافه میکنیم:
private async Task SetHTMLAsync() { if(!string.IsNullOrEmpty(HotelRoomModel.Details)) { await QuillHtml.LoadHTMLContent(HotelRoomModel.Details); } }
protected override async Task OnInitializedAsync() { if (Id.HasValue) { // Update Mode Title = "Update"; HotelRoomModel = await HotelRoomService.GetHotelRoomAsync(Id.Value); await SetHTMLAsync(); } // ... }
private async Task HandleHotelRoomUpsert() { // ... // Create Mode HotelRoomModel.Details = await QuillHtml.GetHTML(); // ... }
مشکل! ادیتور در زمان ویرایش یک رکورد، اطلاعات پیشین را نمایش نمیدهد!
پس از اعمال تغییرات فوق، برنامه را اجرا میکنیم. سپس یک اتاق جدید را اضافه کرده و در لیست نمایش اتاقها، گزینهی ویرایش آنرا انتخاب میکنیم. در این حالت هرچند کار مقدار دهی HotelRoomModel.Details در زمان ثبت اطلاعات انجام شده، اما ... در زمان ویرایش چیزی نمایش داده نمیشود و تغییراتی را که به متد رویدادگردان OnInitializedAsync اضافه کردهایم، عمل نمیکنند.
در این مورد در قسمت بررسی چرخهی حیات کامپوننتها توضیحاتی ابتدایی ارائه شد:
«رویدادهای OnAfterRender و OnAfterRenderAsync
پس از هر بار رندر کامپوننت، این متدها فراخوانی میشوند. در این مرحله کار بارگذاری کامپوننت، دریافت اطلاعات و نمایش آنها به پایان رسیدهاست. یکی از کاربردهای آن، آغاز کامپوننتهای جاوا اسکریپتی است که برای کار، نیاز به DOM را دارند؛ مانند نمایش یک modal بوت استرپی.»
بنابراین در این حالت خاص که ادیتور جاوا اسکریپتی مورد استفاده، پس از رندر کامل UI نمایش داده میشود، قرار دادن متد SetHTML در روال رویدادگردان OnInitializedAsync کار نخواهد کرد و باید آنرا به روال رویدادگردان OnAfterRender انتقال دهیم:
protected override async Task OnAfterRenderAsync(bool firstRender) { await SetHTMLAsync(); }
private async Task SetHTMLAsync() { if(!string.IsNullOrEmpty(HotelRoomModel.Details)) { await QuillHtml.LoadHTMLContent(HotelRoomModel.Details); StateHasChanged(); } }
مشکل! اگر در این حالت سعی کنیم متنی را در ادیتور وارد کنیم، میسر نیست و همچنین CPU Usage سیستم به 100 درصد رسیدهاست!
علت اینجا است که فراخوانی StateHasChanged، هر چند سبب رندر مجدد UI میشود، اما چون در پایان کار رندر قرار داریم، یک حلقهی بینهایت را سبب خواهد شد. به همین جهت باید در متد OnAfterRenderAsync، بر اساس پارامتر firstRender، از رندرهای بعدی جلوگیری کرد:
protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) { return; } while (true) { try { await SetHTMLAsync(); break; } catch { await Task.Delay(100); // Quill needs some time to load } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-20.zip