با سلام و تشکر؛ من قسمت کلاینت مثال جاری را با Server side Blazor عوض کردم. متاسفانه در هنگامیکه کاربر به صفحه لاگین ارجاع داده میشود و لاگین میکند، برنامه به صفحه ای که کاربر در آن بوده باز نمیگردد و به جای آن صفحه Welcome to IdentityServer4 نمایش داده میشود. ظاهرا Blazor به صورت داخلی دارای چنین مکانیسمی نیست. اگر راهنمایی بفرمایید که چطور میتوانم کاربر را به صفحه ای که در آن بوده Redirect کنم ممنون میشوم.
نظرات نظرسنجیها
از کدام نوع اصلی Blazor بیشتر استفاده میکنید؟
به دلیل سرعت لود اولیه بالاتر Blazor Server اکثرا از این تکنولوژی استفاده میکنم. هر چه توجیه در رابطه با کندی سرعت لود Blazor WASM برای کاربر میآوریم که به این دلایل سرعت اولیه قدری کند است، بازهم خیلیها قبول نمیکنند. البته مایکروسافت قول مساعد داده است که در دات نت ۸ با ترکیب مکانیزمهای این دو، این مشکل را نیز حل خواهد کرد.
نظرات اشتراکها
لیستی از کامپوننتهای رایگان مخصوص Blazor
- لطفا از قسمت پرسشها استفاده کنید.
- از Blazor SSR استفاده کنید؛ مثل همین سایت جاری. Blazor SSR، در حقیقت جایگزین مدرن ASP.NET Core Razor Pages است. از لحاظ SEO هم فوق العادهاست. این خروجی یک ماه قبل آن است:
پ.ن.
اگر علاقمندید بدانید که کاربران هدایت شدهی از طریق موتورهای جستجو، بهدنبال چه موضوعاتی هستند، صفحهی ارجاعات خارجی سایت را دنبال کنید.
طرح مشکل! نیاز به دریافت انواع و اقسام مقادیر یک جنس (مانند اعداد و یا تاریخ) در کامپوننتهای 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
Reactive extensions یا به صورت خلاصه Rx ،کتابخانهی سورس باز تهیه شدهای توسط مایکروسافت است که اگر بخواهیم آنرا به سادهترین شکل ممکن تعریف کنیم، معنای Linq to events را میدهد و امکان مدیریت تعاملهای پیچیدهی async را به صورت declaratively فراهم میکند. هدف آن بسط فضای نام System.Linq و تبدیل نتایج یک کوئری LINQ به یک مجموعهی Observable است؛ به همراه مدیریت مسایل همزمانی آن.
این افزونه جزو موفقترین کتابخانههای دات نتی مایکروسافت در سالهای اخیر به شما میرود؛ تا حدی که معادلهای بسیاری از آن برای زبانهای دیگر مانند Java، JavaScript، Python، CPP و غیره نیز تهیه شدهاند.
استفاده از Rx به همراه یک کوئری LINQ
یک برنامهی کنسول جدید را ایجاد کنید. سپس برای نصب کتابخانهی Rx، دستور ذیل را در کنسول پاورشل نیوگت اجرا نمائید:
نصب آن از طریق نیوگت، به صورت خودکار کلیه وابستگیهای مرتبط با آنرا نیز به پروژهی جاری اضافه میکند:
سپس متد Main این برنامه را به نحو ذیل تغییر دهید:
در اینجا یک سری عملیات متداول را مشاهده میکنید. بازهای از اعداد توسط متد Enumerable.Range ایجاد شده و سپس به کمک یک حلقه، تمام آیتمهای آن نمایش داده میشوند. همچنین در پایان کار نیز یک متد دیگر فراخوانی شدهاست.
اکنون اگر بخواهیم همین عملیات را توسط Rx انجام دهیم، به شکل زیر خواهد بود:
ابتدا نیاز است تا کوئری متداول LINQ را تبدیل به نمونهی Observable آن کرد. اینکار را توسط متد الحاقی ToObservable که در فضای نام System.Reactive.Linq تعریف شدهاست، انجام میدهیم. به این ترتیب، هر زمانیکه که عددی به query اضافه میشود، با استفاده از متد Subscribe میتوان تغییرات آنرا تحت کنترل قرار داد. برای مثال در اینجا هربار که عددی در بازهی 1 تا 5 تولید میشود، یکبار پارامتر onNext اجرا خواهد شد. برای نمونه در مثال فوق، از نتیجهی آن برای نمایش مقدار دریافتی، استفاده شدهاست. سپس توسط پارامتر اختیاری onCompleted، در پایان کار، یک متد خاص را میتوان فراخوانی کرد. خروجی برنامه در این حالت نیز به صورت ذیل است:
البته اگر قصد خلاصه نویسی داشته باشیم، سطر آخر متد Main، با سطر ذیل یکی است:
در این مثال ساده صرفا یک Syntax دیگر را نسبت به حلقهی foreach متداول مشاهده کردیم که اندکی فشردهتر است. در هر دو حالت نیز عملیات انجام شده در تردجاری صورت گرفتهاند. اما قابلیتها و ارزشهای واقعی Rx زمانی آشکار خواهند شد که پردازش موازی و پردازش در تردهای دیگر را در آن فعال کنیم.
الگوی Observer
Rx پیاده سازی کنندهی الگوی طراحی شیءگرایی به نام Observer است. برای توضیح آن یک لامپ و سوئیچ برق را درنظر بگیرید. زمانیکه لامپ مشاهده میکند سوئیچ برق در حالت روشن قرار گرفتهاست، روشن خواهد شد و برعکس. در اینجا به سوئیچ، subject و به لامپ، observer گفته میشود. هر زمان که حالت سوئیچ تغییر میکند، از طریق یک callback، وضعیت خود را به observer اعلام خواهد کرد. علت استفاده از callbackها، ارائه راهحلهای عمومی است تا بتواند با انواع و اقسام اشیاء کار کند. به این ترتیب هر بار که شیء observer از نوع متفاوتی تعریف میشود (مثلا بجای لامپ یک خودرو قرار گیرد)، نیازی نخواهد بود تا subject را تغییر داد.
در Rx دو اینترفیس معادل observer و subject تعریف شدهاند. در اینجا اینترفیس IObserver معادل observer است و اینترفیس IObservable معادل subject میباشد:
کار متد Subscribe، اتصال به Observer است و برای این حالت نیاز به کلاسی دارد که اینترفیس IObserver را پیاده سازی کند.
در اینجا OnCompleted زمانی اجرا میشود که پردازش مجموعهای از اعداد int پایان یافته باشد. OnError در زمان وقوع استثنایی اجرا میشود و OnNext به ازای هر عدد موجود در مجموعهی در حال پردازش، یکبار اجرا میشود. البته نیازی به پیاده سازی صریح این اینترفیس نیست و توسط متد توکار Observer.Create میتوان به همین نتیجه رسید.
مجموعههای Observable کلید کار با Rx هستند. در مثال قبل ملاحظه کردیم که با استفاده از متد الحاقی ToObservable بر روی یک کوئری LINQ و یا هر نوع IEnumerable ایی، میتوان یک مجموعهی Observable را ایجاد کرد. خروجی کوئری حاصل از آن به صورت خودکار اینترفیس IObservable را پیاده سازی میکند که دارای یک متد به نام Subscribe است.
در متد Subscribe کاری که به صورت خودکار صورت خواهد گرفت، ایجاد یک حلقهی foreach بر روی مجموعهی مورد آنالیز و سپس فراخوانی متد OnNext کلاس پیاده سازی کنندهی IObserver به ازای هر آیتم موجود در مجموعه است (فراخوانی observer.OnNext). در پایان کار هم فقط return this در اینجا صورت خواهد گرفت. در حین پردازش حلقه، اگر خطایی رخ دهد، متد observer.OnError انجام میشود.
در مثال قبل،کوئری LINQ نوشته شده، خروجی از نوع IObservable ندارد. به کمک متد الحاقی ToObservable:
به صورت خودکار، IEnumerable حاصل از کوئری LINQ را تبدیل به یک IObservable کردهایم. به این ترتیب اکنون کوئری LINQ ما همانند سوئیچ برق عمل میکند و با تغییر آیتمهای موجود در آن، مشاهدهگرهایی که به آن متصل شدهاند (از طریق فراخوانی متد Subscribe)، امکان دریافت سیگنالهای تغییر وضعیت آنرا خواهند داشت.
البته استفاده از متد Subscribe به نحوی که در مثال قبل ذکر شد، خلاصه شدهی الگوی Observer است. اگر بخواهیم دقیقا مانند الگو عمل کنیم، چنین شکلی را خواهد داشت:
ابتدا توسط متد ToObservable یک IObservable (سوئیچ) را ایجاد کردهایم. سپس توسط کلاس Observer موجود در فضای نام System.Reactive، یک IObserver (لامپ) را ایجاد کردهایم. کار اتصال سوئیچ به لامپ در متد Subscribe انجام میشود. اکنون هر زمانیکه تغییری در وضعیت observableQuery حاصل شود، سیگنالی را به observer ارسال میکند. در اینجا callbacks کار مدیریت observer را انجام میدهند.
پردازش نتایج یک کوئری LINQ در تردی دیگر توسط Rx
برای اجرای نتایج متد Subscribe در یک ترد جدید، میتوان پارامتر scheduler متد ToObservable را مقدار دهی کرد:
خروجی این مثال به نحو ذیل است:
پیش از آغاز کار و در متد Main، ترد آی دی ثبت شده مساوی 1 است. سپس هربار که callback متد Subscribe فراخوانی شدهاست، ملاحظه میکنید که ترد آی دی آن مساوی عدد 3 است. به این معنا که کلیه نتایج در یک ترد مشخص دیگر پردازش شدهاند.
NewThreadScheduler.Default در فضای نام System.Reactive.Concurrency واقع شدهاست.
یک نکته
در نگارشهای آغازین Rx، مقدار scheduler را میشد معادل Scheduler.NewThread نیز قرار داد که در نگارشهای جدید منسوخ شده درنظر گرفته شده و به زودی حذف خواهد شد. معادلهای جدید آن اکنون NewThreadScheduler.Default، ThreadPoolScheduler.Default و امثال آن هستند.
مدیریت خاتمهی اعمال انجام شدهی در تردهای دیگر توسط Rx
یکی از مواردی که حین اجرای نتیجهی callbackهای پردازش شدهی در تردهای دیگر نیاز است بدانیم، زمان خاتمهی کار آنها است. برای نمونه در مثال قبل، نمایش Done پس از پایان تمام callbacks انجام شدهاست. فرض کنید، callback پایان عملیات را حذف کرده و متد finished را پس از فراخوانی متد observableQuery.Subscribe قرار دهیم:
اینبار اگر برنامه را اجرا کنیم به خروجی ذیل خواهیم رسید:
این خروجی بدین معنا است که متد observableQuery.Subscribeدر حین اجرا شدن در تردی دیگر، صبر نخواهد کرد تا عملیات مرتبط با آن خاتمه یابد و سپس سطر بعدی را اجرا کند. بنابراین برای حل این مشکل، تنها کافی است به آن اعلام کنیم که پس از پایان عملیات، onCompleted را اجرا کن.
مدیریت استثناهای رخ داده در حین پردازش مجموعههای واکنشگرا
متد Subscribe دارای چندین overload است. تا اینجا نمونهای که دارای پارامترهای onNext و onCompleted بودند را بررسی کردیم. اگر بخواهیم مدیریت استثناءها را نیز در اینجا اضافه کنیم، فقط کافی است از overload دیگر آن که دارای پارامتر onError است، استفاده نمائیم:
اگر callback پارامتر onError اجرا شود، دیگر به onCompleted نخواهیم رسید. همچنین دیگر onNext ایی نیز اجرا نخواهد شد.
مدیریت ترد اجرای نتایج حاصل از Rx در یک برنامهی دسکتاپ WPF یا WinForms
تا اینجا مشاهده کردیم که اجرای callbackهای observer در یک ترد دیگر، به سادگی تنظیم پارامتر scheduler متد ToObservable است. اما در برنامههای دسکتاپ برای به روز رسانی عناصر رابط کاربری، حتما باید در تردی قرار داشته باشیم که آن رابط کاربری در آن ایجاد شدهاست یا به عبارتی در ترد اصلی برنامه؛ در غیر اینصورت برنامه کرش خواهد کرد. مدیریت این مساله نیز در Rx بسیار سادهاست. ابتدا نیاز است بستهی Rx-WPF را نصب کرد:
سپس توسط متد ObserveOn میتوان مشخص کرد که نتیجهی عملیات باید بر روی کدام ترد اجرا شود:
روش دیگر آن استفاده از متد ObserveOnDispatcher میباشد:
بنابراین مشخص سازی پارامتر scheduler متد ToObservable، به معنای اجرای query آن در یک ترد دیگر و استفاده از متد ObserveOn، به معنای مشخص سازی ترد اجرای callbackهای مشاهدهگر است.
و یا اگر از WinForms استفاده میکنید، ابتدا بستهی Rx خاص آنرا نصب کنید:
و سپس ترد اجرای callbackها را SynchronizationContext.Current مشخص نمائید:
یک نکته
در Rx فرض میشود که کوئری شما زمانبر است و callbackهای مشاهدهگر سریع عمل میکنند. بنابراین هدف از callbackهای آن، پردازشهای سنگین نیست. جهت آزمایش این مساله، اینبار query ابتدایی برنامه را به شکل ذیل تغییر دهید که در آن بازگشت زمانبر یک سری داده شبیه سازی شدهاند.
سپس با استفاده از متد ToObservable، ترد دیگری را برای اجرای واقعی آن مشخص کنید تا در حین اجرای آن برنامه در حالت هنگ به نظر نرسد و سپس نمایش آنرا به کمک متد ObserveOn، بر روی ترد اصلی برنامه انجام دهید.
این افزونه جزو موفقترین کتابخانههای دات نتی مایکروسافت در سالهای اخیر به شما میرود؛ تا حدی که معادلهای بسیاری از آن برای زبانهای دیگر مانند Java، JavaScript، Python، CPP و غیره نیز تهیه شدهاند.
استفاده از Rx به همراه یک کوئری LINQ
یک برنامهی کنسول جدید را ایجاد کنید. سپس برای نصب کتابخانهی Rx، دستور ذیل را در کنسول پاورشل نیوگت اجرا نمائید:
PM> Install-Package Rx-Main
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Rx-Core" version="2.2.4" targetFramework="net45" /> <package id="Rx-Interfaces" version="2.2.4" targetFramework="net45" /> <package id="Rx-Linq" version="2.2.4" targetFramework="net45" /> <package id="Rx-Main" version="2.2.4" targetFramework="net45" /> <package id="Rx-PlatformServices" version="2.2.4" targetFramework="net45" /> </packages>
using System; using System.Linq; namespace Rx01 { class Program { static void Main(string[] args) { var query = Enumerable.Range(1, 5).Select(number => number); foreach (var number in query) { Console.WriteLine(number); } finished(); } private static void finished() { Console.WriteLine("Done!"); } } }
اکنون اگر بخواهیم همین عملیات را توسط Rx انجام دهیم، به شکل زیر خواهد بود:
using System; using System.Linq; using System.Reactive.Linq; namespace Rx01 { class Program { static void Main(string[] args) { var query = Enumerable.Range(1, 5).Select(number => number); var observableQuery = query.ToObservable(); observableQuery.Subscribe(onNext: number => Console.WriteLine(number), onCompleted: () => finished()); } private static void finished() { Console.WriteLine("Done!"); } } }
1 2 3 4 5 Done!
observableQuery.Subscribe(Console.WriteLine, finished);
در این مثال ساده صرفا یک Syntax دیگر را نسبت به حلقهی foreach متداول مشاهده کردیم که اندکی فشردهتر است. در هر دو حالت نیز عملیات انجام شده در تردجاری صورت گرفتهاند. اما قابلیتها و ارزشهای واقعی Rx زمانی آشکار خواهند شد که پردازش موازی و پردازش در تردهای دیگر را در آن فعال کنیم.
الگوی Observer
Rx پیاده سازی کنندهی الگوی طراحی شیءگرایی به نام Observer است. برای توضیح آن یک لامپ و سوئیچ برق را درنظر بگیرید. زمانیکه لامپ مشاهده میکند سوئیچ برق در حالت روشن قرار گرفتهاست، روشن خواهد شد و برعکس. در اینجا به سوئیچ، subject و به لامپ، observer گفته میشود. هر زمان که حالت سوئیچ تغییر میکند، از طریق یک callback، وضعیت خود را به observer اعلام خواهد کرد. علت استفاده از callbackها، ارائه راهحلهای عمومی است تا بتواند با انواع و اقسام اشیاء کار کند. به این ترتیب هر بار که شیء observer از نوع متفاوتی تعریف میشود (مثلا بجای لامپ یک خودرو قرار گیرد)، نیازی نخواهد بود تا subject را تغییر داد.
در Rx دو اینترفیس معادل observer و subject تعریف شدهاند. در اینجا اینترفیس IObserver معادل observer است و اینترفیس IObservable معادل subject میباشد:
class Subject : IObservable<int> { public IDisposable Subscribe(IObserver<int> observer) { } }
class Observer : IObserver<int> { public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(int value) { } }
مجموعههای Observable کلید کار با Rx هستند. در مثال قبل ملاحظه کردیم که با استفاده از متد الحاقی ToObservable بر روی یک کوئری LINQ و یا هر نوع IEnumerable ایی، میتوان یک مجموعهی Observable را ایجاد کرد. خروجی کوئری حاصل از آن به صورت خودکار اینترفیس IObservable را پیاده سازی میکند که دارای یک متد به نام Subscribe است.
در متد Subscribe کاری که به صورت خودکار صورت خواهد گرفت، ایجاد یک حلقهی foreach بر روی مجموعهی مورد آنالیز و سپس فراخوانی متد OnNext کلاس پیاده سازی کنندهی IObserver به ازای هر آیتم موجود در مجموعه است (فراخوانی observer.OnNext). در پایان کار هم فقط return this در اینجا صورت خواهد گرفت. در حین پردازش حلقه، اگر خطایی رخ دهد، متد observer.OnError انجام میشود.
در مثال قبل،کوئری LINQ نوشته شده، خروجی از نوع IObservable ندارد. به کمک متد الحاقی ToObservable:
public static System.IObservable<TSource> ToObservable<TSource>( this System.Collections.Generic.IEnumerable<TSource> source, System.Reactive.Concurrency.IScheduler scheduler)
البته استفاده از متد Subscribe به نحوی که در مثال قبل ذکر شد، خلاصه شدهی الگوی Observer است. اگر بخواهیم دقیقا مانند الگو عمل کنیم، چنین شکلی را خواهد داشت:
var query = Enumerable.Range(1, 5).Select(number => number); var observableQuery = query.ToObservable(); var observer = Observer.Create<int>(onNext: number => Console.WriteLine(number)); observableQuery.Subscribe(observer);
پردازش نتایج یک کوئری LINQ در تردی دیگر توسط Rx
برای اجرای نتایج متد Subscribe در یک ترد جدید، میتوان پارامتر scheduler متد ToObservable را مقدار دهی کرد:
using System; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Threading; namespace Rx01 { class Program { static void Main(string[] args) { Console.WriteLine("Thread-Id: {0}", Thread.CurrentThread.ManagedThreadId); var query = Enumerable.Range(1, 5).Select(number => number); var observableQuery = query.ToObservable(scheduler: NewThreadScheduler.Default); observableQuery.Subscribe(onNext: number => { Console.WriteLine("number: {0}, on Thread-id: {1}", number, Thread.CurrentThread.ManagedThreadId); }, onCompleted: () => finished()); } private static void finished() { Console.WriteLine("Done!"); } } }
Thread-Id: 1 number: 1, on Thread-id: 3 number: 2, on Thread-id: 3 number: 3, on Thread-id: 3 number: 4, on Thread-id: 3 number: 5, on Thread-id: 3 Done!
NewThreadScheduler.Default در فضای نام System.Reactive.Concurrency واقع شدهاست.
یک نکته
در نگارشهای آغازین Rx، مقدار scheduler را میشد معادل Scheduler.NewThread نیز قرار داد که در نگارشهای جدید منسوخ شده درنظر گرفته شده و به زودی حذف خواهد شد. معادلهای جدید آن اکنون NewThreadScheduler.Default، ThreadPoolScheduler.Default و امثال آن هستند.
مدیریت خاتمهی اعمال انجام شدهی در تردهای دیگر توسط Rx
یکی از مواردی که حین اجرای نتیجهی callbackهای پردازش شدهی در تردهای دیگر نیاز است بدانیم، زمان خاتمهی کار آنها است. برای نمونه در مثال قبل، نمایش Done پس از پایان تمام callbacks انجام شدهاست. فرض کنید، callback پایان عملیات را حذف کرده و متد finished را پس از فراخوانی متد observableQuery.Subscribe قرار دهیم:
observableQuery.Subscribe(onNext: number => { Console.WriteLine("number: {0}, on Thread-id: {1}", number, Thread.CurrentThread.ManagedThreadId); }/*, onCompleted: () => finished()*/); finished();
Thread-Id: 1 number: 1, on Thread-id: 3 Done! number: 2, on Thread-id: 3 number: 3, on Thread-id: 3 number: 4, on Thread-id: 3 number: 5, on Thread-id: 3
مدیریت استثناهای رخ داده در حین پردازش مجموعههای واکنشگرا
متد Subscribe دارای چندین overload است. تا اینجا نمونهای که دارای پارامترهای onNext و onCompleted بودند را بررسی کردیم. اگر بخواهیم مدیریت استثناءها را نیز در اینجا اضافه کنیم، فقط کافی است از overload دیگر آن که دارای پارامتر onError است، استفاده نمائیم:
observableQuery.Subscribe( onNext: number => Console.WriteLine(number), onError: exception => Console.WriteLine(exception.Message), onCompleted: () => finished());
مدیریت ترد اجرای نتایج حاصل از Rx در یک برنامهی دسکتاپ WPF یا WinForms
تا اینجا مشاهده کردیم که اجرای callbackهای observer در یک ترد دیگر، به سادگی تنظیم پارامتر scheduler متد ToObservable است. اما در برنامههای دسکتاپ برای به روز رسانی عناصر رابط کاربری، حتما باید در تردی قرار داشته باشیم که آن رابط کاربری در آن ایجاد شدهاست یا به عبارتی در ترد اصلی برنامه؛ در غیر اینصورت برنامه کرش خواهد کرد. مدیریت این مساله نیز در Rx بسیار سادهاست. ابتدا نیاز است بستهی Rx-WPF را نصب کرد:
PM> Install-Package Rx-WPF
observableQuery.ObserveOn(DispatcherScheduler.Current).Subscribe(...)
observableQuery.ObserveOnDispatcher().Subscribe(...)
و یا اگر از WinForms استفاده میکنید، ابتدا بستهی Rx خاص آنرا نصب کنید:
PM> Install-Package Rx-WinForms
observableQuery.ObserveOn(SynchronizationContext.Current).Subscribe(...)
یک نکته
در Rx فرض میشود که کوئری شما زمانبر است و callbackهای مشاهدهگر سریع عمل میکنند. بنابراین هدف از callbackهای آن، پردازشهای سنگین نیست. جهت آزمایش این مساله، اینبار query ابتدایی برنامه را به شکل ذیل تغییر دهید که در آن بازگشت زمانبر یک سری داده شبیه سازی شدهاند.
var query = Enumerable.Range(1, 5).Select(number => { Thread.Sleep(250); return number; });
نظرات اشتراکها
آیا blazor آینده ای دارد؟
مطلب عجیبی بود چون نویسنده MVP بوده یه زمانی. Blazor مسلماً هدفش برنامه نویسهای دات نت و سی شارپ هستن و اصولا کاری به برنامه نویسهای javascript نداره که اونا رو به خودش جذب کنه. نویسنده به Web assembly ایراد گرفته اما وسطهای متن اومده گفته تکنولوژیهای دیگه مثل flutter هم دارن میرن سمت web assembly و یه جایی هم اشاره میکنه که تکنولوژیهای دیگه که از Web assembly استفاده میکنن تو بحث کارایی بهتر از blazor هستند یا خواهند بود. نویسنده به razor اشاره میکنه که چرا برنامه نویس محدود شده. در کل نویسنده به دلایل نام معلوم مغرضانه به blazor حمله کرده. البته این موارد کم هم نیست مثلا توی youtube هم یکی رفته react رو با blazor مقایسه کرده که داخل یک صفحه چند هزار تا div ایجاد میکنه. حتی به خودش زحمت نداده کد Async بزنه و بجای concat از String builder استفاده کنه.
اشتراکها
یک کتابخانه Blazor
یک کتابخانه تقریبا جامع برای پروژههای Blazor تقریبا شامل تمام اجزاء مورد نیاز به همراه مستندات کامل ، قابلیت Theme و قالب های Bootstrap ، Material ، Ant Design ، Bulma و eFrolic . هنوز نسخه نهایی نیست اما بسیار مفید و کامل است.