یک نکتهی تکمیلی: امکان اجرای سادهتر اعمال async پس از رخداد onchange در Blazor 7x
پیشنیاز: برای اجرای نکات زیر، نیاز به حداقل NET SDK 7.0.101. است و اگر از ویژوال استودیو استفاده میکنید، باید شماره نگارش آن حداقل 17.4.3 باشد؛ در غیراینصورت با خطای «'cannot convert from 'method group' to 'Action» مواجه خواهید شد.
همانطور که در مطلب فوق هم مشاهده کردید، در جهت انجام اعتبارسنجی از راه دور async پس از ورود اطلاعات، تنها رخدادی که در اینجا در اختیار ما قرار میگیرد، رخداد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخدادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم تا بتوانیم برای مثال به کمک رویداد OnFieldChanged آن، این عملیات async را انجام دهیم. در دات نت 7.0.1، این وضعیت با معرفی modifier جدیدی به نام bind:after@ تغییر کردهاست که در ادامه توضیحات آنرا ملاحظه خواهید کرد.
تعاریف زیر را جهت پیاده سازی یک انقیاد دوطرفه (two-way data-binding) درنظر بگیرید:
که در اولی با درج bind@ بر روی یک المان استاندارد HTML و در دومی با ذکر bind-Value@ میسر شدهاست. در این حالت هر تغییری در مقدار کنترل قرار گرفتهی بر روی صفحه، به خاصیت متصل به آن منعکس میشود (با پیاده سازی خودکار یک رویدادگردان onchange توسط Blazor در پشت صحنه) و برعکس.
مشکل! اگر در اینجا نیاز باشد تا در حین ورود اطلاعات، کدی نیز اجرا شود چه باید کرد؟
متاسفانه در این حالت نمیتوانیم رویدادگردان onchange را به صورت دستی، به تعاریف فوق اضافه کنیم و اگر چنین کاری را انجام دهیم، با خطای زیر مواجه خواهیم شد:
عنوان میکند که چون ما خودمان onchange را راسا پیاده سازی کردهایم، شما دیگر نمیتوانید اینکار را مجددا انجام دهید!
راه حلهای ممکن انجام اعمال async پس از بروز تغییرات تا پیش از دات نت 7
الف) username متصل را تبدیل به یک خاصیت get و set دار کرده و اکنون در قسمت set آن میتوان عملیات synchronous ای را انجام داد که متاسفانه در این حالت، امکان انجام اعمال async میسر نیست.
ب) چون میخواهیم عملیات async ای را پس از تغییرات انجام دهیم، باید از انقیاد دوطرفه صرفنظر کنیم و مدیریت رویداد onchange را خودمان بهدست بگیریم؛ برای نمونه در مثال زیر میتوان با پیاده سازی async متد CheckUsername به هدف خود رسید؛ اما همانطور که مشاهده میکنید، این عملیات اکنون one-way binding است:
ج) اگر از EditForm و کنترلهای آن استفاده میکنیم، میتوان همانند مثال مطلب جاری از رویداد OnFieldChanged استفاده کرد یا راه دیگر آن شکستن bind-Value@ به اجزای تشکیل دهندهی آن است که سه جزء Value ،ValueExpression و ValueChanged را تشکیل میدهد و اینبار میتوان رویداد ValueChanged آنرا دستی پیاده سازی کرد:
راه حل جدید انجام اعمال async پس از بروز تغییرات در دات نت 7
Blazor در دات نت 7، به همراه یک bind:after modifier@ است که امکان اجرای متدی را (چه همزمان یا غیرهمزمان) پس از بروز تغییرات، میسر میکند و مزیت آن عدم نیاز به بازنویسی متد onchange و از دست دادن انقیاد دوطرفه است:
همانطور که مشاهده میکنید هنوز در این حالت bind@ وجود دارد (یعنی two-way data-binding هنوز هم برقرار است) و توسط bind:after@، متدی را که قرار است پس از تغییرات اجرا شود، مشخص کردهایم.
این modifier را حتی میتوان به کنترلهای EditForm نیز اعمال کرد؛ بدون اینکه نیازی به استفاده از راهحلهای پیشین (حالت ج عنوان شده) باشد:
در اینجا نیز هنوز از مزایای two-way data-binding برخورداریم و همچنین میتوانیم پس از تغییری، یک متد sync و یا async را فراخوانی کنیم. برای نمونه پیاده سازی اعتبارسنجی از راه دور مطلب جاری، اینبار به صورت زیر ساده میشود:
پیشنیاز: برای اجرای نکات زیر، نیاز به حداقل NET SDK 7.0.101. است و اگر از ویژوال استودیو استفاده میکنید، باید شماره نگارش آن حداقل 17.4.3 باشد؛ در غیراینصورت با خطای «'cannot convert from 'method group' to 'Action» مواجه خواهید شد.
همانطور که در مطلب فوق هم مشاهده کردید، در جهت انجام اعتبارسنجی از راه دور async پس از ورود اطلاعات، تنها رخدادی که در اینجا در اختیار ما قرار میگیرد، رخداد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخدادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم تا بتوانیم برای مثال به کمک رویداد OnFieldChanged آن، این عملیات async را انجام دهیم. در دات نت 7.0.1، این وضعیت با معرفی modifier جدیدی به نام bind:after@ تغییر کردهاست که در ادامه توضیحات آنرا ملاحظه خواهید کرد.
تعاریف زیر را جهت پیاده سازی یک انقیاد دوطرفه (two-way data-binding) درنظر بگیرید:
<input @bind="username" /> <InputText @bind-Value="Model.Name" />
مشکل! اگر در اینجا نیاز باشد تا در حین ورود اطلاعات، کدی نیز اجرا شود چه باید کرد؟
متاسفانه در این حالت نمیتوانیم رویدادگردان onchange را به صورت دستی، به تعاریف فوق اضافه کنیم و اگر چنین کاری را انجام دهیم، با خطای زیر مواجه خواهیم شد:
RZ10008 The attribute 'onchange' is used two or more times for this element. Attributes must be unique (case-insensitive). The attribute 'onchange' is used by the '@bind' directive attribute.
راه حلهای ممکن انجام اعمال async پس از بروز تغییرات تا پیش از دات نت 7
الف) username متصل را تبدیل به یک خاصیت get و set دار کرده و اکنون در قسمت set آن میتوان عملیات synchronous ای را انجام داد که متاسفانه در این حالت، امکان انجام اعمال async میسر نیست.
ب) چون میخواهیم عملیات async ای را پس از تغییرات انجام دهیم، باید از انقیاد دوطرفه صرفنظر کنیم و مدیریت رویداد onchange را خودمان بهدست بگیریم؛ برای نمونه در مثال زیر میتوان با پیاده سازی async متد CheckUsername به هدف خود رسید؛ اما همانطور که مشاهده میکنید، این عملیات اکنون one-way binding است:
<input value="@username" @onchange="CheckUsername" />
<InputText Value="@Model.Name" ValueExpression="()=>Model.Name" ValueChanged="(string s)=>CheckUsername(s)" /> <ValidationMessage For="() => Model.Name" />
راه حل جدید انجام اعمال async پس از بروز تغییرات در دات نت 7
Blazor در دات نت 7، به همراه یک bind:after modifier@ است که امکان اجرای متدی را (چه همزمان یا غیرهمزمان) پس از بروز تغییرات، میسر میکند و مزیت آن عدم نیاز به بازنویسی متد onchange و از دست دادن انقیاد دوطرفه است:
<input @bind="username" @bind:after="CheckUsername" />
این modifier را حتی میتوان به کنترلهای EditForm نیز اعمال کرد؛ بدون اینکه نیازی به استفاده از راهحلهای پیشین (حالت ج عنوان شده) باشد:
<InputText @bind-Value="Model.Name" @bind-Value:after="CheckUsername" /> <ValidationMessage For="() => Model.Name" />
async Task CheckUsername() { if (!string.IsNullOrWhiteSpace(Model.Name)) { _messageStore?.Clear(EditContext.Field(nameof(UserDto.Name))); var response = await HttpClient.PostAsJsonAsync( UserValidationUrl, new UserDto { Name = Model.Name }); var responseContent = await response.Content.ReadAsStringAsync(); if (string.Equals(responseContent, "false", StringComparison.OrdinalIgnoreCase)) { _messageStore?.Add(EditContext.Field(nameof(UserDto.Name)), $"`{Model.Name}` is in use. Please choose another name."); } EditContext.NotifyValidationStateChanged(); } }