طرح مشکل! نیاز به دریافت انواع و اقسام مقادیر یک جنس (مانند اعداد و یا تاریخ) در کامپوننتهای Blazor
فرض کنید میخواهید عددی را در کامپوننتی، دریافت کنید. میتوان اینکار را با تعریف یک پارامتر عمومی به صورت زیر انجام داد:
و ... مشکل از همینجا شروع میشود! خوب، برای نوعهای double ، decimal ، float ، long و غیره چه باید کرد؟ آیا باید به ازای هر کدام، یک پارامتر مخصوص را تعریف کنیم؟ و یا اگر خواستیم زمان و تاریخ را از کاربر دریافت کنیم، آیا باید او را مجبور به ارائهی فقط DateTime کنیم و یا ... شاید بهتر باشد به او بگوییم اگر «رشتهای» را در اختیار ما قرار دهید، ما یکبار دیگر خودمان کار تبدیل آنرا به نوع تاریخ انجام خواهیم داد!
برای حل این نوع مشکلات، میتوان در Blazor پارامترها را جنریک تعریف کرد. برای نمونه اگر به طراحی یک سری کامپوننتهای پایهای Blazor دقت کنیم، امکان دریافت مستقیم انواع و اقسام اعداد و یا انواع و اقسام نوعهای مرتبط با زمان را دارند؛ بدون اینکه این اطلاعات را به صورت رشتهای دریافت کنند و یا به ازای هر نوع عددی، یک پارامتر جداگانه را تعریف و یا اینکه استفاده کننده را محدود به ورود تنها یک نوع عددی و یا زمانی خاص کرده باشند.
نحوهی جنریک تعریف کردن یک کامپوننت در Blazor
Blazor فایلهای razor. را در حین پروسهی build، به cs. تبدیل میکند و از آنجائیکه فایلهای razor. الزامی به تعریف نام کلاس مرتبط را ندارند و این نام به صورت خودکار دقیقا از نام خود فایل، دریافت میشود، نیاز است راهی را یافت تا بتوان کلاس مرتبط را به صورت Generic تعریف کرد. برای این منظور، دایرکتیو ویژهای به نام typeparam@ پیش بینی شدهاست که توسط آن میتوان یک یا چند نوع/پارامتر جنریک جدا شدهی توسط کاما را تعریف کنیم (تعریف شدهی در فایل فرضی MyGenericComponent.razor):
در این مثال کامپوننتی را مشاهده میکنید که مقدار دریافتی خود را به صورت جنریک از نوع T دریافت میکند. این T باید توسط دایرکتیو typeparam@ دقیقا قید شود تا در حین ساخت خودکار کلاس معادل این فایل، مورد استفاده قرار گیرد.
نحوهی جنریک تعریف کردن کامپوننتهای دارای code-behind
اگر قسمت code@ فایلهای razor. را به فایلهای code-benind منتقل میکنید و داخل فایل razor.، کد #C نمینویسید، روش جنریک تعریف کردن یک کامپوننت، به صورت زیر خواهد بود (برای مثال دو فایل MyGenericComponentWithCodeBehind.razor و MyGenericComponentWithCodeBehind.razor.cs تعریف شدهاند):
در اینجا ذکر دایرکتیو typeparam@ در فایل razor. متناظر هم باید صورت گیرد:
یعنی هر دو کلاس نهایی حاصل از این فایلها که به صورت partial تعریف شدهاند (و در آخر یکی میشوند)، باید به صورت جنریک تعریف شوند.
روش مقید کردن پارامترهای جنریک در کامپوننتها
از زمان دات نت 6 به بعد، امکان محدود کردن بازهی نوعهای قابل پذیرش T نیز میسر شدهاست:
روش مشخص کردن صریح نوع پارامترهای جنریک، در حین استفادهی از آنها
عموما نیازی به مشخص کردن نوع پارامترهای جنریک، در حین استفادهی از آنها نیست و کامپایلر بر اساس نوع مقدار ورودی، سعی خواهد کرد این نوع را به صورت خودکار تشخیص دهد؛ اما اگر به هر دلیلی چنین امری ممکن نبود و خطایی را دریافت کردید، میتوان نوع پارامتر جنریک را به صورت زیر مشخص کرد:
در اینجا نام پارامتر جنریک، ذکر شده و سپس نوعی به آن انتساب داده میشود.
روش پردازش پارامترهای جنریک در کامپوننتها
تا اینجا امکان پذیرش نوعهای جنریک را توسط استفاده کننده میسر کردیم؛ اما قسمت دیگر آن، نحوهی کار با نوع T، درون یک کامپوننت است:
برای مثال فرض کنید کامپوننت جنریک ما قرار است انواع و اقسام اعداد را بپذیرد و سپس بر اساس Format مشخص شده، آنها را نمایش دهد. در اینجا جهت واکنش نشان دادن به تغییرات Value، میتوان از روال رخداد گردان OnParametersSet استفاده کرد. سپس در متد ConvertNumberToText، بر اساس هر نوع میسر عددی، باید منطق ویژهای را تهیه کرد که نمونهای از آنرا در اینجا مشاهده میکنید.
و در آخر نمایش نهایی آن هم در فایل razor. متناظر، به این صورت خواهد بود:
فرض کنید میخواهید عددی را در کامپوننتی، دریافت کنید. میتوان اینکار را با تعریف یک پارامتر عمومی به صورت زیر انجام داد:
[Parameter] public int Value { get; set; }
برای حل این نوع مشکلات، میتوان در Blazor پارامترها را جنریک تعریف کرد. برای نمونه اگر به طراحی یک سری کامپوننتهای پایهای Blazor دقت کنیم، امکان دریافت مستقیم انواع و اقسام اعداد و یا انواع و اقسام نوعهای مرتبط با زمان را دارند؛ بدون اینکه این اطلاعات را به صورت رشتهای دریافت کنند و یا به ازای هر نوع عددی، یک پارامتر جداگانه را تعریف و یا اینکه استفاده کننده را محدود به ورود تنها یک نوع عددی و یا زمانی خاص کرده باشند.
نحوهی جنریک تعریف کردن یک کامپوننت در Blazor
Blazor فایلهای razor. را در حین پروسهی build، به cs. تبدیل میکند و از آنجائیکه فایلهای razor. الزامی به تعریف نام کلاس مرتبط را ندارند و این نام به صورت خودکار دقیقا از نام خود فایل، دریافت میشود، نیاز است راهی را یافت تا بتوان کلاس مرتبط را به صورت Generic تعریف کرد. برای این منظور، دایرکتیو ویژهای به نام typeparam@ پیش بینی شدهاست که توسط آن میتوان یک یا چند نوع/پارامتر جنریک جدا شدهی توسط کاما را تعریف کنیم (تعریف شدهی در فایل فرضی MyGenericComponent.razor):
@typeparam T @code { [Parameter] public T Value { get; set; } }
نحوهی جنریک تعریف کردن کامپوننتهای دارای code-behind
اگر قسمت code@ فایلهای razor. را به فایلهای code-benind منتقل میکنید و داخل فایل razor.، کد #C نمینویسید، روش جنریک تعریف کردن یک کامپوننت، به صورت زیر خواهد بود (برای مثال دو فایل MyGenericComponentWithCodeBehind.razor و MyGenericComponentWithCodeBehind.razor.cs تعریف شدهاند):
using Microsoft.AspNetCore.Components; namespace BlazorGenericComponents.Pages; public partial class MyGenericComponentWithCodeBehind<T> { [Parameter] public T Value { get; set; } }
@typeparam T
روش مقید کردن پارامترهای جنریک در کامپوننتها
از زمان دات نت 6 به بعد، امکان محدود کردن بازهی نوعهای قابل پذیرش T نیز میسر شدهاست:
@typeparam T where T : IMyInterface
روش مشخص کردن صریح نوع پارامترهای جنریک، در حین استفادهی از آنها
عموما نیازی به مشخص کردن نوع پارامترهای جنریک، در حین استفادهی از آنها نیست و کامپایلر بر اساس نوع مقدار ورودی، سعی خواهد کرد این نوع را به صورت خودکار تشخیص دهد؛ اما اگر به هر دلیلی چنین امری ممکن نبود و خطایی را دریافت کردید، میتوان نوع پارامتر جنریک را به صورت زیر مشخص کرد:
<MyGenericComponent Value="10" T="int"/>
روش پردازش پارامترهای جنریک در کامپوننتها
تا اینجا امکان پذیرش نوعهای جنریک را توسط استفاده کننده میسر کردیم؛ اما قسمت دیگر آن، نحوهی کار با نوع T، درون یک کامپوننت است:
using Microsoft.AspNetCore.Components; namespace BlazorGenericComponents.Pages; public partial class MyGenericComponentWithCodeBehind<T> { private string FormattedValue { set; get; } [Parameter] public T Value { get; set; } [Parameter] public string Format { get; set; } protected override void OnParametersSet() { FormattedValue = ConvertNumberToText(); } private string ConvertNumberToText() { return Value switch { short shortValue => shortValue.ToString(Format), ushort ushortValue => ushortValue.ToString(Format), int intValue => intValue.ToString(Format), uint uintValue => uintValue.ToString(Format), byte byteValue => byteValue.ToString(Format), sbyte sbyteValue => sbyteValue.ToString(Format), decimal decimalValue => decimalValue.ToString(Format), double doubleValue => doubleValue.ToString(Format), float floatValue => floatValue.ToString(Format), long longValue => longValue.ToString(Format), ulong ulongValue => ulongValue.ToString(Format), _ => string.Empty }; } }
و در آخر نمایش نهایی آن هم در فایل razor. متناظر، به این صورت خواهد بود:
@typeparam T @FormattedValue
اضافه شدن JavaScript initializers به Blazor 6x
در اینجا میتوان فایل ویژهای به نام NAME.lib.module.js را به پوشهی wwwroot پروژه اضافه کرد که name آن، همان نام اسمبلی، کتابخانه و در اصل package identifier پروژهاست؛ با این محتوا:
export function beforeStart(options, extensions) { console.log("beforeStart"); } export function afterStarted(blazor) { console.log("afterStarted"); }
- سفارشی سازی نحوهی بارگذاری یک برنامهی Blazor
- اجرای کدهای سفارشی، پیش و پس از بارگذاری برنامه
- امکان تنظیم ویژگیهای Blazor
یک مثال: بارگذاری یک اسکریپت پس از کامل شدن بارگذاری Blazor
<body> ... <script src="_framework/blazor.{webassembly|server}.js" autostart="false"></script> <script> Blazor.start().then(function () { var customScript = document.createElement('script'); customScript.setAttribute('src', 'scripts.js'); document.head.appendChild(customScript); }); </script> </body>
اشتراکها
نگاهی به ویژگی های جدید 6 #C
The C# language itself has changed little in version 6, the main importance of the release being the introduction of the Roslyn .NET Compiler Platform. However the New features and improvements that have been made to C# are welcome because they are aimed at aiding productivity. Paulo Morgado explains what they are, and how to use them.
اشتراکها
مقایسهای بین Xamarin.Forms و MAUI
Xamarin.Forms vs MAUI
| Xamarin.Forms | MAUI |
---|---|---|
Platforms | | |
Android | API 19+ | API 21+ |
iOS | 9-14 | 10+ |
Linux | Community | Community |
macOS | Community | Microsoft |
Tizen | Samsung | Samsung |
Windows | UWP Microsoft WPF Community | Microsoft |
Features | | |
Renderers | Tightly coupled to BindableObject | Loosely coupled, no Core dependencies |
App Models | MVVM, RxUI | MVVM, RxUI, MVU, Blazor |
Single Project | No | Yes |
Multi-targeting | No | Yes |
Multi-window | No | Yes |
Misc | | |
.NET | Xamarin.iOS, Xamarin.Android, Mono, .NET Framework, ... | .NET 6+ |
Acquisition | NuGet & Visual Studio Installer | dotnet |
Project System | Franken-proj | SDK Style |
dotnet CLI | No | Yes |
Tools | | |
Visual Studio 2019 | Yes | Yes |
Visual Studio 2019 for Mac | Yes | Yes |
Visual Studio Code | No | Yes |
نظرات اشتراکها
فراخوانی بیشتر از یک بار "window.onload"
- اگر از MVC استفاده میکنید، در فایل layout
به کمک RenderSection کلیه اسکریپتهای inline فقط در همین مکان فراخوانی آن قرار میگیرند. به این ترتیب در Viewها فقط کافی است یک section به نام JavaScript اضافه کرد تا در این مکان درج شود.
- در ASP.NET Web forms هم میشود این نظم رو پدید آورد. از ContentPlaceHolder استفاده کنید. یکی در master page تعریف شود دیگری در وب فرم به ارث رسیده از آن برای قرار دادن اسکریپتهای خاص همان صفحه.
حالا وب فرم شما فقط کافی است اسکریپتهای خاص خودش را با ID فوق درج کنه
... اینجا اسکریپتهای عمومی قرار گیرند قبل از پایان فایل @RenderSection("JavaScript", false) </body> </html>
- در ASP.NET Web forms هم میشود این نظم رو پدید آورد. از ContentPlaceHolder استفاده کنید. یکی در master page تعریف شود دیگری در وب فرم به ارث رسیده از آن برای قرار دادن اسکریپتهای خاص همان صفحه.
.... سایر قسمتهای فایل مستر پیج <asp:ContentPlaceHolder id='PageScriptPlaceHolder' runat='server'> </asp:ContentPlaceHolder> </body>
<asp:Content ID='ScriptIncludes' runat='server' ContentPlaceHolderID='PageScriptPlaceHolder'> اسکریپتهای صفحه در اینجا </asp:Content>
نکته تکمیلی: نحوه بایند کردن دو Section مختلف در asppsettings.json به یک کلاس
مثلا دو Object متفاوت در appsettings.json داریم و میخواهیم آن را به یک کلاس بایند کنیم:
"SiteSettingsA": { "DefaultUserPicture": "http://site.com/users/defaultavatar.png" }, "SiteSettingsB": { "DefaultUserPicture": "http://site.com/users/defaultavatar2.png" }
public class SiteSettings { public string DefaultUserPicture { get; set; } }
services.Configure<SiteSettings>("FirstSettings", Configuration.GetSection("SiteSettingsA")); services.Configure<SiteSettings>("SecondSettings", Configuration.GetSection("SiteSettingsB"));
private readonly SiteSettings _firstSiteSettings; private readonly SiteSettings _secondSiteSettings; public HomeController(IOptionsSnapshot<SiteSettings> siteSettings) { _firstSiteSettings = siteSettings.Get("FirstSettings"); _secondSiteSettings = siteSettings.Get("SecondSettings"); }
نظرات مطالب
نوشتن Middleware سفارشی در ASP.NET Core
یکی از روشهای مقابله با مشکل فوق استفاده از کلاس SemaphoreSlim می باشد که در NET Framework 4.0 معرفی شده و در فضای نام System.Threading در دسترس میباشد.
سپس به صورت زیر از آن استفاده کنید:
در این صورت تمامی درخواستهای به سمت سرور به ترتیب اجرا خواهند شد و دیگر مشکل فوق را نخواهیم داشت.
اگر اکشن متدهای شما به صورت async await ایجاد کرده اید بهتر هست ابتدا کلاس زیر را ایجاد نمایید:
using System; using System.Threading; using System.Threading.Tasks; namespace MyApp { public class AsyncLock : IDisposable { private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); public async Task<AsyncLock> LockAsync() { await _semaphoreSlim.WaitAsync(); return this; } public void Dispose() { _semaphoreSlim.Release(); } } }
private static readonly AsyncLock _mutex = new AsyncLock(); using(await _mutex.LockAsync()) { // Critical section... You can await here! }
راه حل توکاری ندارد؛ چون این فناوری سمت سرور است. حتی Razor هم یک فناوری سمت سرور است. بنابراین یا باید وقت بگذارید این روشهای قدیمی را به جدید ترجمه کنید:
و یا یک تامین کنندهی منابع عمومی اسکریپتها را تعریف کنید:
<script type="text/javascript"> if (!window.resourceProvider) { window.resourceProvider = { message1: '', message2: '' }; } </script>
@using Microsoft.AspNetCore.Mvc.Localization @inject IViewLocalizer Localizer @section Scripts { <script type="text/javascript"> resourceProvider.message1 = '@Localizer["About Title"]'; </script> }
<script type="text/javascript"> alert(resourceProvider.message1); </script>
نظرات مطالب
ASP.NET MVC #13
با سلام و تشکر فراوان
اعتبار سنجی سمت کلاینت در مثال 13 که فرمودید در سیستم من کار نکرد اما این مثال code project کار میکند.
ولی بعد از اینکه دوباره مثال را در mvc4 vs2012 باز نویسی کردم کار نکرد (دقیقا مانند مثال code project) پس از تغییر متداز (-) split به (/) split و تغییر
به (فقط فرمت تاریخ تغییر کرد)
و حذف
کار کرد.
چرا به این تغییرات نیاز بود؟ (در صورتی که مثال code project کار میکرد، آیا به دلیل تفاوت ورژن است)
این مثال با مثال شما چه تفاوتی دارد که مثال شما در سیستم من اجرا نشد؟
این هم فایل نهایی من بعد از تغییر که کار کرد: MvcApplication-JsValidation.zip
اعتبار سنجی سمت کلاینت در مثال 13 که فرمودید در سیستم من کار نکرد اما این مثال code project کار میکند.
ولی بعد از اینکه دوباره مثال را در mvc4 vs2012 باز نویسی کردم کار نکرد (دقیقا مانند مثال code project) پس از تغییر متداز (-) split به (/) split و تغییر
mcvrTwo.ValidationParameters.Add("param", DateTime.Now.ToString("dd/MM/yyyy"));
mcvrTwo.ValidationParameters.Add("param", DateTime.Now.ToString("dd-MM-yyyy"));
@section Scripts { @Scripts.Render("~/bundles/jqueryval") }
چرا به این تغییرات نیاز بود؟ (در صورتی که مثال code project کار میکرد، آیا به دلیل تفاوت ورژن است)
این مثال با مثال شما چه تفاوتی دارد که مثال شما در سیستم من اجرا نشد؟
این هم فایل نهایی من بعد از تغییر که کار کرد: MvcApplication-JsValidation.zip